@adhdev/daemon-core 0.9.82-rc.13 → 0.9.82-rc.131
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-adapter-types.d.ts +4 -1
- package/dist/cli-adapters/provider-cli-adapter.d.ts +25 -1
- 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 +7538 -1380
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +7494 -1364
- 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 +54 -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 +6 -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-adapter-types.ts +2 -1
- package/src/cli-adapters/provider-cli-adapter.ts +473 -18
- 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 +1065 -40
- package/src/commands/cli-manager.ts +138 -2
- package/src/commands/handler.ts +8 -1
- package/src/commands/mesh-coordinator.ts +13 -143
- package/src/commands/router.ts +3238 -423
- 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 +295 -0
- package/src/mesh/mesh-events.ts +595 -48
- 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 +289 -36
- 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, readChatHistory, 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 {
|
|
@@ -32,6 +40,7 @@ interface ApprovalSelectableInstance extends ProviderInstance {
|
|
|
32
40
|
|
|
33
41
|
interface RuntimeChatMessageMerger extends ProviderInstance {
|
|
34
42
|
mergeRuntimeChatMessages?(messages: ChatMessage[]): ChatMessage[];
|
|
43
|
+
recordAcknowledgedUserInput?(input: InputEnvelope | string): void;
|
|
35
44
|
}
|
|
36
45
|
|
|
37
46
|
type LegacyStringScript = (params?: Record<string, unknown> | string) => string;
|
|
@@ -48,6 +57,16 @@ function getTargetedCliAdapter(h: CommandHelpers, args: any, providerType?: stri
|
|
|
48
57
|
return h.getCliAdapter(args?.targetSessionId || providerType || h.currentSession?.providerType || h.currentManagerKey);
|
|
49
58
|
}
|
|
50
59
|
|
|
60
|
+
function getExplicitHistorySessionId(args: any): string | undefined {
|
|
61
|
+
const explicit = typeof args?.historySessionId === 'string' ? args.historySessionId.trim() : '';
|
|
62
|
+
if (explicit) return explicit;
|
|
63
|
+
|
|
64
|
+
const explicitProviderSessionId = typeof args?.providerSessionId === 'string' ? args.providerSessionId.trim() : '';
|
|
65
|
+
if (explicitProviderSessionId) return explicitProviderSessionId;
|
|
66
|
+
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
51
70
|
function getTargetInstance(h: CommandHelpers, args: any): ApprovalSelectableInstance | null {
|
|
52
71
|
const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
|
|
53
72
|
const sessionId = targetSessionId || h.currentSession?.sessionId || '';
|
|
@@ -139,19 +158,47 @@ async function waitOnceForFreshHermesCliStart(adapter: CliAdapter, log: (msg: st
|
|
|
139
158
|
}
|
|
140
159
|
|
|
141
160
|
function getHistorySessionId(h: CommandHelpers, args: any): string | undefined {
|
|
142
|
-
const explicit =
|
|
161
|
+
const explicit = getExplicitHistorySessionId(args);
|
|
143
162
|
if (explicit) return explicit;
|
|
144
163
|
|
|
145
|
-
const explicitProviderSessionId = typeof args?.providerSessionId === 'string' ? args.providerSessionId.trim() : '';
|
|
146
|
-
if (explicitProviderSessionId) return explicitProviderSessionId;
|
|
147
|
-
|
|
148
164
|
const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
|
|
149
165
|
if (!targetSessionId) return undefined;
|
|
150
166
|
|
|
151
|
-
const
|
|
167
|
+
const session = h.ctx.sessionRegistry?.get(targetSessionId) as any;
|
|
168
|
+
const registeredProviderSessionId = typeof session?.providerSessionId === 'string' ? session.providerSessionId.trim() : '';
|
|
169
|
+
if (registeredProviderSessionId) return registeredProviderSessionId;
|
|
170
|
+
|
|
171
|
+
const instance = getTargetInstance(h, args);
|
|
152
172
|
const state = instance?.getState?.();
|
|
153
173
|
const providerSessionId = typeof state?.providerSessionId === 'string' ? state.providerSessionId.trim() : '';
|
|
154
|
-
|
|
174
|
+
if (providerSessionId) return providerSessionId;
|
|
175
|
+
|
|
176
|
+
const currentSession = h.currentSession as any;
|
|
177
|
+
if (currentSession?.sessionId === targetSessionId) {
|
|
178
|
+
const currentProviderSessionId = typeof currentSession.providerSessionId === 'string'
|
|
179
|
+
? currentSession.providerSessionId.trim()
|
|
180
|
+
: '';
|
|
181
|
+
if (currentProviderSessionId) return currentProviderSessionId;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return targetSessionId;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function resolveCliNativeHistorySessionId(args: any, currentHistorySessionId: string | undefined, parsedProviderSessionId: string | undefined): string | undefined {
|
|
188
|
+
const explicit = getExplicitHistorySessionId(args);
|
|
189
|
+
if (explicit) return explicit;
|
|
190
|
+
|
|
191
|
+
const parsed = typeof parsedProviderSessionId === 'string' ? parsedProviderSessionId.trim() : '';
|
|
192
|
+
const current = typeof currentHistorySessionId === 'string' ? currentHistorySessionId.trim() : '';
|
|
193
|
+
const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
|
|
194
|
+
|
|
195
|
+
// getHistorySessionId falls back to the runtime session id when no native
|
|
196
|
+
// handle has been registered yet. For live CLI adapters the parser may
|
|
197
|
+
// already know the provider-native handle; prefer it over the runtime id so
|
|
198
|
+
// exact native reads do not miss the worker transcript and fall back to PTY
|
|
199
|
+
// or same-workspace history.
|
|
200
|
+
if (parsed && (!current || current === targetSessionId)) return parsed;
|
|
201
|
+
return current || parsed || undefined;
|
|
155
202
|
}
|
|
156
203
|
|
|
157
204
|
function getInteractionId(args: any): string | undefined {
|
|
@@ -221,6 +268,470 @@ function normalizeReadChatMessages(payload: Record<string, any>): ChatMessage[]
|
|
|
221
268
|
return normalizeChatMessages(messages);
|
|
222
269
|
}
|
|
223
270
|
|
|
271
|
+
function getMessageNewestReceivedAt(messages: Array<{ receivedAt?: unknown; timestamp?: unknown }>): number {
|
|
272
|
+
let newest = 0;
|
|
273
|
+
for (const message of messages) {
|
|
274
|
+
const receivedAt = Number(message?.receivedAt ?? message?.timestamp ?? 0);
|
|
275
|
+
if (Number.isFinite(receivedAt) && receivedAt > newest) newest = receivedAt;
|
|
276
|
+
}
|
|
277
|
+
return newest;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function readHistorySessionIdFromMessages(messages: ChatMessage[]): string | undefined {
|
|
281
|
+
for (const message of messages as Array<ChatMessage & { historySessionId?: unknown }>) {
|
|
282
|
+
const historySessionId = typeof message?.historySessionId === 'string' ? message.historySessionId.trim() : '';
|
|
283
|
+
if (historySessionId) return historySessionId;
|
|
284
|
+
}
|
|
285
|
+
return undefined;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function shouldPreserveNativeIdentity(providerType: string, sessionId: string, message: ChatMessage): boolean {
|
|
289
|
+
const providerUnitKey = typeof message.providerUnitKey === 'string' ? message.providerUnitKey.trim() : '';
|
|
290
|
+
const turnKey = typeof message._turnKey === 'string' ? message._turnKey.trim() : '';
|
|
291
|
+
if (!providerUnitKey || !turnKey) return false;
|
|
292
|
+
if (providerType === 'hermes-cli' && sessionId) {
|
|
293
|
+
return providerUnitKey.startsWith(`${providerType}:native:${sessionId}:`)
|
|
294
|
+
&& turnKey.startsWith(`${providerType}:native-turn:${sessionId}:`);
|
|
295
|
+
}
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function normalizeNativeHistoryMessages(providerType: string, messages: ChatMessage[], nativeSessionId?: string): ChatMessage[] {
|
|
300
|
+
let turnIndex = 0;
|
|
301
|
+
return normalizeChatMessages(messages).map((message, index) => {
|
|
302
|
+
const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
|
|
303
|
+
const kind = typeof message.kind === 'string' && message.kind.trim() ? message.kind.trim() : (role === 'system' ? 'system' : 'standard');
|
|
304
|
+
if ((role === 'user' || role === 'human') && index > 0) turnIndex += 1;
|
|
305
|
+
const historySessionId = typeof (message as any).historySessionId === 'string'
|
|
306
|
+
? (message as any).historySessionId.trim()
|
|
307
|
+
: '';
|
|
308
|
+
const contentHash = hashSignatureParts([
|
|
309
|
+
providerType,
|
|
310
|
+
historySessionId,
|
|
311
|
+
String(message.receivedAt || message.timestamp || index),
|
|
312
|
+
role,
|
|
313
|
+
kind,
|
|
314
|
+
flattenContent(message.content),
|
|
315
|
+
]).slice(0, 12);
|
|
316
|
+
const nativeIdentitySessionId = historySessionId || (typeof nativeSessionId === 'string' ? nativeSessionId.trim() : '');
|
|
317
|
+
const preserveNativeIdentity = shouldPreserveNativeIdentity(providerType, nativeIdentitySessionId, message);
|
|
318
|
+
const existingProviderUnitKey = typeof message.providerUnitKey === 'string' ? message.providerUnitKey.trim() : '';
|
|
319
|
+
const existingTurnKey = typeof message._turnKey === 'string' ? message._turnKey.trim() : '';
|
|
320
|
+
const providerUnitKey = preserveNativeIdentity
|
|
321
|
+
? existingProviderUnitKey
|
|
322
|
+
: `${providerType}:native:${nativeIdentitySessionId || 'workspace'}:${index}:${role || 'message'}:${kind}:${contentHash}`;
|
|
323
|
+
const meta = message.meta && typeof message.meta === 'object' ? message.meta as Record<string, unknown> : undefined;
|
|
324
|
+
const isSystemSessionStart = role === 'system' || kind === 'system' || kind === 'session_start';
|
|
325
|
+
const isActivity = role === 'assistant' && (kind === 'tool' || kind === 'terminal' || kind === 'thought');
|
|
326
|
+
return {
|
|
327
|
+
...message,
|
|
328
|
+
role: role === 'human' ? 'user' : (role || 'assistant'),
|
|
329
|
+
kind: isSystemSessionStart ? 'system' : kind,
|
|
330
|
+
providerUnitKey,
|
|
331
|
+
bubbleId: typeof message.bubbleId === 'string' && message.bubbleId.trim()
|
|
332
|
+
&& preserveNativeIdentity
|
|
333
|
+
? message.bubbleId.trim()
|
|
334
|
+
: `bubble:${providerUnitKey}`,
|
|
335
|
+
_turnKey: preserveNativeIdentity
|
|
336
|
+
? existingTurnKey
|
|
337
|
+
: `${providerType}:native-turn:${nativeIdentitySessionId || 'workspace'}:${turnIndex}`,
|
|
338
|
+
bubbleState: message.bubbleState || 'final',
|
|
339
|
+
...(isSystemSessionStart ? {
|
|
340
|
+
visibility: message.visibility || 'hidden',
|
|
341
|
+
transcriptVisibility: message.transcriptVisibility || 'hidden',
|
|
342
|
+
audience: message.audience || 'internal',
|
|
343
|
+
source: message.source || 'runtime_status',
|
|
344
|
+
} : isActivity ? {
|
|
345
|
+
source: message.source || (kind === 'terminal' ? 'terminal_command' : 'tool_call'),
|
|
346
|
+
meta: { ...meta, label: message.senderName || meta?.label || (kind === 'terminal' ? 'Terminal' : 'Tool') },
|
|
347
|
+
} : {
|
|
348
|
+
source: message.source || (role === 'assistant' ? 'assistant_text' : undefined),
|
|
349
|
+
}),
|
|
350
|
+
} as ChatMessage;
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function buildCliMessageSourceProvenance(args: {
|
|
355
|
+
selected: 'native-history' | 'pty-parser';
|
|
356
|
+
provider: string;
|
|
357
|
+
nativeHandle?: string;
|
|
358
|
+
sessionWorkspace?: string;
|
|
359
|
+
intendedWorkspace?: string;
|
|
360
|
+
transcriptWorkspace?: string;
|
|
361
|
+
fallbackReason?: string;
|
|
362
|
+
nativeSource?: string;
|
|
363
|
+
sourcePath?: string;
|
|
364
|
+
sourceMtimeMs?: number;
|
|
365
|
+
nativeHistoryCoverage?: string;
|
|
366
|
+
partialReason?: string;
|
|
367
|
+
unavailableReason?: string;
|
|
368
|
+
nativeMessages?: ChatMessage[];
|
|
369
|
+
ptyMessages?: ChatMessage[];
|
|
370
|
+
returnedMessages?: ChatMessage[];
|
|
371
|
+
safeMapping?: boolean;
|
|
372
|
+
freshEnough?: boolean;
|
|
373
|
+
ptyStatusApprovalOnly?: boolean;
|
|
374
|
+
}): Record<string, unknown> {
|
|
375
|
+
const sourceMtimeMs = Number(args.sourceMtimeMs || 0);
|
|
376
|
+
const sourceMtimeAgeMs = sourceMtimeMs > 0 ? Math.max(0, Date.now() - sourceMtimeMs) : undefined;
|
|
377
|
+
const nativeMessages = args.nativeMessages || [];
|
|
378
|
+
const ptyMessages = args.ptyMessages || [];
|
|
379
|
+
const returnedMessages = args.returnedMessages || [];
|
|
380
|
+
const identityStatus = args.selected === 'native-history'
|
|
381
|
+
? 'safe'
|
|
382
|
+
: args.fallbackReason === 'native_history_not_safely_mapped'
|
|
383
|
+
? 'ambiguous_session_identity'
|
|
384
|
+
: args.fallbackReason?.startsWith('native_history_unavailable')
|
|
385
|
+
? 'transcript_unmapped'
|
|
386
|
+
: undefined;
|
|
387
|
+
return {
|
|
388
|
+
selected: args.selected,
|
|
389
|
+
provider: args.provider,
|
|
390
|
+
providerType: args.provider,
|
|
391
|
+
...(identityStatus ? { identityStatus } : {}),
|
|
392
|
+
...(args.nativeHandle ? { nativeHandle: args.nativeHandle } : {}),
|
|
393
|
+
...(args.nativeHandle ? { nativeSessionId: args.nativeHandle } : {}),
|
|
394
|
+
...(args.sessionWorkspace ? { sessionWorkspace: args.sessionWorkspace } : {}),
|
|
395
|
+
...(args.intendedWorkspace ? { intendedWorkspace: args.intendedWorkspace } : {}),
|
|
396
|
+
...(args.transcriptWorkspace ? { transcriptWorkspace: args.transcriptWorkspace } : {}),
|
|
397
|
+
...(args.fallbackReason ? { fallbackReason: args.fallbackReason } : {}),
|
|
398
|
+
...(args.nativeSource ? { nativeSource: args.nativeSource } : {}),
|
|
399
|
+
...(args.sourcePath ? { sourcePath: args.sourcePath } : {}),
|
|
400
|
+
...(args.nativeHistoryCoverage ? { nativeHistoryCoverage: args.nativeHistoryCoverage } : {}),
|
|
401
|
+
...(args.partialReason ? { partialReason: args.partialReason } : {}),
|
|
402
|
+
...(args.unavailableReason ? { unavailableReason: args.unavailableReason } : {}),
|
|
403
|
+
ptyStatusApprovalOnly: args.ptyStatusApprovalOnly === true,
|
|
404
|
+
staleness: {
|
|
405
|
+
sourceMtimeMs: sourceMtimeMs || undefined,
|
|
406
|
+
sourceMtimeAgeMs,
|
|
407
|
+
nativeNewestMessageAt: getMessageNewestReceivedAt(nativeMessages),
|
|
408
|
+
ptyNewestMessageAt: getMessageNewestReceivedAt(ptyMessages),
|
|
409
|
+
freshEnough: args.freshEnough === true,
|
|
410
|
+
},
|
|
411
|
+
coverage: {
|
|
412
|
+
nativeMessageCount: nativeMessages.length,
|
|
413
|
+
ptyMessageCount: ptyMessages.length,
|
|
414
|
+
returnedMessageCount: returnedMessages.length,
|
|
415
|
+
safeMapping: args.safeMapping === true,
|
|
416
|
+
// true when PTY message bodies are suppressed and must not be treated as
|
|
417
|
+
// chat content. PTY may still contribute status/approval/screen evidence.
|
|
418
|
+
ptyMessagesSuppressed: args.selected === 'native-history' || args.ptyStatusApprovalOnly === true,
|
|
419
|
+
},
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function buildNativeHistoryFallbackReason(args: {
|
|
424
|
+
providerType: string;
|
|
425
|
+
provider?: ProviderModule;
|
|
426
|
+
nativeSource?: string;
|
|
427
|
+
nativeHistoryCoverage?: string;
|
|
428
|
+
unavailableReason?: string;
|
|
429
|
+
nativeMessageCount: number;
|
|
430
|
+
safeMapping: boolean;
|
|
431
|
+
freshEnough: boolean;
|
|
432
|
+
}): string {
|
|
433
|
+
if (!supportsCliNativeTranscript(args.providerType, args.provider)) return 'provider_native_transcript_not_supported';
|
|
434
|
+
if (args.unavailableReason) return `native_history_unavailable:${args.unavailableReason}`;
|
|
435
|
+
if (args.nativeSource === 'native-unavailable') return 'native_history_unavailable';
|
|
436
|
+
if (args.nativeHistoryCoverage === 'partial') return 'native_history_partial';
|
|
437
|
+
if (args.nativeHistoryCoverage === 'unavailable') return 'native_history_unavailable';
|
|
438
|
+
if (args.nativeSource && args.nativeSource !== 'provider-native') return `native_history_source_${args.nativeSource}`;
|
|
439
|
+
if (args.nativeMessageCount <= 0) return 'native_history_empty';
|
|
440
|
+
if (!args.safeMapping) return 'native_history_not_safely_mapped';
|
|
441
|
+
if (!args.freshEnough) return 'native_history_stale';
|
|
442
|
+
return 'native_history_not_selected';
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function isUnsafeNativeTranscriptFallback(reason?: string): boolean {
|
|
446
|
+
const value = String(reason || '').trim();
|
|
447
|
+
return value.startsWith('native_history_unavailable')
|
|
448
|
+
|| value === 'native_history_not_safely_mapped'
|
|
449
|
+
|| value === 'native_history_stale'
|
|
450
|
+
|| value === 'native_history_partial';
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function coerceUnsafeNativeFallbackStatus(status: string, activeModal: unknown): string {
|
|
454
|
+
if (status === 'waiting_approval' && activeModal) return status;
|
|
455
|
+
return 'idle';
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function isRuntimeInputAckMessage(message: ChatMessage | undefined): boolean {
|
|
459
|
+
if (!message || typeof message !== 'object') return false;
|
|
460
|
+
const role = String((message as any).role || '').trim().toLowerCase();
|
|
461
|
+
if (role !== 'user' && role !== 'human') return false;
|
|
462
|
+
const meta = (message as any).meta;
|
|
463
|
+
return !!meta && typeof meta === 'object' && !Array.isArray(meta) && meta.runtimeInputAck === true;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function selectRuntimeInputAckMessages(messages: ChatMessage[]): ChatMessage[] {
|
|
467
|
+
return messages.filter((message) => isRuntimeInputAckMessage(message));
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function readExactRuntimeMirrorMessages(args: {
|
|
471
|
+
providerType: string;
|
|
472
|
+
targetSessionId?: string;
|
|
473
|
+
currentSessionId?: string;
|
|
474
|
+
tailLimit: number;
|
|
475
|
+
historyBehavior?: ProviderModule['historyBehavior'];
|
|
476
|
+
}): ChatMessage[] {
|
|
477
|
+
const targetSessionId = String(args.targetSessionId || '').trim();
|
|
478
|
+
const currentSessionId = String(args.currentSessionId || '').trim();
|
|
479
|
+
if (!targetSessionId || targetSessionId !== currentSessionId) return [];
|
|
480
|
+
|
|
481
|
+
const history = readChatHistory(
|
|
482
|
+
args.providerType,
|
|
483
|
+
0,
|
|
484
|
+
Math.max(args.tailLimit || 0, 200),
|
|
485
|
+
targetSessionId,
|
|
486
|
+
0,
|
|
487
|
+
args.historyBehavior,
|
|
488
|
+
);
|
|
489
|
+
return normalizeChatMessages((history.messages || []) as ChatMessage[])
|
|
490
|
+
.filter((message) => {
|
|
491
|
+
const historySessionId = String((message as any).historySessionId || '').trim();
|
|
492
|
+
const instanceId = String((message as any).instanceId || '').trim();
|
|
493
|
+
return historySessionId === targetSessionId || instanceId === targetSessionId;
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function normalizeComparableWorkspace(value: unknown): string {
|
|
498
|
+
const text = typeof value === 'string' ? value.trim() : '';
|
|
499
|
+
if (!text) return '';
|
|
500
|
+
return path.resolve(text);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function isCurrentRuntimePtySafelyAttributed(args: {
|
|
504
|
+
adapter: CliAdapter;
|
|
505
|
+
helpers: CommandHelpers;
|
|
506
|
+
readChatArgs: any;
|
|
507
|
+
sessionWorkspace?: string;
|
|
508
|
+
intendedWorkspace?: string;
|
|
509
|
+
ptyMessages: ChatMessage[];
|
|
510
|
+
}): boolean {
|
|
511
|
+
if (args.adapter.cliType !== 'codex-cli') return false;
|
|
512
|
+
if (!Array.isArray(args.ptyMessages) || args.ptyMessages.length === 0) return false;
|
|
513
|
+
const targetSessionId = typeof args.readChatArgs?.targetSessionId === 'string'
|
|
514
|
+
? args.readChatArgs.targetSessionId.trim()
|
|
515
|
+
: '';
|
|
516
|
+
const currentSession = args.helpers.currentSession as any;
|
|
517
|
+
const currentSessionId = typeof currentSession?.sessionId === 'string'
|
|
518
|
+
? currentSession.sessionId.trim()
|
|
519
|
+
: '';
|
|
520
|
+
if (!targetSessionId || !currentSessionId || targetSessionId !== currentSessionId) return false;
|
|
521
|
+
|
|
522
|
+
const runtimeMeta = typeof (args.adapter as any).getRuntimeMetadata === 'function'
|
|
523
|
+
? (args.adapter as any).getRuntimeMetadata()
|
|
524
|
+
: null;
|
|
525
|
+
const runtimeId = typeof runtimeMeta?.runtimeId === 'string' ? runtimeMeta.runtimeId.trim() : '';
|
|
526
|
+
if (!runtimeId || runtimeId !== targetSessionId) return false;
|
|
527
|
+
const surfaceKind = typeof runtimeMeta?.surfaceKind === 'string' ? runtimeMeta.surfaceKind : '';
|
|
528
|
+
if (surfaceKind === 'inactive_record' || surfaceKind === 'recovery_snapshot') return false;
|
|
529
|
+
|
|
530
|
+
const sessionWorkspace = normalizeComparableWorkspace(args.sessionWorkspace);
|
|
531
|
+
const adapterWorkspace = normalizeComparableWorkspace(args.adapter.workingDir);
|
|
532
|
+
if (!sessionWorkspace || !adapterWorkspace || sessionWorkspace !== adapterWorkspace) return false;
|
|
533
|
+
const intendedWorkspace = normalizeComparableWorkspace(args.intendedWorkspace);
|
|
534
|
+
if (intendedWorkspace && intendedWorkspace !== sessionWorkspace) return false;
|
|
535
|
+
|
|
536
|
+
const registryEntry = args.helpers.ctx?.sessionRegistry?.get?.(targetSessionId) as any;
|
|
537
|
+
const registryInstanceKey = typeof registryEntry?.adapterKey === 'string' && registryEntry.adapterKey.trim()
|
|
538
|
+
? registryEntry.adapterKey.trim()
|
|
539
|
+
: typeof registryEntry?.instanceKey === 'string' && registryEntry.instanceKey.trim()
|
|
540
|
+
? registryEntry.instanceKey.trim()
|
|
541
|
+
: '';
|
|
542
|
+
if (registryInstanceKey) {
|
|
543
|
+
const targetInstance = args.helpers.ctx?.instanceManager?.getInstance?.(registryInstanceKey);
|
|
544
|
+
if (targetInstance) {
|
|
545
|
+
const instanceType = typeof (targetInstance as any).type === 'string' ? (targetInstance as any).type : '';
|
|
546
|
+
if (instanceType && instanceType !== args.adapter.cliType) return false;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return true;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function supportsCliNativeTranscript(providerType: string, provider?: ProviderModule): boolean {
|
|
554
|
+
if (CLI_NATIVE_TRANSCRIPT_PROVIDERS.has(providerType)) return true;
|
|
555
|
+
return provider?.category === 'cli' && isNativeSourceCanonicalHistory(provider?.canonicalHistory);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function getComparableVisibleText(message: ChatMessage | undefined): string {
|
|
559
|
+
if (!message) return '';
|
|
560
|
+
const role = String((message as any).role || '').trim().toLowerCase();
|
|
561
|
+
if (role !== 'user' && role !== 'assistant') return '';
|
|
562
|
+
const kind = String((message as any).kind || 'standard').trim().toLowerCase();
|
|
563
|
+
if (kind && kind !== 'standard') return '';
|
|
564
|
+
const content = flattenContent((message as any).content).replace(/\s+/g, ' ').trim();
|
|
565
|
+
return content;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function hasOverlappingVisibleConversationText(nativeMessages: ChatMessage[], ptyMessages: ChatMessage[]): boolean {
|
|
569
|
+
const nativeTexts = nativeMessages.map(getComparableVisibleText).filter(Boolean);
|
|
570
|
+
const ptyTexts = ptyMessages.map(getComparableVisibleText).filter(Boolean);
|
|
571
|
+
if (nativeTexts.length === 0 || ptyTexts.length === 0) return false;
|
|
572
|
+
for (const nativeText of nativeTexts) {
|
|
573
|
+
for (const ptyText of ptyTexts) {
|
|
574
|
+
if (nativeText === ptyText) return true;
|
|
575
|
+
const shorter = nativeText.length <= ptyText.length ? nativeText : ptyText;
|
|
576
|
+
const longer = nativeText.length <= ptyText.length ? ptyText : nativeText;
|
|
577
|
+
if (shorter.length >= 32 && longer.includes(shorter)) return true;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function hasSafeNativeHistoryMapping(args: {
|
|
584
|
+
historySessionId?: string;
|
|
585
|
+
providerSessionId?: string;
|
|
586
|
+
workspace?: string;
|
|
587
|
+
nativeMessages: ChatMessage[];
|
|
588
|
+
ptyMessages?: ChatMessage[];
|
|
589
|
+
requireWorkspaceContentOverlap?: boolean;
|
|
590
|
+
}): boolean {
|
|
591
|
+
const isCoordinatorTranscript = args.nativeMessages.some((m: any) => {
|
|
592
|
+
const text = typeof m?.content === 'string' ? m.content : JSON.stringify(m?.content || '');
|
|
593
|
+
return text.includes('mesh_send_task') || text.includes('mesh_status') || text.includes('mesh_read_chat') || text.includes('mesh_launch_session');
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
const explicitSessionId = String(args.historySessionId || args.providerSessionId || '').trim();
|
|
597
|
+
if (explicitSessionId) {
|
|
598
|
+
const messageSessionIds = args.nativeMessages
|
|
599
|
+
.map((message: any) => typeof message?.historySessionId === 'string' ? message.historySessionId.trim() : '')
|
|
600
|
+
.filter(Boolean);
|
|
601
|
+
if (messageSessionIds.length > 0) {
|
|
602
|
+
return messageSessionIds.some((id) => id === explicitSessionId);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Messages carry no historySessionId — cannot confirm they belong to the requested session.
|
|
606
|
+
// Only allow a coordinator transcript that is also confirmed by the PTY side; otherwise
|
|
607
|
+
// fail closed so a same-workspace session's history is never silently accepted.
|
|
608
|
+
if (isCoordinatorTranscript && args.ptyMessages && args.ptyMessages.length > 0) {
|
|
609
|
+
const ptyHasCoordinator = args.ptyMessages.some((m: any) => {
|
|
610
|
+
const text = typeof m?.content === 'string' ? m.content : JSON.stringify(m?.content || '');
|
|
611
|
+
return text.includes('mesh_send_task') || text.includes('mesh_status') || text.includes('mesh_read_chat');
|
|
612
|
+
});
|
|
613
|
+
return ptyHasCoordinator;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// No historySessionId in messages and no coordinator cross-check: fail closed.
|
|
617
|
+
// Workspace-only matching must not override an explicit session identity.
|
|
618
|
+
return false;
|
|
619
|
+
}
|
|
620
|
+
const workspace = String(args.workspace || '').trim();
|
|
621
|
+
if (!workspace) return false;
|
|
622
|
+
const workspaceMatches = args.nativeMessages.some((message: any) => String(message?.workspace || '').trim() === workspace);
|
|
623
|
+
if (!workspaceMatches) return false;
|
|
624
|
+
|
|
625
|
+
if (isCoordinatorTranscript && args.ptyMessages && args.ptyMessages.length > 0) {
|
|
626
|
+
const ptyHasCoordinator = args.ptyMessages.some((m: any) => {
|
|
627
|
+
const text = typeof m?.content === 'string' ? m.content : JSON.stringify(m?.content || '');
|
|
628
|
+
return text.includes('mesh_send_task') || text.includes('mesh_status') || text.includes('mesh_read_chat');
|
|
629
|
+
});
|
|
630
|
+
if (!ptyHasCoordinator) {
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
if (!args.requireWorkspaceContentOverlap) return true;
|
|
636
|
+
return hasOverlappingVisibleConversationText(args.nativeMessages, args.ptyMessages || []);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Provenance boundary: workspace-only native history lookup is never safe
|
|
640
|
+
// because multiple concurrent sessions sharing the same cwd would alias each
|
|
641
|
+
// other. historySessionId (the provider-native session key) is required to
|
|
642
|
+
// establish ownership. hasSafeNativeHistoryMapping() enforces the same
|
|
643
|
+
// invariant after the read; both guards must hold for native history to be used.
|
|
644
|
+
function readCliProviderNativeHistory(agentStr: string, args: {
|
|
645
|
+
canonicalHistory?: ProviderModule['canonicalHistory'];
|
|
646
|
+
historySessionId?: string;
|
|
647
|
+
workspace?: string;
|
|
648
|
+
offset: number;
|
|
649
|
+
limit: number;
|
|
650
|
+
excludeRecentCount: number;
|
|
651
|
+
historyBehavior?: ProviderModule['historyBehavior'];
|
|
652
|
+
scripts?: ProviderScripts;
|
|
653
|
+
}): ReturnType<typeof readProviderChatHistory> & { lookup: 'session' | 'workspace' } {
|
|
654
|
+
if (!args.historySessionId) {
|
|
655
|
+
return {
|
|
656
|
+
messages: [],
|
|
657
|
+
hasMore: false,
|
|
658
|
+
source: 'native-unavailable',
|
|
659
|
+
unavailableReason: 'native_history_workspace_only_lookup_unsafe',
|
|
660
|
+
lookup: 'session',
|
|
661
|
+
} as ReturnType<typeof readProviderChatHistory> & { lookup: 'session' | 'workspace' };
|
|
662
|
+
}
|
|
663
|
+
const sessionHistory = readProviderChatHistory(agentStr, {
|
|
664
|
+
canonicalHistory: args.canonicalHistory,
|
|
665
|
+
historySessionId: args.historySessionId,
|
|
666
|
+
workspace: args.workspace,
|
|
667
|
+
offset: args.offset,
|
|
668
|
+
limit: args.limit,
|
|
669
|
+
excludeRecentCount: args.excludeRecentCount,
|
|
670
|
+
historyBehavior: args.historyBehavior,
|
|
671
|
+
scripts: args.scripts as any,
|
|
672
|
+
});
|
|
673
|
+
// Native transcripts are keyed by provider/runtime session identity. Falling
|
|
674
|
+
// back to workspace makes concurrent local Codex/Hermes sessions alias each
|
|
675
|
+
// other when they share the same cwd.
|
|
676
|
+
return { ...(sessionHistory as any), lookup: 'session' };
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function readLiveCodexWorkspaceNativeHistory(agentStr: string, args: {
|
|
680
|
+
canonicalHistory?: ProviderModule['canonicalHistory'];
|
|
681
|
+
workspace?: string;
|
|
682
|
+
offset: number;
|
|
683
|
+
limit: number;
|
|
684
|
+
excludeRecentCount: number;
|
|
685
|
+
historyBehavior?: ProviderModule['historyBehavior'];
|
|
686
|
+
scripts?: ProviderScripts;
|
|
687
|
+
}): (ReturnType<typeof readProviderChatHistory> & { lookup: 'workspace' }) | null {
|
|
688
|
+
if (agentStr !== 'codex-cli') return null;
|
|
689
|
+
const workspace = typeof args.workspace === 'string' ? args.workspace.trim() : '';
|
|
690
|
+
if (!workspace) return null;
|
|
691
|
+
const history = readProviderChatHistory(agentStr, {
|
|
692
|
+
canonicalHistory: args.canonicalHistory,
|
|
693
|
+
workspace,
|
|
694
|
+
offset: args.offset,
|
|
695
|
+
limit: args.limit,
|
|
696
|
+
excludeRecentCount: args.excludeRecentCount,
|
|
697
|
+
historyBehavior: args.historyBehavior,
|
|
698
|
+
scripts: args.scripts as any,
|
|
699
|
+
});
|
|
700
|
+
return { ...(history as any), lookup: 'workspace' };
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function isNativeHistoryFreshEnough(args: {
|
|
704
|
+
sourceMtimeMs?: number;
|
|
705
|
+
nativeMessages: ChatMessage[];
|
|
706
|
+
ptyMessages: ChatMessage[];
|
|
707
|
+
}): boolean {
|
|
708
|
+
const nativeNewest = getMessageNewestReceivedAt(args.nativeMessages);
|
|
709
|
+
const ptyNewest = getMessageNewestReceivedAt(args.ptyMessages);
|
|
710
|
+
if (nativeNewest > 0 && nativeNewest >= ptyNewest) return true;
|
|
711
|
+
const sourceMtimeMs = Number(args.sourceMtimeMs || 0);
|
|
712
|
+
if (sourceMtimeMs > 0 && Date.now() - sourceMtimeMs <= CLI_NATIVE_HISTORY_FRESH_MS) return true;
|
|
713
|
+
return ptyNewest === 0 && nativeNewest > 0;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function shouldPreserveReadChatPayloadField(key: string): boolean {
|
|
717
|
+
return key === 'messageSource' || key === 'transcriptProvenance';
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function updateMessageSourceReturnedCount(value: unknown, returnedMessageCount: number): unknown {
|
|
721
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return value;
|
|
722
|
+
const record = value as Record<string, unknown>;
|
|
723
|
+
const coverage = record.coverage && typeof record.coverage === 'object' && !Array.isArray(record.coverage)
|
|
724
|
+
? record.coverage as Record<string, unknown>
|
|
725
|
+
: undefined;
|
|
726
|
+
if (!coverage) return value;
|
|
727
|
+
return {
|
|
728
|
+
...record,
|
|
729
|
+
coverage: {
|
|
730
|
+
...coverage,
|
|
731
|
+
returnedMessageCount,
|
|
732
|
+
},
|
|
733
|
+
};
|
|
734
|
+
}
|
|
224
735
|
|
|
225
736
|
function deriveHistoryDedupKey(message: ChatMessage & { _unitKey?: string; _turnKey?: string }): string | undefined {
|
|
226
737
|
const unitKey = typeof message._unitKey === 'string' ? message._unitKey.trim() : '';
|
|
@@ -281,7 +792,7 @@ function normalizeReadChatCommandStatus(status: unknown, activeModal: unknown):
|
|
|
281
792
|
}
|
|
282
793
|
switch (raw) {
|
|
283
794
|
case 'starting':
|
|
284
|
-
return hasNonEmptyModalButtons(activeModal) ? 'waiting_approval' : '
|
|
795
|
+
return hasNonEmptyModalButtons(activeModal) ? 'waiting_approval' : 'starting';
|
|
285
796
|
case 'stopped':
|
|
286
797
|
case 'disconnected':
|
|
287
798
|
case 'not_monitored':
|
|
@@ -295,6 +806,16 @@ function isGeneratingLikeStatus(status: unknown): boolean {
|
|
|
295
806
|
return status === 'generating' || status === 'streaming' || status === 'long_generating' || status === 'starting';
|
|
296
807
|
}
|
|
297
808
|
|
|
809
|
+
function hasVisibleAssistantMessage(messages: unknown[] | undefined): boolean {
|
|
810
|
+
if (!Array.isArray(messages)) return false;
|
|
811
|
+
return messages.some((message: any) => {
|
|
812
|
+
if (!message || message.role !== 'assistant') return false;
|
|
813
|
+
const kind = typeof message.kind === 'string' ? message.kind : 'standard';
|
|
814
|
+
if (kind !== 'standard') return false;
|
|
815
|
+
return String(message.content || '').trim().length > 0;
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
|
|
298
819
|
function shouldTrustCliAdapterTerminalStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any): boolean {
|
|
299
820
|
if (!isGeneratingLikeStatus(parsedStatus)) return false;
|
|
300
821
|
if (hasNonEmptyModalButtons(activeModal)) return false;
|
|
@@ -304,7 +825,26 @@ function shouldTrustCliAdapterTerminalStatus(parsedStatus: unknown, activeModal:
|
|
|
304
825
|
return true;
|
|
305
826
|
}
|
|
306
827
|
|
|
307
|
-
function normalizeCliReadChatStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any): string {
|
|
828
|
+
function normalizeCliReadChatStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any, parsedMessages?: unknown[]): string {
|
|
829
|
+
const adapterRawStatus = typeof adapterStatus?.status === 'string' ? adapterStatus.status.trim() : '';
|
|
830
|
+
if (adapterRawStatus === 'starting'
|
|
831
|
+
&& isGeneratingLikeStatus(parsedStatus)
|
|
832
|
+
&& !hasNonEmptyModalButtons(activeModal)
|
|
833
|
+
&& Array.isArray(parsedMessages)
|
|
834
|
+
&& parsedMessages.length === 0
|
|
835
|
+
&& Array.isArray(adapterStatus?.messages)
|
|
836
|
+
&& adapterStatus.messages.length === 0
|
|
837
|
+
&& !(typeof adapter.isProcessing === 'function' && adapter.isProcessing())) {
|
|
838
|
+
return 'starting';
|
|
839
|
+
}
|
|
840
|
+
if (
|
|
841
|
+
isGeneratingLikeStatus(adapterRawStatus)
|
|
842
|
+
&& parsedStatus === 'idle'
|
|
843
|
+
&& !hasNonEmptyModalButtons(activeModal)
|
|
844
|
+
&& !hasVisibleAssistantMessage(parsedMessages)
|
|
845
|
+
) {
|
|
846
|
+
return adapterRawStatus;
|
|
847
|
+
}
|
|
308
848
|
if (shouldTrustCliAdapterTerminalStatus(parsedStatus, activeModal, adapter, adapterStatus)) return 'idle';
|
|
309
849
|
return typeof parsedStatus === 'string' && parsedStatus.trim() ? parsedStatus : 'idle';
|
|
310
850
|
}
|
|
@@ -342,6 +882,13 @@ function buildReadChatCommandResult(payload: Record<string, any>, args: any): Co
|
|
|
342
882
|
const visibleMessages = filterUserFacingChatMessages(messages);
|
|
343
883
|
const sync = buildFullTail(visibleMessages, normalizeReadChatTailLimit(args));
|
|
344
884
|
const hiddenMsgCount = Math.max(0, messages.length - visibleMessages.length);
|
|
885
|
+
const preservedPayloadFields = Object.fromEntries(Object.entries(payload).filter(([key]) => shouldPreserveReadChatPayloadField(key)));
|
|
886
|
+
if (preservedPayloadFields.messageSource) {
|
|
887
|
+
preservedPayloadFields.messageSource = updateMessageSourceReturnedCount(preservedPayloadFields.messageSource, sync.messages.length);
|
|
888
|
+
}
|
|
889
|
+
if (preservedPayloadFields.transcriptProvenance) {
|
|
890
|
+
preservedPayloadFields.transcriptProvenance = updateMessageSourceReturnedCount(preservedPayloadFields.transcriptProvenance, sync.messages.length);
|
|
891
|
+
}
|
|
345
892
|
const returnedDebugReadChat = debugReadChat
|
|
346
893
|
? {
|
|
347
894
|
...debugReadChat,
|
|
@@ -356,6 +903,7 @@ function buildReadChatCommandResult(payload: Record<string, any>, args: any): Co
|
|
|
356
903
|
return {
|
|
357
904
|
success: true,
|
|
358
905
|
...validatedPayload,
|
|
906
|
+
...preservedPayloadFields,
|
|
359
907
|
messages: sync.messages,
|
|
360
908
|
totalMessages: sync.totalMessages,
|
|
361
909
|
...(returnedDebugReadChat ? { debugReadChat: returnedDebugReadChat } : {}),
|
|
@@ -584,6 +1132,8 @@ function buildChatDebugBundleSummary(bundle: Record<string, unknown>): Record<st
|
|
|
584
1132
|
adapterStatus: debugReadChat.adapterStatus,
|
|
585
1133
|
parsedStatus: debugReadChat.parsedStatus,
|
|
586
1134
|
returnedStatus: debugReadChat.returnedStatus,
|
|
1135
|
+
selectedMessageSource: debugReadChat.selectedMessageSource,
|
|
1136
|
+
messageSource: debugReadChat.messageSource,
|
|
587
1137
|
parsedMsgCount: debugReadChat.parsedMsgCount,
|
|
588
1138
|
returnedMsgCount: debugReadChat.returnedMsgCount,
|
|
589
1139
|
shouldPreferAdapterMessages: debugReadChat.shouldPreferAdapterMessages,
|
|
@@ -646,11 +1196,20 @@ export async function handleGetChatDebugBundle(h: CommandHelpers, args: any): Pr
|
|
|
646
1196
|
providerSessionId: readResult.providerSessionId,
|
|
647
1197
|
transcriptAuthority: readResult.transcriptAuthority,
|
|
648
1198
|
coverage: readResult.coverage,
|
|
1199
|
+
messageSource: readResult.messageSource,
|
|
1200
|
+
transcriptProvenance: readResult.transcriptProvenance,
|
|
649
1201
|
activeModal: readResult.activeModal,
|
|
650
1202
|
messagesTail: Array.isArray(readResult.messages) ? readResult.messages.slice(-20) : [],
|
|
651
1203
|
debugReadChat: readResult.debugReadChat,
|
|
652
1204
|
}
|
|
653
|
-
: {
|
|
1205
|
+
: {
|
|
1206
|
+
success: false,
|
|
1207
|
+
error: readResult.error,
|
|
1208
|
+
code: readResult.code,
|
|
1209
|
+
messageSource: readResult.messageSource,
|
|
1210
|
+
transcriptProvenance: readResult.transcriptProvenance,
|
|
1211
|
+
debugReadChat: readResult.debugReadChat,
|
|
1212
|
+
};
|
|
654
1213
|
} catch (error: any) {
|
|
655
1214
|
readChat = { success: false, error: error?.message || String(error) };
|
|
656
1215
|
}
|
|
@@ -817,16 +1376,56 @@ export async function handleChatHistory(h: CommandHelpers, args: any): Promise<C
|
|
|
817
1376
|
: typeof (h.currentSession as any)?.workspace === 'string'
|
|
818
1377
|
? (h.currentSession as any).workspace
|
|
819
1378
|
: undefined;
|
|
820
|
-
const
|
|
821
|
-
|
|
822
|
-
historySessionId
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
1379
|
+
const exactNativeHistoryScope = Boolean(
|
|
1380
|
+
(typeof args?.targetSessionId === 'string' && args.targetSessionId.trim())
|
|
1381
|
+
|| (typeof args?.historySessionId === 'string' && args.historySessionId.trim())
|
|
1382
|
+
|| (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
|
|
1383
|
+
);
|
|
1384
|
+
const result = supportsCliNativeTranscript(agentStr, provider) && isNativeSourceCanonicalHistory(provider?.canonicalHistory)
|
|
1385
|
+
? readCliProviderNativeHistory(agentStr, {
|
|
1386
|
+
canonicalHistory: provider?.canonicalHistory,
|
|
1387
|
+
historySessionId,
|
|
1388
|
+
workspace,
|
|
1389
|
+
offset: offset || 0,
|
|
1390
|
+
limit: limit || 30,
|
|
1391
|
+
excludeRecentCount,
|
|
1392
|
+
historyBehavior: provider?.historyBehavior,
|
|
1393
|
+
scripts: provider?.scripts as any,
|
|
1394
|
+
})
|
|
1395
|
+
: readProviderChatHistory(agentStr, {
|
|
1396
|
+
canonicalHistory: provider?.canonicalHistory,
|
|
1397
|
+
historySessionId,
|
|
1398
|
+
workspace,
|
|
1399
|
+
offset: offset || 0,
|
|
1400
|
+
limit: limit || 30,
|
|
1401
|
+
excludeRecentCount,
|
|
1402
|
+
historyBehavior: provider?.historyBehavior,
|
|
1403
|
+
scripts: provider?.scripts as any,
|
|
1404
|
+
});
|
|
1405
|
+
if (supportsCliNativeTranscript(agentStr, provider) && isNativeSourceCanonicalHistory(provider?.canonicalHistory)) {
|
|
1406
|
+
const lookup = (result as any).lookup === 'workspace' ? 'workspace' : 'session';
|
|
1407
|
+
const messages = Array.isArray((result as any).messages)
|
|
1408
|
+
? normalizeNativeHistoryMessages(agentStr, (result as any).messages as ChatMessage[], (result as any)?.providerSessionId)
|
|
1409
|
+
: [];
|
|
1410
|
+
const historyProviderSessionId = typeof (result as any)?.providerSessionId === 'string'
|
|
1411
|
+
? (result as any).providerSessionId
|
|
1412
|
+
: readHistorySessionIdFromMessages(messages) || historySessionId;
|
|
1413
|
+
const safeMapping = hasSafeNativeHistoryMapping({
|
|
1414
|
+
historySessionId: lookup === 'workspace' ? undefined : historySessionId,
|
|
1415
|
+
providerSessionId: lookup === 'workspace' ? undefined : historyProviderSessionId,
|
|
1416
|
+
workspace,
|
|
1417
|
+
nativeMessages: messages,
|
|
1418
|
+
});
|
|
1419
|
+
if ((result as any).source === 'provider-native' && messages.length > 0 && !safeMapping) {
|
|
1420
|
+
return {
|
|
1421
|
+
success: true,
|
|
1422
|
+
messages: [],
|
|
1423
|
+
hasMore: false,
|
|
1424
|
+
source: 'native-unavailable',
|
|
1425
|
+
agent: agentStr,
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
830
1429
|
return { success: true, ...result, agent: agentStr };
|
|
831
1430
|
} catch (e: any) {
|
|
832
1431
|
return { success: false, error: e.message };
|
|
@@ -874,7 +1473,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
874
1473
|
? parsedRecord.coverage
|
|
875
1474
|
: undefined;
|
|
876
1475
|
const activeModal = parsedRecord.activeModal ?? parsedRecord.modal ?? null;
|
|
877
|
-
const returnedStatus = normalizeCliReadChatStatus(parsedRecord.status, activeModal, adapter, adapterStatus);
|
|
1476
|
+
const returnedStatus = normalizeCliReadChatStatus(parsedRecord.status, activeModal, adapter, adapterStatus, parsedRecord.messages);
|
|
878
1477
|
const runtimeMessageMerger = getTargetInstance(h, args) as RuntimeChatMessageMerger | null;
|
|
879
1478
|
const parsedMessages = finalizeStreamingMessagesWhenIdle(parsedRecord.messages as ChatMessage[], returnedStatus);
|
|
880
1479
|
const returnedMessages = runtimeMessageMerger?.category === 'cli'
|
|
@@ -882,36 +1481,368 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
882
1481
|
&& typeof runtimeMessageMerger.mergeRuntimeChatMessages === 'function'
|
|
883
1482
|
? runtimeMessageMerger.mergeRuntimeChatMessages(parsedMessages)
|
|
884
1483
|
: parsedMessages;
|
|
1484
|
+
const providerType = provider?.type || adapter.cliType;
|
|
1485
|
+
let selectedMessages = returnedMessages;
|
|
1486
|
+
let selectedTitle = title;
|
|
1487
|
+
let selectedProviderSessionId = providerSessionId;
|
|
1488
|
+
let selectedTranscriptAuthority = transcriptAuthority;
|
|
1489
|
+
let selectedCoverage = coverage;
|
|
1490
|
+
let selectedStatus = returnedStatus;
|
|
1491
|
+
const sessionWorkspace = typeof (h.currentSession as any)?.workspace === 'string'
|
|
1492
|
+
? (h.currentSession as any).workspace
|
|
1493
|
+
: typeof adapter.workingDir === 'string'
|
|
1494
|
+
? adapter.workingDir
|
|
1495
|
+
: undefined;
|
|
1496
|
+
const intendedWorkspace = typeof args?.workspace === 'string' ? args.workspace : undefined;
|
|
1497
|
+
let messageSource = buildCliMessageSourceProvenance({
|
|
1498
|
+
selected: 'pty-parser',
|
|
1499
|
+
provider: adapter.cliType,
|
|
1500
|
+
fallbackReason: supportsCliNativeTranscript(providerType, provider) ? 'native_history_not_checked' : 'provider_native_transcript_not_supported',
|
|
1501
|
+
sessionWorkspace,
|
|
1502
|
+
intendedWorkspace,
|
|
1503
|
+
ptyMessages: returnedMessages,
|
|
1504
|
+
returnedMessages,
|
|
1505
|
+
ptyStatusApprovalOnly: false,
|
|
1506
|
+
});
|
|
1507
|
+
|
|
1508
|
+
if (supportsCliNativeTranscript(providerType, provider) && isNativeSourceCanonicalHistory(provider?.canonicalHistory)) {
|
|
1509
|
+
const agentStr = provider?.type || args?.agentType || getCurrentProviderType(h, adapter.cliType);
|
|
1510
|
+
const workspace = sessionWorkspace;
|
|
1511
|
+
const nativeHistoryLimit = Math.max(
|
|
1512
|
+
normalizeReadChatTailLimit(args) || 0,
|
|
1513
|
+
returnedMessages.length,
|
|
1514
|
+
200,
|
|
1515
|
+
);
|
|
1516
|
+
const nativeHistorySessionId = resolveCliNativeHistorySessionId(args, historySessionId, providerSessionId);
|
|
1517
|
+
const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
|
|
1518
|
+
const exactNativeHistoryScope = Boolean(
|
|
1519
|
+
(typeof args?.historySessionId === 'string' && args.historySessionId.trim())
|
|
1520
|
+
|| (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
|
|
1521
|
+
|| providerSessionId
|
|
1522
|
+
|| (nativeHistorySessionId && nativeHistorySessionId !== targetSessionId)
|
|
1523
|
+
|| ((h.currentSession as any)?.sessionId === args?.targetSessionId && typeof (h.currentSession as any)?.providerSessionId === 'string' && (h.currentSession as any).providerSessionId.trim())
|
|
1524
|
+
);
|
|
1525
|
+
let nativeHistory: (ReturnType<typeof readProviderChatHistory> & { lookup?: 'session' | 'workspace' }) | null = null;
|
|
1526
|
+
try {
|
|
1527
|
+
nativeHistory = readCliProviderNativeHistory(agentStr, {
|
|
1528
|
+
canonicalHistory: provider?.canonicalHistory,
|
|
1529
|
+
historySessionId: nativeHistorySessionId,
|
|
1530
|
+
workspace,
|
|
1531
|
+
offset: 0,
|
|
1532
|
+
limit: nativeHistoryLimit,
|
|
1533
|
+
excludeRecentCount: 0,
|
|
1534
|
+
historyBehavior: provider?.historyBehavior,
|
|
1535
|
+
scripts: provider?.scripts as any,
|
|
1536
|
+
});
|
|
1537
|
+
} catch (error: any) {
|
|
1538
|
+
const fallbackReason = `native_history_error:${error?.message || String(error)}`;
|
|
1539
|
+
messageSource = buildCliMessageSourceProvenance({
|
|
1540
|
+
selected: 'pty-parser',
|
|
1541
|
+
provider: adapter.cliType,
|
|
1542
|
+
fallbackReason,
|
|
1543
|
+
sessionWorkspace,
|
|
1544
|
+
intendedWorkspace,
|
|
1545
|
+
ptyMessages: returnedMessages,
|
|
1546
|
+
returnedMessages,
|
|
1547
|
+
ptyStatusApprovalOnly: false,
|
|
1548
|
+
});
|
|
1549
|
+
nativeHistory = null;
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
if (nativeHistory) {
|
|
1553
|
+
const nativeMessages = Array.isArray((nativeHistory as any).messages)
|
|
1554
|
+
? normalizeNativeHistoryMessages(agentStr, (nativeHistory as any).messages as ChatMessage[], (nativeHistory as any)?.providerSessionId)
|
|
1555
|
+
: [];
|
|
1556
|
+
const historyProviderSessionId = typeof (nativeHistory as any)?.providerSessionId === 'string'
|
|
1557
|
+
? (nativeHistory as any).providerSessionId
|
|
1558
|
+
: readHistorySessionIdFromMessages(nativeMessages) || nativeHistorySessionId || historySessionId;
|
|
1559
|
+
const nativeHistoryCoverage = typeof (nativeHistory as any)?.nativeHistoryCoverage === 'string'
|
|
1560
|
+
? (nativeHistory as any).nativeHistoryCoverage
|
|
1561
|
+
: undefined;
|
|
1562
|
+
const partialReason = typeof (nativeHistory as any)?.partialReason === 'string'
|
|
1563
|
+
? (nativeHistory as any).partialReason
|
|
1564
|
+
: undefined;
|
|
1565
|
+
const unavailableReason = typeof (nativeHistory as any)?.unavailableReason === 'string'
|
|
1566
|
+
? (nativeHistory as any).unavailableReason
|
|
1567
|
+
: undefined;
|
|
1568
|
+
const lookup = (nativeHistory as any).lookup === 'workspace' ? 'workspace' : 'session';
|
|
1569
|
+
const transcriptWorkspace = typeof (nativeHistory as any)?.workspace === 'string'
|
|
1570
|
+
? (nativeHistory as any).workspace
|
|
1571
|
+
: nativeMessages.map((message: any) => typeof message?.workspace === 'string' ? message.workspace.trim() : '').find(Boolean);
|
|
1572
|
+
const nativeHistorySessionForMapping = adapter.cliType === 'antigravity-cli'
|
|
1573
|
+
&& historyProviderSessionId
|
|
1574
|
+
&& nativeHistorySessionId
|
|
1575
|
+
&& historyProviderSessionId !== nativeHistorySessionId
|
|
1576
|
+
? undefined
|
|
1577
|
+
: nativeHistorySessionId;
|
|
1578
|
+
const safeMapping = hasSafeNativeHistoryMapping({
|
|
1579
|
+
historySessionId: lookup === 'workspace' ? undefined : nativeHistorySessionForMapping,
|
|
1580
|
+
providerSessionId: lookup === 'workspace' ? undefined : historyProviderSessionId || providerSessionId,
|
|
1581
|
+
workspace,
|
|
1582
|
+
nativeMessages,
|
|
1583
|
+
ptyMessages: returnedMessages,
|
|
1584
|
+
requireWorkspaceContentOverlap: lookup === 'workspace' && !exactNativeHistoryScope,
|
|
1585
|
+
});
|
|
1586
|
+
const freshEnough = isNativeHistoryFreshEnough({
|
|
1587
|
+
sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
|
|
1588
|
+
nativeMessages,
|
|
1589
|
+
ptyMessages: returnedMessages,
|
|
1590
|
+
});
|
|
1591
|
+
const nativeUsableForChatMessages = (nativeHistory as any).source === 'provider-native'
|
|
1592
|
+
&& nativeMessages.length > 0
|
|
1593
|
+
&& nativeHistoryCoverage !== 'partial'
|
|
1594
|
+
&& nativeHistoryCoverage !== 'unavailable'
|
|
1595
|
+
&& safeMapping;
|
|
1596
|
+
const allowStaleNativeChatMessages = adapter.cliType === 'antigravity-cli' && nativeUsableForChatMessages;
|
|
1597
|
+
if (nativeUsableForChatMessages && (freshEnough || allowStaleNativeChatMessages)) {
|
|
1598
|
+
selectedMessages = finalizeStreamingMessagesWhenIdle(nativeMessages, returnedStatus);
|
|
1599
|
+
selectedProviderSessionId = historyProviderSessionId || providerSessionId;
|
|
1600
|
+
selectedTranscriptAuthority = 'provider';
|
|
1601
|
+
selectedCoverage = (nativeHistory as any).hasMore ? 'tail' : 'full';
|
|
1602
|
+
messageSource = buildCliMessageSourceProvenance({
|
|
1603
|
+
selected: 'native-history',
|
|
1604
|
+
provider: adapter.cliType,
|
|
1605
|
+
nativeHandle: selectedProviderSessionId || nativeHistorySessionId || historySessionId,
|
|
1606
|
+
sessionWorkspace,
|
|
1607
|
+
intendedWorkspace,
|
|
1608
|
+
transcriptWorkspace,
|
|
1609
|
+
nativeSource: (nativeHistory as any).source,
|
|
1610
|
+
sourcePath: (nativeHistory as any).sourcePath,
|
|
1611
|
+
sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
|
|
1612
|
+
nativeHistoryCoverage,
|
|
1613
|
+
partialReason,
|
|
1614
|
+
unavailableReason,
|
|
1615
|
+
nativeMessages,
|
|
1616
|
+
ptyMessages: returnedMessages,
|
|
1617
|
+
returnedMessages: selectedMessages,
|
|
1618
|
+
safeMapping,
|
|
1619
|
+
freshEnough,
|
|
1620
|
+
ptyStatusApprovalOnly: true,
|
|
1621
|
+
});
|
|
1622
|
+
} else {
|
|
1623
|
+
const liveCurrentRuntimePtySafe = isCurrentRuntimePtySafelyAttributed({
|
|
1624
|
+
adapter,
|
|
1625
|
+
helpers: h,
|
|
1626
|
+
readChatArgs: args,
|
|
1627
|
+
sessionWorkspace,
|
|
1628
|
+
intendedWorkspace,
|
|
1629
|
+
ptyMessages: returnedMessages,
|
|
1630
|
+
});
|
|
1631
|
+
const mayProbeLiveCodexWorkspaceNative = adapter.cliType === 'codex-cli'
|
|
1632
|
+
&& liveCurrentRuntimePtySafe
|
|
1633
|
+
&& !(typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
|
|
1634
|
+
&& !(providerSessionId && providerSessionId.trim())
|
|
1635
|
+
&& (!historyProviderSessionId || historyProviderSessionId === nativeHistorySessionId || historyProviderSessionId === historySessionId);
|
|
1636
|
+
const liveWorkspaceNativeHistory = mayProbeLiveCodexWorkspaceNative
|
|
1637
|
+
? readLiveCodexWorkspaceNativeHistory(agentStr, {
|
|
1638
|
+
canonicalHistory: provider?.canonicalHistory,
|
|
1639
|
+
workspace,
|
|
1640
|
+
offset: 0,
|
|
1641
|
+
limit: nativeHistoryLimit,
|
|
1642
|
+
excludeRecentCount: 0,
|
|
1643
|
+
historyBehavior: provider?.historyBehavior,
|
|
1644
|
+
scripts: provider?.scripts as any,
|
|
1645
|
+
})
|
|
1646
|
+
: null;
|
|
1647
|
+
const liveWorkspaceNativeMessages = Array.isArray((liveWorkspaceNativeHistory as any)?.messages)
|
|
1648
|
+
? normalizeNativeHistoryMessages(agentStr, (liveWorkspaceNativeHistory as any).messages as ChatMessage[], (liveWorkspaceNativeHistory as any)?.providerSessionId)
|
|
1649
|
+
: [];
|
|
1650
|
+
const liveWorkspaceNativeProviderSessionId = typeof (liveWorkspaceNativeHistory as any)?.providerSessionId === 'string'
|
|
1651
|
+
? (liveWorkspaceNativeHistory as any).providerSessionId
|
|
1652
|
+
: readHistorySessionIdFromMessages(liveWorkspaceNativeMessages);
|
|
1653
|
+
const liveWorkspaceTranscriptWorkspace = typeof (liveWorkspaceNativeHistory as any)?.workspace === 'string'
|
|
1654
|
+
? (liveWorkspaceNativeHistory as any).workspace
|
|
1655
|
+
: liveWorkspaceNativeMessages.map((message: any) => typeof message?.workspace === 'string' ? message.workspace.trim() : '').find(Boolean);
|
|
1656
|
+
const liveWorkspaceNativeSafeMapping = liveWorkspaceNativeMessages.length > 0
|
|
1657
|
+
&& hasSafeNativeHistoryMapping({
|
|
1658
|
+
workspace,
|
|
1659
|
+
nativeMessages: liveWorkspaceNativeMessages,
|
|
1660
|
+
ptyMessages: returnedMessages,
|
|
1661
|
+
requireWorkspaceContentOverlap: true,
|
|
1662
|
+
});
|
|
1663
|
+
const liveWorkspaceNativeFreshEnough = liveWorkspaceNativeMessages.length > 0
|
|
1664
|
+
&& isNativeHistoryFreshEnough({
|
|
1665
|
+
sourceMtimeMs: (liveWorkspaceNativeHistory as any)?.sourceMtimeMs,
|
|
1666
|
+
nativeMessages: liveWorkspaceNativeMessages,
|
|
1667
|
+
ptyMessages: returnedMessages,
|
|
1668
|
+
});
|
|
1669
|
+
const liveWorkspaceNativeUsable = (liveWorkspaceNativeHistory as any)?.source === 'provider-native'
|
|
1670
|
+
&& liveWorkspaceNativeMessages.length > 0
|
|
1671
|
+
&& (liveWorkspaceNativeHistory as any)?.nativeHistoryCoverage !== 'partial'
|
|
1672
|
+
&& (liveWorkspaceNativeHistory as any)?.nativeHistoryCoverage !== 'unavailable'
|
|
1673
|
+
&& liveWorkspaceNativeSafeMapping
|
|
1674
|
+
&& liveWorkspaceNativeFreshEnough;
|
|
1675
|
+
if (liveWorkspaceNativeUsable) {
|
|
1676
|
+
selectedMessages = finalizeStreamingMessagesWhenIdle(liveWorkspaceNativeMessages, returnedStatus);
|
|
1677
|
+
selectedProviderSessionId = liveWorkspaceNativeProviderSessionId || providerSessionId;
|
|
1678
|
+
selectedTranscriptAuthority = 'provider';
|
|
1679
|
+
selectedCoverage = (liveWorkspaceNativeHistory as any).hasMore ? 'tail' : 'full';
|
|
1680
|
+
messageSource = buildCliMessageSourceProvenance({
|
|
1681
|
+
selected: 'native-history',
|
|
1682
|
+
provider: adapter.cliType,
|
|
1683
|
+
nativeHandle: selectedProviderSessionId || nativeHistorySessionId || historySessionId,
|
|
1684
|
+
sessionWorkspace,
|
|
1685
|
+
intendedWorkspace,
|
|
1686
|
+
transcriptWorkspace: liveWorkspaceTranscriptWorkspace,
|
|
1687
|
+
nativeSource: (liveWorkspaceNativeHistory as any).source,
|
|
1688
|
+
sourcePath: (liveWorkspaceNativeHistory as any).sourcePath,
|
|
1689
|
+
sourceMtimeMs: (liveWorkspaceNativeHistory as any).sourceMtimeMs,
|
|
1690
|
+
nativeHistoryCoverage: (liveWorkspaceNativeHistory as any).nativeHistoryCoverage,
|
|
1691
|
+
partialReason: (liveWorkspaceNativeHistory as any).partialReason,
|
|
1692
|
+
unavailableReason: (liveWorkspaceNativeHistory as any).unavailableReason,
|
|
1693
|
+
nativeMessages: liveWorkspaceNativeMessages,
|
|
1694
|
+
ptyMessages: returnedMessages,
|
|
1695
|
+
returnedMessages: selectedMessages,
|
|
1696
|
+
safeMapping: true,
|
|
1697
|
+
freshEnough: true,
|
|
1698
|
+
ptyStatusApprovalOnly: true,
|
|
1699
|
+
});
|
|
1700
|
+
(messageSource as any).selectedDaemonSource = 'live-workspace-native-history';
|
|
1701
|
+
(messageSource as any).runtimeMappingSafe = true;
|
|
1702
|
+
} else {
|
|
1703
|
+
const fallbackReason = buildNativeHistoryFallbackReason({
|
|
1704
|
+
providerType,
|
|
1705
|
+
provider,
|
|
1706
|
+
nativeSource: (nativeHistory as any).source,
|
|
1707
|
+
nativeHistoryCoverage,
|
|
1708
|
+
unavailableReason,
|
|
1709
|
+
nativeMessageCount: nativeMessages.length,
|
|
1710
|
+
safeMapping,
|
|
1711
|
+
freshEnough,
|
|
1712
|
+
});
|
|
1713
|
+
const unsafeNativeFallback = adapter.cliType === 'codex-cli'
|
|
1714
|
+
&& isUnsafeNativeTranscriptFallback(fallbackReason);
|
|
1715
|
+
const safeCurrentRuntimePtyMessages = unsafeNativeFallback
|
|
1716
|
+
&& isCurrentRuntimePtySafelyAttributed({
|
|
1717
|
+
adapter,
|
|
1718
|
+
helpers: h,
|
|
1719
|
+
readChatArgs: args,
|
|
1720
|
+
sessionWorkspace,
|
|
1721
|
+
intendedWorkspace,
|
|
1722
|
+
ptyMessages: returnedMessages,
|
|
1723
|
+
});
|
|
1724
|
+
const safeRuntimeAckMessages = unsafeNativeFallback
|
|
1725
|
+
&& !safeCurrentRuntimePtyMessages
|
|
1726
|
+
? selectRuntimeInputAckMessages(returnedMessages)
|
|
1727
|
+
: [];
|
|
1728
|
+
const exactRuntimeMirrorMessages = unsafeNativeFallback
|
|
1729
|
+
&& !safeCurrentRuntimePtyMessages
|
|
1730
|
+
&& safeRuntimeAckMessages.length === 0
|
|
1731
|
+
? readExactRuntimeMirrorMessages({
|
|
1732
|
+
providerType,
|
|
1733
|
+
targetSessionId: typeof args?.targetSessionId === 'string' ? args.targetSessionId : undefined,
|
|
1734
|
+
currentSessionId: typeof (h.currentSession as any)?.sessionId === 'string' ? (h.currentSession as any).sessionId : undefined,
|
|
1735
|
+
tailLimit: nativeHistoryLimit,
|
|
1736
|
+
historyBehavior: provider?.historyBehavior,
|
|
1737
|
+
})
|
|
1738
|
+
: [];
|
|
1739
|
+
const safeDaemonMessages = safeRuntimeAckMessages.length > 0
|
|
1740
|
+
? safeRuntimeAckMessages
|
|
1741
|
+
: exactRuntimeMirrorMessages;
|
|
1742
|
+
if (unsafeNativeFallback) {
|
|
1743
|
+
if (safeCurrentRuntimePtyMessages) {
|
|
1744
|
+
selectedMessages = returnedMessages;
|
|
1745
|
+
selectedTranscriptAuthority = 'daemon';
|
|
1746
|
+
selectedCoverage = coverage || 'current-turn';
|
|
1747
|
+
selectedStatus = returnedStatus;
|
|
1748
|
+
} else {
|
|
1749
|
+
selectedMessages = safeDaemonMessages;
|
|
1750
|
+
selectedTranscriptAuthority = safeDaemonMessages.length > 0 ? 'daemon' : undefined;
|
|
1751
|
+
selectedCoverage = safeDaemonMessages.length > 0 ? 'tail' : undefined;
|
|
1752
|
+
selectedStatus = coerceUnsafeNativeFallbackStatus(returnedStatus, activeModal);
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
messageSource = buildCliMessageSourceProvenance({
|
|
1756
|
+
selected: 'pty-parser',
|
|
1757
|
+
provider: adapter.cliType,
|
|
1758
|
+
nativeHandle: historyProviderSessionId || nativeHistorySessionId || historySessionId,
|
|
1759
|
+
sessionWorkspace,
|
|
1760
|
+
intendedWorkspace,
|
|
1761
|
+
transcriptWorkspace,
|
|
1762
|
+
fallbackReason,
|
|
1763
|
+
nativeSource: (nativeHistory as any).source,
|
|
1764
|
+
sourcePath: (nativeHistory as any).sourcePath,
|
|
1765
|
+
sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
|
|
1766
|
+
nativeHistoryCoverage,
|
|
1767
|
+
partialReason,
|
|
1768
|
+
unavailableReason,
|
|
1769
|
+
nativeMessages,
|
|
1770
|
+
ptyMessages: returnedMessages,
|
|
1771
|
+
returnedMessages: unsafeNativeFallback && !safeCurrentRuntimePtyMessages ? safeDaemonMessages : returnedMessages,
|
|
1772
|
+
safeMapping,
|
|
1773
|
+
freshEnough,
|
|
1774
|
+
ptyStatusApprovalOnly: unsafeNativeFallback && !safeCurrentRuntimePtyMessages,
|
|
1775
|
+
});
|
|
1776
|
+
if (safeCurrentRuntimePtyMessages) {
|
|
1777
|
+
(messageSource as any).selectedDaemonSource = 'current-runtime-pty';
|
|
1778
|
+
(messageSource as any).transcriptAuthority = 'daemon';
|
|
1779
|
+
(messageSource as any).runtimeMappingSafe = true;
|
|
1780
|
+
}
|
|
1781
|
+
if (unsafeNativeFallback && exactRuntimeMirrorMessages.length > 0) {
|
|
1782
|
+
(messageSource as any).selectedDaemonSource = 'exact-runtime-mirror';
|
|
1783
|
+
(messageSource as any).transcriptAuthority = 'daemon';
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
885
1789
|
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
1790
|
return buildReadChatCommandResult({
|
|
887
|
-
messages:
|
|
888
|
-
status:
|
|
1791
|
+
messages: selectedMessages,
|
|
1792
|
+
status: selectedStatus,
|
|
889
1793
|
activeModal,
|
|
1794
|
+
messageSource,
|
|
1795
|
+
transcriptProvenance: messageSource,
|
|
890
1796
|
debugReadChat: {
|
|
891
1797
|
provider: adapter.cliType,
|
|
892
1798
|
targetSessionId: String(args?.targetSessionId || ''),
|
|
893
1799
|
adapterStatus: String(adapterStatus.status || ''),
|
|
894
1800
|
parsedStatus: String(parsedRecord.status || ''),
|
|
895
|
-
returnedStatus: String(
|
|
896
|
-
|
|
1801
|
+
returnedStatus: String(selectedStatus || ''),
|
|
1802
|
+
selectedMessageSource: (messageSource as any).selected,
|
|
1803
|
+
messageSource,
|
|
1804
|
+
shouldPreferAdapterMessages: supportsCliNativeTranscript(providerType, provider)
|
|
1805
|
+
&& isNativeSourceCanonicalHistory(provider?.canonicalHistory)
|
|
1806
|
+
&& (messageSource as any).selected !== 'native-history'
|
|
1807
|
+
&& typeof (messageSource as any).fallbackReason === 'string'
|
|
1808
|
+
&& (messageSource as any).fallbackReason.startsWith('native_history_')
|
|
1809
|
+
&& (messageSource as any).fallbackReason !== 'native_history_not_checked'
|
|
1810
|
+
&& !isUnsafeNativeTranscriptFallback((messageSource as any).fallbackReason)
|
|
1811
|
+
&& !(selectedTranscriptAuthority === 'provider' && selectedCoverage === 'full'),
|
|
897
1812
|
parsedMsgCount: parsedRecord.messages.length,
|
|
898
|
-
returnedMsgCount:
|
|
1813
|
+
returnedMsgCount: selectedMessages.length,
|
|
899
1814
|
},
|
|
900
|
-
...(
|
|
901
|
-
...(
|
|
902
|
-
...(
|
|
903
|
-
...(
|
|
1815
|
+
...(selectedTitle ? { title: selectedTitle } : {}),
|
|
1816
|
+
...(selectedProviderSessionId ? { providerSessionId: selectedProviderSessionId } : {}),
|
|
1817
|
+
...(selectedTranscriptAuthority ? { transcriptAuthority: selectedTranscriptAuthority } : {}),
|
|
1818
|
+
...(selectedCoverage ? { coverage: selectedCoverage } : {}),
|
|
904
1819
|
}, args);
|
|
905
1820
|
}
|
|
906
1821
|
const historyLimit = normalizeReadChatTailLimit(args);
|
|
907
1822
|
try {
|
|
908
1823
|
const agentStr = provider?.type || args?.agentType || getCurrentProviderType(h);
|
|
909
|
-
const workspace = typeof
|
|
910
|
-
?
|
|
911
|
-
:
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1824
|
+
const workspace = typeof (h.currentSession as any)?.workspace === 'string'
|
|
1825
|
+
? (h.currentSession as any).workspace
|
|
1826
|
+
: undefined;
|
|
1827
|
+
const intendedWorkspace = typeof args?.workspace === 'string' ? args.workspace : undefined;
|
|
1828
|
+
const exactNativeHistoryScope = Boolean(
|
|
1829
|
+
(typeof args?.targetSessionId === 'string' && args.targetSessionId.trim())
|
|
1830
|
+
|| (typeof args?.historySessionId === 'string' && args.historySessionId.trim())
|
|
1831
|
+
|| (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
|
|
1832
|
+
|| ((h.currentSession as any)?.sessionId === args?.targetSessionId && typeof (h.currentSession as any)?.providerSessionId === 'string' && (h.currentSession as any).providerSessionId.trim())
|
|
1833
|
+
);
|
|
1834
|
+
const history = supportsCliNativeTranscript(agentStr, provider) && isNativeSourceCanonicalHistory(provider?.canonicalHistory)
|
|
1835
|
+
? readCliProviderNativeHistory(agentStr, {
|
|
1836
|
+
canonicalHistory: provider?.canonicalHistory,
|
|
1837
|
+
historySessionId,
|
|
1838
|
+
workspace,
|
|
1839
|
+
offset: 0,
|
|
1840
|
+
limit: historyLimit,
|
|
1841
|
+
excludeRecentCount: 0,
|
|
1842
|
+
historyBehavior: provider?.historyBehavior,
|
|
1843
|
+
scripts: provider?.scripts as any,
|
|
1844
|
+
})
|
|
1845
|
+
: readProviderChatHistory(agentStr, {
|
|
915
1846
|
canonicalHistory: provider?.canonicalHistory,
|
|
916
1847
|
historySessionId,
|
|
917
1848
|
workspace,
|
|
@@ -921,12 +1852,87 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
921
1852
|
historyBehavior: provider?.historyBehavior,
|
|
922
1853
|
scripts: provider?.scripts as any,
|
|
923
1854
|
});
|
|
1855
|
+
const lookup = (history as any).lookup === 'workspace' ? 'workspace' : 'session';
|
|
1856
|
+
const historyMessages = Array.isArray((history as any)?.messages)
|
|
1857
|
+
? normalizeNativeHistoryMessages(agentStr, (history as any).messages as ChatMessage[], (history as any)?.providerSessionId)
|
|
1858
|
+
: [];
|
|
924
1859
|
const historyProviderSessionId = typeof (history as any)?.providerSessionId === 'string'
|
|
925
1860
|
? (history as any).providerSessionId
|
|
926
|
-
: historySessionId;
|
|
1861
|
+
: readHistorySessionIdFromMessages(historyMessages) || historySessionId;
|
|
1862
|
+
const nativeHistoryCoverage = typeof (history as any)?.nativeHistoryCoverage === 'string'
|
|
1863
|
+
? (history as any).nativeHistoryCoverage
|
|
1864
|
+
: undefined;
|
|
1865
|
+
const partialReason = typeof (history as any)?.partialReason === 'string'
|
|
1866
|
+
? (history as any).partialReason
|
|
1867
|
+
: undefined;
|
|
1868
|
+
const unavailableReason = typeof (history as any)?.unavailableReason === 'string'
|
|
1869
|
+
? (history as any).unavailableReason
|
|
1870
|
+
: undefined;
|
|
1871
|
+
const transcriptWorkspace = typeof (history as any)?.workspace === 'string'
|
|
1872
|
+
? (history as any).workspace
|
|
1873
|
+
: historyMessages.map((message: any) => typeof message?.workspace === 'string' ? message.workspace.trim() : '').find(Boolean);
|
|
1874
|
+
const safeMapping = supportsCliNativeTranscript(agentStr, provider)
|
|
1875
|
+
? hasSafeNativeHistoryMapping({
|
|
1876
|
+
historySessionId: lookup === 'workspace' ? undefined : historySessionId,
|
|
1877
|
+
providerSessionId: lookup === 'workspace' ? undefined : historyProviderSessionId,
|
|
1878
|
+
workspace,
|
|
1879
|
+
nativeMessages: historyMessages,
|
|
1880
|
+
})
|
|
1881
|
+
: false;
|
|
1882
|
+
const nativeSelected = supportsCliNativeTranscript(agentStr, provider)
|
|
1883
|
+
&& (history as any).source === 'provider-native'
|
|
1884
|
+
&& historyMessages.length > 0
|
|
1885
|
+
&& nativeHistoryCoverage !== 'partial'
|
|
1886
|
+
&& nativeHistoryCoverage !== 'unavailable'
|
|
1887
|
+
&& safeMapping;
|
|
1888
|
+
const messageSource = buildCliMessageSourceProvenance({
|
|
1889
|
+
selected: nativeSelected ? 'native-history' : 'pty-parser',
|
|
1890
|
+
provider: agentStr,
|
|
1891
|
+
nativeHandle: historyProviderSessionId || historySessionId,
|
|
1892
|
+
sessionWorkspace: workspace,
|
|
1893
|
+
intendedWorkspace,
|
|
1894
|
+
transcriptWorkspace,
|
|
1895
|
+
fallbackReason: nativeSelected
|
|
1896
|
+
? undefined
|
|
1897
|
+
: buildNativeHistoryFallbackReason({
|
|
1898
|
+
providerType: agentStr,
|
|
1899
|
+
provider,
|
|
1900
|
+
nativeSource: (history as any).source,
|
|
1901
|
+
nativeHistoryCoverage,
|
|
1902
|
+
unavailableReason,
|
|
1903
|
+
nativeMessageCount: historyMessages.length,
|
|
1904
|
+
safeMapping,
|
|
1905
|
+
freshEnough: true,
|
|
1906
|
+
}),
|
|
1907
|
+
nativeSource: (history as any).source,
|
|
1908
|
+
sourcePath: (history as any).sourcePath,
|
|
1909
|
+
sourceMtimeMs: (history as any).sourceMtimeMs,
|
|
1910
|
+
nativeHistoryCoverage,
|
|
1911
|
+
partialReason,
|
|
1912
|
+
unavailableReason,
|
|
1913
|
+
nativeMessages: historyMessages,
|
|
1914
|
+
returnedMessages: historyMessages,
|
|
1915
|
+
safeMapping,
|
|
1916
|
+
freshEnough: true,
|
|
1917
|
+
ptyStatusApprovalOnly: false,
|
|
1918
|
+
});
|
|
1919
|
+
const requiresNativeSource = supportsCliNativeTranscript(agentStr, provider)
|
|
1920
|
+
&& isNativeSourceCanonicalHistory(provider?.canonicalHistory);
|
|
1921
|
+
if (requiresNativeSource && !nativeSelected) {
|
|
1922
|
+
return {
|
|
1923
|
+
success: false,
|
|
1924
|
+
code: 'native_history_not_safely_available',
|
|
1925
|
+
error: 'Provider-native history was not safely available for the requested CLI session.',
|
|
1926
|
+
providerSessionId: historyProviderSessionId,
|
|
1927
|
+
messageSource,
|
|
1928
|
+
transcriptProvenance: messageSource,
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
927
1931
|
return buildReadChatCommandResult({
|
|
928
|
-
messages:
|
|
1932
|
+
messages: historyMessages,
|
|
929
1933
|
status: 'idle',
|
|
1934
|
+
messageSource,
|
|
1935
|
+
transcriptProvenance: messageSource,
|
|
930
1936
|
...(typeof (history as any)?.title === 'string' ? { title: (history as any).title } : {}),
|
|
931
1937
|
...(historyProviderSessionId ? { providerSessionId: historyProviderSessionId } : {}),
|
|
932
1938
|
...(((provider?.historyBehavior as any)?.transcriptAuthority === 'provider' || (provider?.historyBehavior as any)?.transcriptAuthority === 'daemon')
|
|
@@ -1184,8 +2190,24 @@ export async function handleSendChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
1184
2190
|
assertTextOnlyInput(provider, input);
|
|
1185
2191
|
if (!text) return { success: false, error: 'text required for PTY send' };
|
|
1186
2192
|
await waitOnceForFreshHermesCliStart(adapter, _log);
|
|
1187
|
-
|
|
1188
|
-
|
|
2193
|
+
const forceSend = args?.force === true || args?.forceSend === true;
|
|
2194
|
+
if (forceSend && typeof adapter.forceSendMessage === 'function') {
|
|
2195
|
+
await adapter.forceSendMessage(text);
|
|
2196
|
+
} else if (forceSend) {
|
|
2197
|
+
await adapter.sendMessage(text, { force: true });
|
|
2198
|
+
} else {
|
|
2199
|
+
await adapter.sendMessage(text);
|
|
2200
|
+
}
|
|
2201
|
+
const target = getTargetInstance(h, args) as RuntimeChatMessageMerger | null;
|
|
2202
|
+
if (target?.category === 'cli'
|
|
2203
|
+
&& target.type === adapter.cliType
|
|
2204
|
+
&& typeof target.recordAcknowledgedUserInput === 'function') {
|
|
2205
|
+
target.recordAcknowledgedUserInput(input);
|
|
2206
|
+
}
|
|
2207
|
+
return {
|
|
2208
|
+
..._logSendSuccess(`${transport}-adapter`, adapter.cliType),
|
|
2209
|
+
...(forceSend ? { forceSent: true } : {}),
|
|
2210
|
+
};
|
|
1189
2211
|
} catch (e: any) {
|
|
1190
2212
|
return { success: false, error: `${transport} send failed: ${e.message}` };
|
|
1191
2213
|
}
|
|
@@ -1742,6 +2764,9 @@ export async function handleResolveAction(h: CommandHelpers, args: any): Promise
|
|
|
1742
2764
|
if (buttonIndex < 0 && (action === 'always' || /always/i.test(button))) {
|
|
1743
2765
|
buttonIndex = buttons.findIndex(b => /always/i.test(b));
|
|
1744
2766
|
}
|
|
2767
|
+
if (buttonIndex < 0 && (action === 'approve' || action === 'accept')) {
|
|
2768
|
+
buttonIndex = pickApprovalButton(buttons, provider).index;
|
|
2769
|
+
}
|
|
1745
2770
|
if (buttonIndex < 0) {
|
|
1746
2771
|
return { success: false, error: 'Approval action did not match any visible button' };
|
|
1747
2772
|
}
|