@adhdev/daemon-core 0.9.82-rc.12 → 0.9.82-rc.121
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/subscription-updates.d.ts +1 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +21 -0
- package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +14 -0
- package/dist/commands/router.d.ts +22 -0
- package/dist/config/chat-history.d.ts +4 -0
- package/dist/config/mesh-config.d.ts +68 -1
- package/dist/git/git-commands.d.ts +5 -1
- package/dist/index.d.ts +15 -5
- package/dist/index.js +7029 -1368
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6984 -1351
- 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 +73 -0
- package/dist/mesh/mesh-events.d.ts +26 -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-refine-status.d.ts +27 -0
- package/dist/mesh/mesh-work-queue.d.ts +27 -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 +115 -0
- package/dist/providers/chat-message-normalization.d.ts +1 -0
- package/dist/providers/cli-provider-instance.d.ts +4 -1
- package/dist/repo-mesh-types.d.ts +62 -0
- package/dist/status/reporter.d.ts +2 -0
- package/package.json +3 -1
- package/src/boot/daemon-lifecycle.ts +1 -0
- package/src/chat/subscription-updates.ts +5 -1
- package/src/cli-adapters/provider-cli-adapter.ts +453 -17
- 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 +32 -10
- package/src/commands/chat-commands.ts +730 -30
- package/src/commands/cli-manager.ts +129 -1
- package/src/commands/handler.ts +8 -1
- package/src/commands/mesh-coordinator.ts +13 -143
- package/src/commands/router.ts +3239 -430
- package/src/config/chat-history.ts +37 -9
- 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/index.ts +41 -4
- 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 +31 -8
- package/src/mesh/mesh-active-work.ts +292 -0
- package/src/mesh/mesh-events.ts +389 -47
- 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-refine-status.ts +145 -0
- package/src/mesh/mesh-work-queue.ts +199 -137
- package/src/mesh/preview-freshness.ts +118 -0
- package/src/mesh/refine-config.ts +366 -0
- package/src/mesh/worktree-bootstrap-config.ts +234 -0
- package/src/providers/approval-utils.ts +12 -5
- package/src/providers/chat-message-normalization.ts +7 -12
- package/src/providers/cli-provider-instance.ts +201 -28
- package/src/providers/ide-provider-instance.ts +17 -3
- package/src/providers/provider-loader.ts +10 -4
- package/src/providers/read-chat-contract.ts +1 -1
- package/src/providers/version-archive.ts +38 -20
- package/src/repo-mesh-types.ts +67 -0
- package/src/status/reporter.ts +15 -0
- package/src/system/host-memory.ts +29 -12
|
@@ -12,8 +12,9 @@ import type { CliAdapter } from '../cli-adapter-types.js';
|
|
|
12
12
|
import { flattenContent, normalizeInputEnvelope, type InputEnvelope, type ProviderModule, type ProviderScripts } from '../providers/contracts.js';
|
|
13
13
|
import { assertProviderSupportsDeclaredInput, assertTextOnlyInput } from '../providers/provider-input-support.js';
|
|
14
14
|
import { validateReadChatResultPayload } from '../providers/read-chat-contract.js';
|
|
15
|
+
import { pickApprovalButton } from '../providers/approval-utils.js';
|
|
15
16
|
import type { ProviderInstance } from '../providers/provider-instance.js';
|
|
16
|
-
import { readProviderChatHistory } from '../config/chat-history.js';
|
|
17
|
+
import { isNativeSourceCanonicalHistory, readProviderChatHistory } from '../config/chat-history.js';
|
|
17
18
|
import { LOG, getRecentLogs } from '../logging/logger.js';
|
|
18
19
|
import { getRecentDebugTrace, recordDebugTrace } from '../logging/debug-trace.js';
|
|
19
20
|
import { buildChatMessageSignature, hashSignatureParts } from '../chat/chat-signatures.js';
|
|
@@ -24,6 +25,13 @@ import { filterUserFacingChatMessages, normalizeChatMessages } from '../provider
|
|
|
24
25
|
const RECENT_SEND_WINDOW_MS = 1200;
|
|
25
26
|
export const READ_CHAT_PROVIDER_EVAL_TIMEOUT_MS = 25_000;
|
|
26
27
|
const HERMES_CLI_STARTING_SEND_SETTLE_MS = 2_000;
|
|
28
|
+
const CLI_NATIVE_HISTORY_FRESH_MS = 5 * 60_000;
|
|
29
|
+
// Fallback list for supportsCliNativeTranscript() when the ProviderModule is
|
|
30
|
+
// unavailable (e.g. provider config not yet loaded). The authoritative check is
|
|
31
|
+
// provider.canonicalHistory via isNativeSourceCanonicalHistory(). New providers
|
|
32
|
+
// with native transcripts should set canonicalHistory in their provider.json
|
|
33
|
+
// rather than adding entries here.
|
|
34
|
+
const CLI_NATIVE_TRANSCRIPT_PROVIDERS = new Set(['codex-cli', 'claude-cli', 'hermes-cli', 'antigravity-cli']);
|
|
27
35
|
const recentSendByTarget = new Map<string, number>();
|
|
28
36
|
|
|
29
37
|
interface ApprovalSelectableInstance extends ProviderInstance {
|
|
@@ -48,6 +56,16 @@ function getTargetedCliAdapter(h: CommandHelpers, args: any, providerType?: stri
|
|
|
48
56
|
return h.getCliAdapter(args?.targetSessionId || providerType || h.currentSession?.providerType || h.currentManagerKey);
|
|
49
57
|
}
|
|
50
58
|
|
|
59
|
+
function getExplicitHistorySessionId(args: any): string | undefined {
|
|
60
|
+
const explicit = typeof args?.historySessionId === 'string' ? args.historySessionId.trim() : '';
|
|
61
|
+
if (explicit) return explicit;
|
|
62
|
+
|
|
63
|
+
const explicitProviderSessionId = typeof args?.providerSessionId === 'string' ? args.providerSessionId.trim() : '';
|
|
64
|
+
if (explicitProviderSessionId) return explicitProviderSessionId;
|
|
65
|
+
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
51
69
|
function getTargetInstance(h: CommandHelpers, args: any): ApprovalSelectableInstance | null {
|
|
52
70
|
const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
|
|
53
71
|
const sessionId = targetSessionId || h.currentSession?.sessionId || '';
|
|
@@ -139,19 +157,47 @@ async function waitOnceForFreshHermesCliStart(adapter: CliAdapter, log: (msg: st
|
|
|
139
157
|
}
|
|
140
158
|
|
|
141
159
|
function getHistorySessionId(h: CommandHelpers, args: any): string | undefined {
|
|
142
|
-
const explicit =
|
|
160
|
+
const explicit = getExplicitHistorySessionId(args);
|
|
143
161
|
if (explicit) return explicit;
|
|
144
162
|
|
|
145
|
-
const explicitProviderSessionId = typeof args?.providerSessionId === 'string' ? args.providerSessionId.trim() : '';
|
|
146
|
-
if (explicitProviderSessionId) return explicitProviderSessionId;
|
|
147
|
-
|
|
148
163
|
const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
|
|
149
164
|
if (!targetSessionId) return undefined;
|
|
150
165
|
|
|
151
|
-
const
|
|
166
|
+
const session = h.ctx.sessionRegistry?.get(targetSessionId) as any;
|
|
167
|
+
const registeredProviderSessionId = typeof session?.providerSessionId === 'string' ? session.providerSessionId.trim() : '';
|
|
168
|
+
if (registeredProviderSessionId) return registeredProviderSessionId;
|
|
169
|
+
|
|
170
|
+
const instance = getTargetInstance(h, args);
|
|
152
171
|
const state = instance?.getState?.();
|
|
153
172
|
const providerSessionId = typeof state?.providerSessionId === 'string' ? state.providerSessionId.trim() : '';
|
|
154
|
-
|
|
173
|
+
if (providerSessionId) return providerSessionId;
|
|
174
|
+
|
|
175
|
+
const currentSession = h.currentSession as any;
|
|
176
|
+
if (currentSession?.sessionId === targetSessionId) {
|
|
177
|
+
const currentProviderSessionId = typeof currentSession.providerSessionId === 'string'
|
|
178
|
+
? currentSession.providerSessionId.trim()
|
|
179
|
+
: '';
|
|
180
|
+
if (currentProviderSessionId) return currentProviderSessionId;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return targetSessionId;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function resolveCliNativeHistorySessionId(args: any, currentHistorySessionId: string | undefined, parsedProviderSessionId: string | undefined): string | undefined {
|
|
187
|
+
const explicit = getExplicitHistorySessionId(args);
|
|
188
|
+
if (explicit) return explicit;
|
|
189
|
+
|
|
190
|
+
const parsed = typeof parsedProviderSessionId === 'string' ? parsedProviderSessionId.trim() : '';
|
|
191
|
+
const current = typeof currentHistorySessionId === 'string' ? currentHistorySessionId.trim() : '';
|
|
192
|
+
const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
|
|
193
|
+
|
|
194
|
+
// getHistorySessionId falls back to the runtime session id when no native
|
|
195
|
+
// handle has been registered yet. For live CLI adapters the parser may
|
|
196
|
+
// already know the provider-native handle; prefer it over the runtime id so
|
|
197
|
+
// exact native reads do not miss the worker transcript and fall back to PTY
|
|
198
|
+
// or same-workspace history.
|
|
199
|
+
if (parsed && (!current || current === targetSessionId)) return parsed;
|
|
200
|
+
return current || parsed || undefined;
|
|
155
201
|
}
|
|
156
202
|
|
|
157
203
|
function getInteractionId(args: any): string | undefined {
|
|
@@ -221,6 +267,321 @@ function normalizeReadChatMessages(payload: Record<string, any>): ChatMessage[]
|
|
|
221
267
|
return normalizeChatMessages(messages);
|
|
222
268
|
}
|
|
223
269
|
|
|
270
|
+
function getMessageNewestReceivedAt(messages: Array<{ receivedAt?: unknown; timestamp?: unknown }>): number {
|
|
271
|
+
let newest = 0;
|
|
272
|
+
for (const message of messages) {
|
|
273
|
+
const receivedAt = Number(message?.receivedAt ?? message?.timestamp ?? 0);
|
|
274
|
+
if (Number.isFinite(receivedAt) && receivedAt > newest) newest = receivedAt;
|
|
275
|
+
}
|
|
276
|
+
return newest;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function readHistorySessionIdFromMessages(messages: ChatMessage[]): string | undefined {
|
|
280
|
+
for (const message of messages as Array<ChatMessage & { historySessionId?: unknown }>) {
|
|
281
|
+
const historySessionId = typeof message?.historySessionId === 'string' ? message.historySessionId.trim() : '';
|
|
282
|
+
if (historySessionId) return historySessionId;
|
|
283
|
+
}
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function shouldPreserveNativeIdentity(providerType: string, sessionId: string, message: ChatMessage): boolean {
|
|
288
|
+
const providerUnitKey = typeof message.providerUnitKey === 'string' ? message.providerUnitKey.trim() : '';
|
|
289
|
+
const turnKey = typeof message._turnKey === 'string' ? message._turnKey.trim() : '';
|
|
290
|
+
if (!providerUnitKey || !turnKey) return false;
|
|
291
|
+
if (providerType === 'hermes-cli' && sessionId) {
|
|
292
|
+
return providerUnitKey.startsWith(`${providerType}:native:${sessionId}:`)
|
|
293
|
+
&& turnKey.startsWith(`${providerType}:native-turn:${sessionId}:`);
|
|
294
|
+
}
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function normalizeNativeHistoryMessages(providerType: string, messages: ChatMessage[], nativeSessionId?: string): ChatMessage[] {
|
|
299
|
+
let turnIndex = 0;
|
|
300
|
+
return normalizeChatMessages(messages).map((message, index) => {
|
|
301
|
+
const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
|
|
302
|
+
const kind = typeof message.kind === 'string' && message.kind.trim() ? message.kind.trim() : (role === 'system' ? 'system' : 'standard');
|
|
303
|
+
if ((role === 'user' || role === 'human') && index > 0) turnIndex += 1;
|
|
304
|
+
const historySessionId = typeof (message as any).historySessionId === 'string'
|
|
305
|
+
? (message as any).historySessionId.trim()
|
|
306
|
+
: '';
|
|
307
|
+
const contentHash = hashSignatureParts([
|
|
308
|
+
providerType,
|
|
309
|
+
historySessionId,
|
|
310
|
+
String(message.receivedAt || message.timestamp || index),
|
|
311
|
+
role,
|
|
312
|
+
kind,
|
|
313
|
+
flattenContent(message.content),
|
|
314
|
+
]).slice(0, 12);
|
|
315
|
+
const nativeIdentitySessionId = historySessionId || (typeof nativeSessionId === 'string' ? nativeSessionId.trim() : '');
|
|
316
|
+
const preserveNativeIdentity = shouldPreserveNativeIdentity(providerType, nativeIdentitySessionId, message);
|
|
317
|
+
const existingProviderUnitKey = typeof message.providerUnitKey === 'string' ? message.providerUnitKey.trim() : '';
|
|
318
|
+
const existingTurnKey = typeof message._turnKey === 'string' ? message._turnKey.trim() : '';
|
|
319
|
+
const providerUnitKey = preserveNativeIdentity
|
|
320
|
+
? existingProviderUnitKey
|
|
321
|
+
: `${providerType}:native:${nativeIdentitySessionId || 'workspace'}:${index}:${role || 'message'}:${kind}:${contentHash}`;
|
|
322
|
+
const meta = message.meta && typeof message.meta === 'object' ? message.meta as Record<string, unknown> : undefined;
|
|
323
|
+
const isSystemSessionStart = role === 'system' || kind === 'system' || kind === 'session_start';
|
|
324
|
+
const isActivity = role === 'assistant' && (kind === 'tool' || kind === 'terminal' || kind === 'thought');
|
|
325
|
+
return {
|
|
326
|
+
...message,
|
|
327
|
+
role: role === 'human' ? 'user' : (role || 'assistant'),
|
|
328
|
+
kind: isSystemSessionStart ? 'system' : kind,
|
|
329
|
+
providerUnitKey,
|
|
330
|
+
bubbleId: typeof message.bubbleId === 'string' && message.bubbleId.trim()
|
|
331
|
+
&& preserveNativeIdentity
|
|
332
|
+
? message.bubbleId.trim()
|
|
333
|
+
: `bubble:${providerUnitKey}`,
|
|
334
|
+
_turnKey: preserveNativeIdentity
|
|
335
|
+
? existingTurnKey
|
|
336
|
+
: `${providerType}:native-turn:${nativeIdentitySessionId || 'workspace'}:${turnIndex}`,
|
|
337
|
+
bubbleState: message.bubbleState || 'final',
|
|
338
|
+
...(isSystemSessionStart ? {
|
|
339
|
+
visibility: message.visibility || 'hidden',
|
|
340
|
+
transcriptVisibility: message.transcriptVisibility || 'hidden',
|
|
341
|
+
audience: message.audience || 'internal',
|
|
342
|
+
source: message.source || 'runtime_status',
|
|
343
|
+
} : isActivity ? {
|
|
344
|
+
source: message.source || (kind === 'terminal' ? 'terminal_command' : 'tool_call'),
|
|
345
|
+
meta: { ...meta, label: message.senderName || meta?.label || (kind === 'terminal' ? 'Terminal' : 'Tool') },
|
|
346
|
+
} : {
|
|
347
|
+
source: message.source || (role === 'assistant' ? 'assistant_text' : undefined),
|
|
348
|
+
}),
|
|
349
|
+
} as ChatMessage;
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function buildCliMessageSourceProvenance(args: {
|
|
354
|
+
selected: 'native-history' | 'pty-parser';
|
|
355
|
+
provider: string;
|
|
356
|
+
nativeHandle?: string;
|
|
357
|
+
fallbackReason?: string;
|
|
358
|
+
nativeSource?: string;
|
|
359
|
+
sourcePath?: string;
|
|
360
|
+
sourceMtimeMs?: number;
|
|
361
|
+
nativeHistoryCoverage?: string;
|
|
362
|
+
partialReason?: string;
|
|
363
|
+
unavailableReason?: string;
|
|
364
|
+
nativeMessages?: ChatMessage[];
|
|
365
|
+
ptyMessages?: ChatMessage[];
|
|
366
|
+
returnedMessages?: ChatMessage[];
|
|
367
|
+
safeMapping?: boolean;
|
|
368
|
+
freshEnough?: boolean;
|
|
369
|
+
ptyStatusApprovalOnly?: boolean;
|
|
370
|
+
}): Record<string, unknown> {
|
|
371
|
+
const sourceMtimeMs = Number(args.sourceMtimeMs || 0);
|
|
372
|
+
const sourceMtimeAgeMs = sourceMtimeMs > 0 ? Math.max(0, Date.now() - sourceMtimeMs) : undefined;
|
|
373
|
+
const nativeMessages = args.nativeMessages || [];
|
|
374
|
+
const ptyMessages = args.ptyMessages || [];
|
|
375
|
+
const returnedMessages = args.returnedMessages || [];
|
|
376
|
+
return {
|
|
377
|
+
selected: args.selected,
|
|
378
|
+
provider: args.provider,
|
|
379
|
+
providerType: args.provider,
|
|
380
|
+
...(args.nativeHandle ? { nativeHandle: args.nativeHandle } : {}),
|
|
381
|
+
...(args.nativeHandle ? { nativeSessionId: args.nativeHandle } : {}),
|
|
382
|
+
...(args.fallbackReason ? { fallbackReason: args.fallbackReason } : {}),
|
|
383
|
+
...(args.nativeSource ? { nativeSource: args.nativeSource } : {}),
|
|
384
|
+
...(args.sourcePath ? { sourcePath: args.sourcePath } : {}),
|
|
385
|
+
...(args.nativeHistoryCoverage ? { nativeHistoryCoverage: args.nativeHistoryCoverage } : {}),
|
|
386
|
+
...(args.partialReason ? { partialReason: args.partialReason } : {}),
|
|
387
|
+
...(args.unavailableReason ? { unavailableReason: args.unavailableReason } : {}),
|
|
388
|
+
ptyStatusApprovalOnly: args.ptyStatusApprovalOnly === true,
|
|
389
|
+
staleness: {
|
|
390
|
+
sourceMtimeMs: sourceMtimeMs || undefined,
|
|
391
|
+
sourceMtimeAgeMs,
|
|
392
|
+
nativeNewestMessageAt: getMessageNewestReceivedAt(nativeMessages),
|
|
393
|
+
ptyNewestMessageAt: getMessageNewestReceivedAt(ptyMessages),
|
|
394
|
+
freshEnough: args.freshEnough === true,
|
|
395
|
+
},
|
|
396
|
+
coverage: {
|
|
397
|
+
nativeMessageCount: nativeMessages.length,
|
|
398
|
+
ptyMessageCount: ptyMessages.length,
|
|
399
|
+
returnedMessageCount: returnedMessages.length,
|
|
400
|
+
safeMapping: args.safeMapping === true,
|
|
401
|
+
},
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function buildNativeHistoryFallbackReason(args: {
|
|
406
|
+
providerType: string;
|
|
407
|
+
provider?: ProviderModule;
|
|
408
|
+
nativeSource?: string;
|
|
409
|
+
nativeHistoryCoverage?: string;
|
|
410
|
+
unavailableReason?: string;
|
|
411
|
+
nativeMessageCount: number;
|
|
412
|
+
safeMapping: boolean;
|
|
413
|
+
freshEnough: boolean;
|
|
414
|
+
}): string {
|
|
415
|
+
if (!supportsCliNativeTranscript(args.providerType, args.provider)) return 'provider_native_transcript_not_supported';
|
|
416
|
+
if (args.unavailableReason) return `native_history_unavailable:${args.unavailableReason}`;
|
|
417
|
+
if (args.nativeSource === 'native-unavailable') return 'native_history_unavailable';
|
|
418
|
+
if (args.nativeHistoryCoverage === 'partial') return 'native_history_partial';
|
|
419
|
+
if (args.nativeHistoryCoverage === 'unavailable') return 'native_history_unavailable';
|
|
420
|
+
if (args.nativeSource && args.nativeSource !== 'provider-native') return `native_history_source_${args.nativeSource}`;
|
|
421
|
+
if (args.nativeMessageCount <= 0) return 'native_history_empty';
|
|
422
|
+
if (!args.safeMapping) return 'native_history_not_safely_mapped';
|
|
423
|
+
if (!args.freshEnough) return 'native_history_stale';
|
|
424
|
+
return 'native_history_not_selected';
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function supportsCliNativeTranscript(providerType: string, provider?: ProviderModule): boolean {
|
|
428
|
+
if (CLI_NATIVE_TRANSCRIPT_PROVIDERS.has(providerType)) return true;
|
|
429
|
+
return provider?.category === 'cli' && isNativeSourceCanonicalHistory(provider?.canonicalHistory);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function getComparableVisibleText(message: ChatMessage | undefined): string {
|
|
433
|
+
if (!message) return '';
|
|
434
|
+
const role = String((message as any).role || '').trim().toLowerCase();
|
|
435
|
+
if (role !== 'user' && role !== 'assistant') return '';
|
|
436
|
+
const kind = String((message as any).kind || 'standard').trim().toLowerCase();
|
|
437
|
+
if (kind && kind !== 'standard') return '';
|
|
438
|
+
const content = flattenContent((message as any).content).replace(/\s+/g, ' ').trim();
|
|
439
|
+
return content;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function hasOverlappingVisibleConversationText(nativeMessages: ChatMessage[], ptyMessages: ChatMessage[]): boolean {
|
|
443
|
+
const nativeTexts = nativeMessages.map(getComparableVisibleText).filter(Boolean);
|
|
444
|
+
const ptyTexts = ptyMessages.map(getComparableVisibleText).filter(Boolean);
|
|
445
|
+
if (nativeTexts.length === 0 || ptyTexts.length === 0) return false;
|
|
446
|
+
for (const nativeText of nativeTexts) {
|
|
447
|
+
for (const ptyText of ptyTexts) {
|
|
448
|
+
if (nativeText === ptyText) return true;
|
|
449
|
+
const shorter = nativeText.length <= ptyText.length ? nativeText : ptyText;
|
|
450
|
+
const longer = nativeText.length <= ptyText.length ? ptyText : nativeText;
|
|
451
|
+
if (shorter.length >= 32 && longer.includes(shorter)) return true;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function hasSafeNativeHistoryMapping(args: {
|
|
458
|
+
historySessionId?: string;
|
|
459
|
+
providerSessionId?: string;
|
|
460
|
+
workspace?: string;
|
|
461
|
+
nativeMessages: ChatMessage[];
|
|
462
|
+
ptyMessages?: ChatMessage[];
|
|
463
|
+
requireWorkspaceContentOverlap?: boolean;
|
|
464
|
+
}): boolean {
|
|
465
|
+
const isCoordinatorTranscript = args.nativeMessages.some((m: any) => {
|
|
466
|
+
const text = typeof m?.content === 'string' ? m.content : JSON.stringify(m?.content || '');
|
|
467
|
+
return text.includes('mesh_send_task') || text.includes('mesh_status') || text.includes('mesh_read_chat') || text.includes('mesh_launch_session');
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
const explicitSessionId = String(args.historySessionId || args.providerSessionId || '').trim();
|
|
471
|
+
if (explicitSessionId) {
|
|
472
|
+
const messageSessionIds = args.nativeMessages
|
|
473
|
+
.map((message: any) => typeof message?.historySessionId === 'string' ? message.historySessionId.trim() : '')
|
|
474
|
+
.filter(Boolean);
|
|
475
|
+
if (messageSessionIds.length > 0) {
|
|
476
|
+
return messageSessionIds.some((id) => id === explicitSessionId);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Messages carry no historySessionId — cannot confirm they belong to the requested session.
|
|
480
|
+
// Only allow a coordinator transcript that is also confirmed by the PTY side; otherwise
|
|
481
|
+
// fail closed so a same-workspace session's history is never silently accepted.
|
|
482
|
+
if (isCoordinatorTranscript && args.ptyMessages && args.ptyMessages.length > 0) {
|
|
483
|
+
const ptyHasCoordinator = args.ptyMessages.some((m: any) => {
|
|
484
|
+
const text = typeof m?.content === 'string' ? m.content : JSON.stringify(m?.content || '');
|
|
485
|
+
return text.includes('mesh_send_task') || text.includes('mesh_status') || text.includes('mesh_read_chat');
|
|
486
|
+
});
|
|
487
|
+
return ptyHasCoordinator;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// No historySessionId in messages and no coordinator cross-check: fail closed.
|
|
491
|
+
// Workspace-only matching must not override an explicit session identity.
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
const workspace = String(args.workspace || '').trim();
|
|
495
|
+
if (!workspace) return false;
|
|
496
|
+
const workspaceMatches = args.nativeMessages.some((message: any) => String(message?.workspace || '').trim() === workspace);
|
|
497
|
+
if (!workspaceMatches) return false;
|
|
498
|
+
|
|
499
|
+
if (isCoordinatorTranscript && args.ptyMessages && args.ptyMessages.length > 0) {
|
|
500
|
+
const ptyHasCoordinator = args.ptyMessages.some((m: any) => {
|
|
501
|
+
const text = typeof m?.content === 'string' ? m.content : JSON.stringify(m?.content || '');
|
|
502
|
+
return text.includes('mesh_send_task') || text.includes('mesh_status') || text.includes('mesh_read_chat');
|
|
503
|
+
});
|
|
504
|
+
if (!ptyHasCoordinator) {
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (!args.requireWorkspaceContentOverlap) return true;
|
|
510
|
+
return hasOverlappingVisibleConversationText(args.nativeMessages, args.ptyMessages || []);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Provenance boundary: workspace-only native history lookup is never safe
|
|
514
|
+
// because multiple concurrent sessions sharing the same cwd would alias each
|
|
515
|
+
// other. historySessionId (the provider-native session key) is required to
|
|
516
|
+
// establish ownership. hasSafeNativeHistoryMapping() enforces the same
|
|
517
|
+
// invariant after the read; both guards must hold for native history to be used.
|
|
518
|
+
function readCliProviderNativeHistory(agentStr: string, args: {
|
|
519
|
+
canonicalHistory?: ProviderModule['canonicalHistory'];
|
|
520
|
+
historySessionId?: string;
|
|
521
|
+
workspace?: string;
|
|
522
|
+
offset: number;
|
|
523
|
+
limit: number;
|
|
524
|
+
excludeRecentCount: number;
|
|
525
|
+
historyBehavior?: ProviderModule['historyBehavior'];
|
|
526
|
+
scripts?: ProviderScripts;
|
|
527
|
+
}): ReturnType<typeof readProviderChatHistory> & { lookup: 'session' | 'workspace' } {
|
|
528
|
+
if (!args.historySessionId) {
|
|
529
|
+
return {
|
|
530
|
+
messages: [],
|
|
531
|
+
hasMore: false,
|
|
532
|
+
source: 'native-unavailable',
|
|
533
|
+
unavailableReason: 'native_history_workspace_only_lookup_unsafe',
|
|
534
|
+
lookup: 'session',
|
|
535
|
+
} as ReturnType<typeof readProviderChatHistory> & { lookup: 'session' | 'workspace' };
|
|
536
|
+
}
|
|
537
|
+
const sessionHistory = readProviderChatHistory(agentStr, {
|
|
538
|
+
canonicalHistory: args.canonicalHistory,
|
|
539
|
+
historySessionId: args.historySessionId,
|
|
540
|
+
workspace: args.workspace,
|
|
541
|
+
offset: args.offset,
|
|
542
|
+
limit: args.limit,
|
|
543
|
+
excludeRecentCount: args.excludeRecentCount,
|
|
544
|
+
historyBehavior: args.historyBehavior,
|
|
545
|
+
scripts: args.scripts as any,
|
|
546
|
+
});
|
|
547
|
+
// Native transcripts are keyed by provider/runtime session identity. Falling
|
|
548
|
+
// back to workspace makes concurrent local Codex/Hermes sessions alias each
|
|
549
|
+
// other when they share the same cwd.
|
|
550
|
+
return { ...(sessionHistory as any), lookup: 'session' };
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function isNativeHistoryFreshEnough(args: {
|
|
554
|
+
sourceMtimeMs?: number;
|
|
555
|
+
nativeMessages: ChatMessage[];
|
|
556
|
+
ptyMessages: ChatMessage[];
|
|
557
|
+
}): boolean {
|
|
558
|
+
const nativeNewest = getMessageNewestReceivedAt(args.nativeMessages);
|
|
559
|
+
const ptyNewest = getMessageNewestReceivedAt(args.ptyMessages);
|
|
560
|
+
if (nativeNewest > 0 && nativeNewest >= ptyNewest) return true;
|
|
561
|
+
const sourceMtimeMs = Number(args.sourceMtimeMs || 0);
|
|
562
|
+
if (sourceMtimeMs > 0 && Date.now() - sourceMtimeMs <= CLI_NATIVE_HISTORY_FRESH_MS) return true;
|
|
563
|
+
return ptyNewest === 0 && nativeNewest > 0;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function shouldPreserveReadChatPayloadField(key: string): boolean {
|
|
567
|
+
return key === 'messageSource' || key === 'transcriptProvenance';
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function updateMessageSourceReturnedCount(value: unknown, returnedMessageCount: number): unknown {
|
|
571
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return value;
|
|
572
|
+
const record = value as Record<string, unknown>;
|
|
573
|
+
const coverage = record.coverage && typeof record.coverage === 'object' && !Array.isArray(record.coverage)
|
|
574
|
+
? record.coverage as Record<string, unknown>
|
|
575
|
+
: undefined;
|
|
576
|
+
if (!coverage) return value;
|
|
577
|
+
return {
|
|
578
|
+
...record,
|
|
579
|
+
coverage: {
|
|
580
|
+
...coverage,
|
|
581
|
+
returnedMessageCount,
|
|
582
|
+
},
|
|
583
|
+
};
|
|
584
|
+
}
|
|
224
585
|
|
|
225
586
|
function deriveHistoryDedupKey(message: ChatMessage & { _unitKey?: string; _turnKey?: string }): string | undefined {
|
|
226
587
|
const unitKey = typeof message._unitKey === 'string' ? message._unitKey.trim() : '';
|
|
@@ -281,7 +642,7 @@ function normalizeReadChatCommandStatus(status: unknown, activeModal: unknown):
|
|
|
281
642
|
}
|
|
282
643
|
switch (raw) {
|
|
283
644
|
case 'starting':
|
|
284
|
-
return hasNonEmptyModalButtons(activeModal) ? 'waiting_approval' : '
|
|
645
|
+
return hasNonEmptyModalButtons(activeModal) ? 'waiting_approval' : 'starting';
|
|
285
646
|
case 'stopped':
|
|
286
647
|
case 'disconnected':
|
|
287
648
|
case 'not_monitored':
|
|
@@ -295,6 +656,16 @@ function isGeneratingLikeStatus(status: unknown): boolean {
|
|
|
295
656
|
return status === 'generating' || status === 'streaming' || status === 'long_generating' || status === 'starting';
|
|
296
657
|
}
|
|
297
658
|
|
|
659
|
+
function hasVisibleAssistantMessage(messages: unknown[] | undefined): boolean {
|
|
660
|
+
if (!Array.isArray(messages)) return false;
|
|
661
|
+
return messages.some((message: any) => {
|
|
662
|
+
if (!message || message.role !== 'assistant') return false;
|
|
663
|
+
const kind = typeof message.kind === 'string' ? message.kind : 'standard';
|
|
664
|
+
if (kind !== 'standard') return false;
|
|
665
|
+
return String(message.content || '').trim().length > 0;
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
|
|
298
669
|
function shouldTrustCliAdapterTerminalStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any): boolean {
|
|
299
670
|
if (!isGeneratingLikeStatus(parsedStatus)) return false;
|
|
300
671
|
if (hasNonEmptyModalButtons(activeModal)) return false;
|
|
@@ -304,7 +675,26 @@ function shouldTrustCliAdapterTerminalStatus(parsedStatus: unknown, activeModal:
|
|
|
304
675
|
return true;
|
|
305
676
|
}
|
|
306
677
|
|
|
307
|
-
function normalizeCliReadChatStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any): string {
|
|
678
|
+
function normalizeCliReadChatStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any, parsedMessages?: unknown[]): string {
|
|
679
|
+
const adapterRawStatus = typeof adapterStatus?.status === 'string' ? adapterStatus.status.trim() : '';
|
|
680
|
+
if (adapterRawStatus === 'starting'
|
|
681
|
+
&& isGeneratingLikeStatus(parsedStatus)
|
|
682
|
+
&& !hasNonEmptyModalButtons(activeModal)
|
|
683
|
+
&& Array.isArray(parsedMessages)
|
|
684
|
+
&& parsedMessages.length === 0
|
|
685
|
+
&& Array.isArray(adapterStatus?.messages)
|
|
686
|
+
&& adapterStatus.messages.length === 0
|
|
687
|
+
&& !(typeof adapter.isProcessing === 'function' && adapter.isProcessing())) {
|
|
688
|
+
return 'starting';
|
|
689
|
+
}
|
|
690
|
+
if (
|
|
691
|
+
isGeneratingLikeStatus(adapterRawStatus)
|
|
692
|
+
&& parsedStatus === 'idle'
|
|
693
|
+
&& !hasNonEmptyModalButtons(activeModal)
|
|
694
|
+
&& !hasVisibleAssistantMessage(parsedMessages)
|
|
695
|
+
) {
|
|
696
|
+
return adapterRawStatus;
|
|
697
|
+
}
|
|
308
698
|
if (shouldTrustCliAdapterTerminalStatus(parsedStatus, activeModal, adapter, adapterStatus)) return 'idle';
|
|
309
699
|
return typeof parsedStatus === 'string' && parsedStatus.trim() ? parsedStatus : 'idle';
|
|
310
700
|
}
|
|
@@ -342,6 +732,13 @@ function buildReadChatCommandResult(payload: Record<string, any>, args: any): Co
|
|
|
342
732
|
const visibleMessages = filterUserFacingChatMessages(messages);
|
|
343
733
|
const sync = buildFullTail(visibleMessages, normalizeReadChatTailLimit(args));
|
|
344
734
|
const hiddenMsgCount = Math.max(0, messages.length - visibleMessages.length);
|
|
735
|
+
const preservedPayloadFields = Object.fromEntries(Object.entries(payload).filter(([key]) => shouldPreserveReadChatPayloadField(key)));
|
|
736
|
+
if (preservedPayloadFields.messageSource) {
|
|
737
|
+
preservedPayloadFields.messageSource = updateMessageSourceReturnedCount(preservedPayloadFields.messageSource, sync.messages.length);
|
|
738
|
+
}
|
|
739
|
+
if (preservedPayloadFields.transcriptProvenance) {
|
|
740
|
+
preservedPayloadFields.transcriptProvenance = updateMessageSourceReturnedCount(preservedPayloadFields.transcriptProvenance, sync.messages.length);
|
|
741
|
+
}
|
|
345
742
|
const returnedDebugReadChat = debugReadChat
|
|
346
743
|
? {
|
|
347
744
|
...debugReadChat,
|
|
@@ -356,6 +753,7 @@ function buildReadChatCommandResult(payload: Record<string, any>, args: any): Co
|
|
|
356
753
|
return {
|
|
357
754
|
success: true,
|
|
358
755
|
...validatedPayload,
|
|
756
|
+
...preservedPayloadFields,
|
|
359
757
|
messages: sync.messages,
|
|
360
758
|
totalMessages: sync.totalMessages,
|
|
361
759
|
...(returnedDebugReadChat ? { debugReadChat: returnedDebugReadChat } : {}),
|
|
@@ -584,6 +982,8 @@ function buildChatDebugBundleSummary(bundle: Record<string, unknown>): Record<st
|
|
|
584
982
|
adapterStatus: debugReadChat.adapterStatus,
|
|
585
983
|
parsedStatus: debugReadChat.parsedStatus,
|
|
586
984
|
returnedStatus: debugReadChat.returnedStatus,
|
|
985
|
+
selectedMessageSource: debugReadChat.selectedMessageSource,
|
|
986
|
+
messageSource: debugReadChat.messageSource,
|
|
587
987
|
parsedMsgCount: debugReadChat.parsedMsgCount,
|
|
588
988
|
returnedMsgCount: debugReadChat.returnedMsgCount,
|
|
589
989
|
shouldPreferAdapterMessages: debugReadChat.shouldPreferAdapterMessages,
|
|
@@ -646,6 +1046,8 @@ export async function handleGetChatDebugBundle(h: CommandHelpers, args: any): Pr
|
|
|
646
1046
|
providerSessionId: readResult.providerSessionId,
|
|
647
1047
|
transcriptAuthority: readResult.transcriptAuthority,
|
|
648
1048
|
coverage: readResult.coverage,
|
|
1049
|
+
messageSource: readResult.messageSource,
|
|
1050
|
+
transcriptProvenance: readResult.transcriptProvenance,
|
|
649
1051
|
activeModal: readResult.activeModal,
|
|
650
1052
|
messagesTail: Array.isArray(readResult.messages) ? readResult.messages.slice(-20) : [],
|
|
651
1053
|
debugReadChat: readResult.debugReadChat,
|
|
@@ -817,16 +1219,56 @@ export async function handleChatHistory(h: CommandHelpers, args: any): Promise<C
|
|
|
817
1219
|
: typeof (h.currentSession as any)?.workspace === 'string'
|
|
818
1220
|
? (h.currentSession as any).workspace
|
|
819
1221
|
: undefined;
|
|
820
|
-
const
|
|
821
|
-
|
|
822
|
-
historySessionId
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
1222
|
+
const exactNativeHistoryScope = Boolean(
|
|
1223
|
+
(typeof args?.targetSessionId === 'string' && args.targetSessionId.trim())
|
|
1224
|
+
|| (typeof args?.historySessionId === 'string' && args.historySessionId.trim())
|
|
1225
|
+
|| (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
|
|
1226
|
+
);
|
|
1227
|
+
const result = supportsCliNativeTranscript(agentStr, provider) && isNativeSourceCanonicalHistory(provider?.canonicalHistory)
|
|
1228
|
+
? readCliProviderNativeHistory(agentStr, {
|
|
1229
|
+
canonicalHistory: provider?.canonicalHistory,
|
|
1230
|
+
historySessionId,
|
|
1231
|
+
workspace,
|
|
1232
|
+
offset: offset || 0,
|
|
1233
|
+
limit: limit || 30,
|
|
1234
|
+
excludeRecentCount,
|
|
1235
|
+
historyBehavior: provider?.historyBehavior,
|
|
1236
|
+
scripts: provider?.scripts as any,
|
|
1237
|
+
})
|
|
1238
|
+
: readProviderChatHistory(agentStr, {
|
|
1239
|
+
canonicalHistory: provider?.canonicalHistory,
|
|
1240
|
+
historySessionId,
|
|
1241
|
+
workspace,
|
|
1242
|
+
offset: offset || 0,
|
|
1243
|
+
limit: limit || 30,
|
|
1244
|
+
excludeRecentCount,
|
|
1245
|
+
historyBehavior: provider?.historyBehavior,
|
|
1246
|
+
scripts: provider?.scripts as any,
|
|
1247
|
+
});
|
|
1248
|
+
if (supportsCliNativeTranscript(agentStr, provider) && isNativeSourceCanonicalHistory(provider?.canonicalHistory)) {
|
|
1249
|
+
const lookup = (result as any).lookup === 'workspace' ? 'workspace' : 'session';
|
|
1250
|
+
const messages = Array.isArray((result as any).messages)
|
|
1251
|
+
? normalizeNativeHistoryMessages(agentStr, (result as any).messages as ChatMessage[], (result as any)?.providerSessionId)
|
|
1252
|
+
: [];
|
|
1253
|
+
const historyProviderSessionId = typeof (result as any)?.providerSessionId === 'string'
|
|
1254
|
+
? (result as any).providerSessionId
|
|
1255
|
+
: readHistorySessionIdFromMessages(messages) || historySessionId;
|
|
1256
|
+
const safeMapping = hasSafeNativeHistoryMapping({
|
|
1257
|
+
historySessionId: lookup === 'workspace' ? undefined : historySessionId,
|
|
1258
|
+
providerSessionId: lookup === 'workspace' ? undefined : historyProviderSessionId,
|
|
1259
|
+
workspace,
|
|
1260
|
+
nativeMessages: messages,
|
|
1261
|
+
});
|
|
1262
|
+
if ((result as any).source === 'provider-native' && messages.length > 0 && !safeMapping) {
|
|
1263
|
+
return {
|
|
1264
|
+
success: true,
|
|
1265
|
+
messages: [],
|
|
1266
|
+
hasMore: false,
|
|
1267
|
+
source: 'native-unavailable',
|
|
1268
|
+
agent: agentStr,
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
830
1272
|
return { success: true, ...result, agent: agentStr };
|
|
831
1273
|
} catch (e: any) {
|
|
832
1274
|
return { success: false, error: e.message };
|
|
@@ -874,7 +1316,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
874
1316
|
? parsedRecord.coverage
|
|
875
1317
|
: undefined;
|
|
876
1318
|
const activeModal = parsedRecord.activeModal ?? parsedRecord.modal ?? null;
|
|
877
|
-
const returnedStatus = normalizeCliReadChatStatus(parsedRecord.status, activeModal, adapter, adapterStatus);
|
|
1319
|
+
const returnedStatus = normalizeCliReadChatStatus(parsedRecord.status, activeModal, adapter, adapterStatus, parsedRecord.messages);
|
|
878
1320
|
const runtimeMessageMerger = getTargetInstance(h, args) as RuntimeChatMessageMerger | null;
|
|
879
1321
|
const parsedMessages = finalizeStreamingMessagesWhenIdle(parsedRecord.messages as ChatMessage[], returnedStatus);
|
|
880
1322
|
const returnedMessages = runtimeMessageMerger?.category === 'cli'
|
|
@@ -882,25 +1324,194 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
882
1324
|
&& typeof runtimeMessageMerger.mergeRuntimeChatMessages === 'function'
|
|
883
1325
|
? runtimeMessageMerger.mergeRuntimeChatMessages(parsedMessages)
|
|
884
1326
|
: parsedMessages;
|
|
1327
|
+
const providerType = provider?.type || adapter.cliType;
|
|
1328
|
+
let selectedMessages = returnedMessages;
|
|
1329
|
+
let selectedTitle = title;
|
|
1330
|
+
let selectedProviderSessionId = providerSessionId;
|
|
1331
|
+
let selectedTranscriptAuthority = transcriptAuthority;
|
|
1332
|
+
let selectedCoverage = coverage;
|
|
1333
|
+
let messageSource = buildCliMessageSourceProvenance({
|
|
1334
|
+
selected: 'pty-parser',
|
|
1335
|
+
provider: adapter.cliType,
|
|
1336
|
+
fallbackReason: supportsCliNativeTranscript(providerType, provider) ? 'native_history_not_checked' : 'provider_native_transcript_not_supported',
|
|
1337
|
+
ptyMessages: returnedMessages,
|
|
1338
|
+
returnedMessages,
|
|
1339
|
+
ptyStatusApprovalOnly: false,
|
|
1340
|
+
});
|
|
1341
|
+
|
|
1342
|
+
if (supportsCliNativeTranscript(providerType, provider) && isNativeSourceCanonicalHistory(provider?.canonicalHistory)) {
|
|
1343
|
+
const agentStr = provider?.type || args?.agentType || getCurrentProviderType(h, adapter.cliType);
|
|
1344
|
+
const workspace = typeof args?.workspace === 'string'
|
|
1345
|
+
? args.workspace
|
|
1346
|
+
: typeof (h.currentSession as any)?.workspace === 'string'
|
|
1347
|
+
? (h.currentSession as any).workspace
|
|
1348
|
+
: typeof adapter.workingDir === 'string'
|
|
1349
|
+
? adapter.workingDir
|
|
1350
|
+
: undefined;
|
|
1351
|
+
const nativeHistoryLimit = Math.max(
|
|
1352
|
+
normalizeReadChatTailLimit(args) || 0,
|
|
1353
|
+
returnedMessages.length,
|
|
1354
|
+
200,
|
|
1355
|
+
);
|
|
1356
|
+
const nativeHistorySessionId = resolveCliNativeHistorySessionId(args, historySessionId, providerSessionId);
|
|
1357
|
+
const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
|
|
1358
|
+
const exactNativeHistoryScope = Boolean(
|
|
1359
|
+
(typeof args?.historySessionId === 'string' && args.historySessionId.trim())
|
|
1360
|
+
|| (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
|
|
1361
|
+
|| providerSessionId
|
|
1362
|
+
|| (nativeHistorySessionId && nativeHistorySessionId !== targetSessionId)
|
|
1363
|
+
|| ((h.currentSession as any)?.sessionId === args?.targetSessionId && typeof (h.currentSession as any)?.providerSessionId === 'string' && (h.currentSession as any).providerSessionId.trim())
|
|
1364
|
+
);
|
|
1365
|
+
let nativeHistory: (ReturnType<typeof readProviderChatHistory> & { lookup?: 'session' | 'workspace' }) | null = null;
|
|
1366
|
+
try {
|
|
1367
|
+
nativeHistory = readCliProviderNativeHistory(agentStr, {
|
|
1368
|
+
canonicalHistory: provider?.canonicalHistory,
|
|
1369
|
+
historySessionId: nativeHistorySessionId,
|
|
1370
|
+
workspace,
|
|
1371
|
+
offset: 0,
|
|
1372
|
+
limit: nativeHistoryLimit,
|
|
1373
|
+
excludeRecentCount: 0,
|
|
1374
|
+
historyBehavior: provider?.historyBehavior,
|
|
1375
|
+
scripts: provider?.scripts as any,
|
|
1376
|
+
});
|
|
1377
|
+
} catch (error: any) {
|
|
1378
|
+
const fallbackReason = `native_history_error:${error?.message || String(error)}`;
|
|
1379
|
+
messageSource = buildCliMessageSourceProvenance({
|
|
1380
|
+
selected: 'pty-parser',
|
|
1381
|
+
provider: adapter.cliType,
|
|
1382
|
+
fallbackReason,
|
|
1383
|
+
ptyMessages: returnedMessages,
|
|
1384
|
+
returnedMessages,
|
|
1385
|
+
ptyStatusApprovalOnly: false,
|
|
1386
|
+
});
|
|
1387
|
+
nativeHistory = null;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
if (nativeHistory) {
|
|
1391
|
+
const nativeMessages = Array.isArray((nativeHistory as any).messages)
|
|
1392
|
+
? normalizeNativeHistoryMessages(agentStr, (nativeHistory as any).messages as ChatMessage[], (nativeHistory as any)?.providerSessionId)
|
|
1393
|
+
: [];
|
|
1394
|
+
const historyProviderSessionId = typeof (nativeHistory as any)?.providerSessionId === 'string'
|
|
1395
|
+
? (nativeHistory as any).providerSessionId
|
|
1396
|
+
: readHistorySessionIdFromMessages(nativeMessages) || nativeHistorySessionId || historySessionId;
|
|
1397
|
+
const nativeHistoryCoverage = typeof (nativeHistory as any)?.nativeHistoryCoverage === 'string'
|
|
1398
|
+
? (nativeHistory as any).nativeHistoryCoverage
|
|
1399
|
+
: undefined;
|
|
1400
|
+
const partialReason = typeof (nativeHistory as any)?.partialReason === 'string'
|
|
1401
|
+
? (nativeHistory as any).partialReason
|
|
1402
|
+
: undefined;
|
|
1403
|
+
const unavailableReason = typeof (nativeHistory as any)?.unavailableReason === 'string'
|
|
1404
|
+
? (nativeHistory as any).unavailableReason
|
|
1405
|
+
: undefined;
|
|
1406
|
+
const lookup = (nativeHistory as any).lookup === 'workspace' ? 'workspace' : 'session';
|
|
1407
|
+
const nativeHistorySessionForMapping = adapter.cliType === 'antigravity-cli'
|
|
1408
|
+
&& historyProviderSessionId
|
|
1409
|
+
&& nativeHistorySessionId
|
|
1410
|
+
&& historyProviderSessionId !== nativeHistorySessionId
|
|
1411
|
+
? undefined
|
|
1412
|
+
: nativeHistorySessionId;
|
|
1413
|
+
const safeMapping = hasSafeNativeHistoryMapping({
|
|
1414
|
+
historySessionId: lookup === 'workspace' ? undefined : nativeHistorySessionForMapping,
|
|
1415
|
+
providerSessionId: lookup === 'workspace' ? undefined : historyProviderSessionId || providerSessionId,
|
|
1416
|
+
workspace,
|
|
1417
|
+
nativeMessages,
|
|
1418
|
+
ptyMessages: returnedMessages,
|
|
1419
|
+
requireWorkspaceContentOverlap: lookup === 'workspace' && !exactNativeHistoryScope,
|
|
1420
|
+
});
|
|
1421
|
+
const freshEnough = isNativeHistoryFreshEnough({
|
|
1422
|
+
sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
|
|
1423
|
+
nativeMessages,
|
|
1424
|
+
ptyMessages: returnedMessages,
|
|
1425
|
+
});
|
|
1426
|
+
const nativeUsableForChatMessages = (nativeHistory as any).source === 'provider-native'
|
|
1427
|
+
&& nativeMessages.length > 0
|
|
1428
|
+
&& nativeHistoryCoverage !== 'partial'
|
|
1429
|
+
&& nativeHistoryCoverage !== 'unavailable'
|
|
1430
|
+
&& safeMapping;
|
|
1431
|
+
const allowStaleNativeChatMessages = adapter.cliType === 'antigravity-cli' && nativeUsableForChatMessages;
|
|
1432
|
+
if (nativeUsableForChatMessages && (freshEnough || allowStaleNativeChatMessages)) {
|
|
1433
|
+
selectedMessages = finalizeStreamingMessagesWhenIdle(nativeMessages, returnedStatus);
|
|
1434
|
+
selectedProviderSessionId = historyProviderSessionId || providerSessionId;
|
|
1435
|
+
selectedTranscriptAuthority = 'provider';
|
|
1436
|
+
selectedCoverage = (nativeHistory as any).hasMore ? 'tail' : 'full';
|
|
1437
|
+
messageSource = buildCliMessageSourceProvenance({
|
|
1438
|
+
selected: 'native-history',
|
|
1439
|
+
provider: adapter.cliType,
|
|
1440
|
+
nativeHandle: selectedProviderSessionId || nativeHistorySessionId || historySessionId,
|
|
1441
|
+
nativeSource: (nativeHistory as any).source,
|
|
1442
|
+
sourcePath: (nativeHistory as any).sourcePath,
|
|
1443
|
+
sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
|
|
1444
|
+
nativeHistoryCoverage,
|
|
1445
|
+
partialReason,
|
|
1446
|
+
unavailableReason,
|
|
1447
|
+
nativeMessages,
|
|
1448
|
+
ptyMessages: returnedMessages,
|
|
1449
|
+
returnedMessages: selectedMessages,
|
|
1450
|
+
safeMapping,
|
|
1451
|
+
freshEnough,
|
|
1452
|
+
ptyStatusApprovalOnly: true,
|
|
1453
|
+
});
|
|
1454
|
+
} else {
|
|
1455
|
+
const fallbackReason = buildNativeHistoryFallbackReason({
|
|
1456
|
+
providerType,
|
|
1457
|
+
provider,
|
|
1458
|
+
nativeSource: (nativeHistory as any).source,
|
|
1459
|
+
nativeHistoryCoverage,
|
|
1460
|
+
unavailableReason,
|
|
1461
|
+
nativeMessageCount: nativeMessages.length,
|
|
1462
|
+
safeMapping,
|
|
1463
|
+
freshEnough,
|
|
1464
|
+
});
|
|
1465
|
+
messageSource = buildCliMessageSourceProvenance({
|
|
1466
|
+
selected: 'pty-parser',
|
|
1467
|
+
provider: adapter.cliType,
|
|
1468
|
+
nativeHandle: historyProviderSessionId || nativeHistorySessionId || historySessionId,
|
|
1469
|
+
fallbackReason,
|
|
1470
|
+
nativeSource: (nativeHistory as any).source,
|
|
1471
|
+
sourcePath: (nativeHistory as any).sourcePath,
|
|
1472
|
+
sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
|
|
1473
|
+
nativeHistoryCoverage,
|
|
1474
|
+
partialReason,
|
|
1475
|
+
unavailableReason,
|
|
1476
|
+
nativeMessages,
|
|
1477
|
+
ptyMessages: returnedMessages,
|
|
1478
|
+
returnedMessages,
|
|
1479
|
+
safeMapping,
|
|
1480
|
+
freshEnough,
|
|
1481
|
+
ptyStatusApprovalOnly: false,
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
885
1486
|
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
1487
|
return buildReadChatCommandResult({
|
|
887
|
-
messages:
|
|
1488
|
+
messages: selectedMessages,
|
|
888
1489
|
status: returnedStatus,
|
|
889
1490
|
activeModal,
|
|
1491
|
+
messageSource,
|
|
1492
|
+
transcriptProvenance: messageSource,
|
|
890
1493
|
debugReadChat: {
|
|
891
1494
|
provider: adapter.cliType,
|
|
892
1495
|
targetSessionId: String(args?.targetSessionId || ''),
|
|
893
1496
|
adapterStatus: String(adapterStatus.status || ''),
|
|
894
1497
|
parsedStatus: String(parsedRecord.status || ''),
|
|
895
1498
|
returnedStatus: String(returnedStatus || ''),
|
|
896
|
-
|
|
1499
|
+
selectedMessageSource: (messageSource as any).selected,
|
|
1500
|
+
messageSource,
|
|
1501
|
+
shouldPreferAdapterMessages: supportsCliNativeTranscript(providerType, provider)
|
|
1502
|
+
&& isNativeSourceCanonicalHistory(provider?.canonicalHistory)
|
|
1503
|
+
&& (messageSource as any).selected !== 'native-history'
|
|
1504
|
+
&& typeof (messageSource as any).fallbackReason === 'string'
|
|
1505
|
+
&& (messageSource as any).fallbackReason.startsWith('native_history_')
|
|
1506
|
+
&& (messageSource as any).fallbackReason !== 'native_history_not_checked'
|
|
1507
|
+
&& !(selectedTranscriptAuthority === 'provider' && selectedCoverage === 'full'),
|
|
897
1508
|
parsedMsgCount: parsedRecord.messages.length,
|
|
898
|
-
returnedMsgCount:
|
|
1509
|
+
returnedMsgCount: selectedMessages.length,
|
|
899
1510
|
},
|
|
900
|
-
...(
|
|
901
|
-
...(
|
|
902
|
-
...(
|
|
903
|
-
...(
|
|
1511
|
+
...(selectedTitle ? { title: selectedTitle } : {}),
|
|
1512
|
+
...(selectedProviderSessionId ? { providerSessionId: selectedProviderSessionId } : {}),
|
|
1513
|
+
...(selectedTranscriptAuthority ? { transcriptAuthority: selectedTranscriptAuthority } : {}),
|
|
1514
|
+
...(selectedCoverage ? { coverage: selectedCoverage } : {}),
|
|
904
1515
|
}, args);
|
|
905
1516
|
}
|
|
906
1517
|
const historyLimit = normalizeReadChatTailLimit(args);
|
|
@@ -911,7 +1522,24 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
911
1522
|
: typeof (h.currentSession as any)?.workspace === 'string'
|
|
912
1523
|
? (h.currentSession as any).workspace
|
|
913
1524
|
: undefined;
|
|
914
|
-
const
|
|
1525
|
+
const exactNativeHistoryScope = Boolean(
|
|
1526
|
+
(typeof args?.targetSessionId === 'string' && args.targetSessionId.trim())
|
|
1527
|
+
|| (typeof args?.historySessionId === 'string' && args.historySessionId.trim())
|
|
1528
|
+
|| (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
|
|
1529
|
+
|| ((h.currentSession as any)?.sessionId === args?.targetSessionId && typeof (h.currentSession as any)?.providerSessionId === 'string' && (h.currentSession as any).providerSessionId.trim())
|
|
1530
|
+
);
|
|
1531
|
+
const history = supportsCliNativeTranscript(agentStr, provider) && isNativeSourceCanonicalHistory(provider?.canonicalHistory)
|
|
1532
|
+
? readCliProviderNativeHistory(agentStr, {
|
|
1533
|
+
canonicalHistory: provider?.canonicalHistory,
|
|
1534
|
+
historySessionId,
|
|
1535
|
+
workspace,
|
|
1536
|
+
offset: 0,
|
|
1537
|
+
limit: historyLimit,
|
|
1538
|
+
excludeRecentCount: 0,
|
|
1539
|
+
historyBehavior: provider?.historyBehavior,
|
|
1540
|
+
scripts: provider?.scripts as any,
|
|
1541
|
+
})
|
|
1542
|
+
: readProviderChatHistory(agentStr, {
|
|
915
1543
|
canonicalHistory: provider?.canonicalHistory,
|
|
916
1544
|
historySessionId,
|
|
917
1545
|
workspace,
|
|
@@ -921,12 +1549,81 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
921
1549
|
historyBehavior: provider?.historyBehavior,
|
|
922
1550
|
scripts: provider?.scripts as any,
|
|
923
1551
|
});
|
|
1552
|
+
const lookup = (history as any).lookup === 'workspace' ? 'workspace' : 'session';
|
|
1553
|
+
const historyMessages = Array.isArray((history as any)?.messages)
|
|
1554
|
+
? normalizeNativeHistoryMessages(agentStr, (history as any).messages as ChatMessage[], (history as any)?.providerSessionId)
|
|
1555
|
+
: [];
|
|
924
1556
|
const historyProviderSessionId = typeof (history as any)?.providerSessionId === 'string'
|
|
925
1557
|
? (history as any).providerSessionId
|
|
926
|
-
: historySessionId;
|
|
1558
|
+
: readHistorySessionIdFromMessages(historyMessages) || historySessionId;
|
|
1559
|
+
const nativeHistoryCoverage = typeof (history as any)?.nativeHistoryCoverage === 'string'
|
|
1560
|
+
? (history as any).nativeHistoryCoverage
|
|
1561
|
+
: undefined;
|
|
1562
|
+
const partialReason = typeof (history as any)?.partialReason === 'string'
|
|
1563
|
+
? (history as any).partialReason
|
|
1564
|
+
: undefined;
|
|
1565
|
+
const unavailableReason = typeof (history as any)?.unavailableReason === 'string'
|
|
1566
|
+
? (history as any).unavailableReason
|
|
1567
|
+
: undefined;
|
|
1568
|
+
const safeMapping = supportsCliNativeTranscript(agentStr, provider)
|
|
1569
|
+
? hasSafeNativeHistoryMapping({
|
|
1570
|
+
historySessionId: lookup === 'workspace' ? undefined : historySessionId,
|
|
1571
|
+
providerSessionId: lookup === 'workspace' ? undefined : historyProviderSessionId,
|
|
1572
|
+
workspace,
|
|
1573
|
+
nativeMessages: historyMessages,
|
|
1574
|
+
})
|
|
1575
|
+
: false;
|
|
1576
|
+
const nativeSelected = supportsCliNativeTranscript(agentStr, provider)
|
|
1577
|
+
&& (history as any).source === 'provider-native'
|
|
1578
|
+
&& historyMessages.length > 0
|
|
1579
|
+
&& nativeHistoryCoverage !== 'partial'
|
|
1580
|
+
&& nativeHistoryCoverage !== 'unavailable'
|
|
1581
|
+
&& safeMapping;
|
|
1582
|
+
const messageSource = buildCliMessageSourceProvenance({
|
|
1583
|
+
selected: nativeSelected ? 'native-history' : 'pty-parser',
|
|
1584
|
+
provider: agentStr,
|
|
1585
|
+
nativeHandle: historyProviderSessionId || historySessionId,
|
|
1586
|
+
fallbackReason: nativeSelected
|
|
1587
|
+
? undefined
|
|
1588
|
+
: buildNativeHistoryFallbackReason({
|
|
1589
|
+
providerType: agentStr,
|
|
1590
|
+
provider,
|
|
1591
|
+
nativeSource: (history as any).source,
|
|
1592
|
+
nativeHistoryCoverage,
|
|
1593
|
+
unavailableReason,
|
|
1594
|
+
nativeMessageCount: historyMessages.length,
|
|
1595
|
+
safeMapping,
|
|
1596
|
+
freshEnough: true,
|
|
1597
|
+
}),
|
|
1598
|
+
nativeSource: (history as any).source,
|
|
1599
|
+
sourcePath: (history as any).sourcePath,
|
|
1600
|
+
sourceMtimeMs: (history as any).sourceMtimeMs,
|
|
1601
|
+
nativeHistoryCoverage,
|
|
1602
|
+
partialReason,
|
|
1603
|
+
unavailableReason,
|
|
1604
|
+
nativeMessages: historyMessages,
|
|
1605
|
+
returnedMessages: historyMessages,
|
|
1606
|
+
safeMapping,
|
|
1607
|
+
freshEnough: true,
|
|
1608
|
+
ptyStatusApprovalOnly: false,
|
|
1609
|
+
});
|
|
1610
|
+
const requiresNativeSource = supportsCliNativeTranscript(agentStr, provider)
|
|
1611
|
+
&& isNativeSourceCanonicalHistory(provider?.canonicalHistory);
|
|
1612
|
+
if (requiresNativeSource && !nativeSelected) {
|
|
1613
|
+
return {
|
|
1614
|
+
success: false,
|
|
1615
|
+
code: 'native_history_not_safely_available',
|
|
1616
|
+
error: 'Provider-native history was not safely available for the requested CLI session.',
|
|
1617
|
+
providerSessionId: historyProviderSessionId,
|
|
1618
|
+
messageSource,
|
|
1619
|
+
transcriptProvenance: messageSource,
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
927
1622
|
return buildReadChatCommandResult({
|
|
928
|
-
messages:
|
|
1623
|
+
messages: historyMessages,
|
|
929
1624
|
status: 'idle',
|
|
1625
|
+
messageSource,
|
|
1626
|
+
transcriptProvenance: messageSource,
|
|
930
1627
|
...(typeof (history as any)?.title === 'string' ? { title: (history as any).title } : {}),
|
|
931
1628
|
...(historyProviderSessionId ? { providerSessionId: historyProviderSessionId } : {}),
|
|
932
1629
|
...(((provider?.historyBehavior as any)?.transcriptAuthority === 'provider' || (provider?.historyBehavior as any)?.transcriptAuthority === 'daemon')
|
|
@@ -1742,6 +2439,9 @@ export async function handleResolveAction(h: CommandHelpers, args: any): Promise
|
|
|
1742
2439
|
if (buttonIndex < 0 && (action === 'always' || /always/i.test(button))) {
|
|
1743
2440
|
buttonIndex = buttons.findIndex(b => /always/i.test(b));
|
|
1744
2441
|
}
|
|
2442
|
+
if (buttonIndex < 0 && (action === 'approve' || action === 'accept')) {
|
|
2443
|
+
buttonIndex = pickApprovalButton(buttons, provider).index;
|
|
2444
|
+
}
|
|
1745
2445
|
if (buttonIndex < 0) {
|
|
1746
2446
|
return { success: false, error: 'Approval action did not match any visible button' };
|
|
1747
2447
|
}
|