@adhdev/daemon-core 0.9.82-rc.8 → 0.9.82-rc.81
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/cli-adapters/provider-cli-adapter.d.ts +2 -0
- package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +2 -0
- package/dist/commands/router.d.ts +22 -0
- package/dist/config/mesh-config.d.ts +66 -1
- package/dist/git/git-commands.d.ts +1 -0
- package/dist/git/git-status.d.ts +5 -0
- package/dist/git/git-types.d.ts +10 -0
- package/dist/index.d.ts +13 -6
- package/dist/index.js +5074 -1177
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5038 -1163
- 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 +60 -0
- package/dist/mesh/mesh-events.d.ts +29 -5
- package/dist/mesh/mesh-fast-forward.d.ts +39 -0
- package/dist/mesh/mesh-host-ownership.d.ts +9 -0
- package/dist/mesh/mesh-ledger.d.ts +38 -1
- package/dist/mesh/mesh-work-queue.d.ts +27 -5
- package/dist/mesh/refine-config.d.ts +119 -0
- package/dist/providers/chat-message-normalization.d.ts +1 -0
- package/dist/providers/cli-provider-instance.d.ts +2 -1
- package/dist/repo-mesh-types.d.ts +39 -0
- package/dist/status/reporter.d.ts +2 -0
- package/package.json +3 -1
- package/src/boot/daemon-lifecycle.ts +1 -0
- package/src/cli-adapters/provider-cli-adapter.ts +91 -3
- package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/src/cli-adapters/provider-cli-parse.ts +4 -0
- package/src/cli-adapters/provider-cli-runtime.ts +3 -1
- package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
- package/src/cli-adapters/provider-cli-shared.ts +20 -10
- package/src/commands/chat-commands.ts +310 -12
- package/src/commands/cli-manager.ts +101 -0
- package/src/commands/handler.ts +8 -1
- package/src/commands/mesh-coordinator.ts +13 -143
- package/src/commands/router.ts +2435 -414
- package/src/config/chat-history.ts +9 -7
- package/src/config/mesh-config.ts +244 -1
- package/src/daemon/dev-cli-debug.ts +10 -1
- package/src/detection/ide-detector.ts +26 -16
- package/src/git/git-commands.ts +3 -3
- package/src/git/git-status.ts +97 -6
- package/src/git/git-summary.ts +3 -0
- package/src/git/git-types.ts +11 -0
- package/src/index.ts +31 -5
- package/src/installer.d.ts +1 -1
- package/src/installer.ts +8 -6
- package/src/launch.d.ts +1 -1
- package/src/launch.ts +37 -28
- package/src/logging/async-batch-writer.ts +55 -0
- package/src/logging/logger.ts +2 -1
- package/src/mesh/beads-db.ts +176 -0
- package/src/mesh/coordinator-prompt.ts +27 -7
- package/src/mesh/mesh-active-work.ts +243 -0
- package/src/mesh/mesh-events.ts +398 -46
- package/src/mesh/mesh-fast-forward.ts +430 -0
- package/src/mesh/mesh-host-ownership.ts +73 -0
- package/src/mesh/mesh-ledger.ts +138 -1
- package/src/mesh/mesh-work-queue.ts +199 -137
- package/src/mesh/refine-config.ts +306 -0
- package/src/providers/chat-message-normalization.ts +3 -1
- package/src/providers/cli-provider-instance.ts +91 -13
- 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 +43 -0
- package/src/status/reporter.ts +15 -0
- package/src/system/host-memory.ts +29 -12
|
@@ -761,6 +761,17 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
761
761
|
if (stableMs < 2000) return;
|
|
762
762
|
|
|
763
763
|
const startupModal = this.runParseApproval(this.recentOutputBuffer);
|
|
764
|
+
const startupStatus = this.runDetectStatus(screenText || this.recentOutputBuffer);
|
|
765
|
+
if (!startupModal && startupStatus !== 'idle') {
|
|
766
|
+
this.recordTrace('startup_settle_deferred', {
|
|
767
|
+
trigger,
|
|
768
|
+
startupStatus,
|
|
769
|
+
stableMs,
|
|
770
|
+
screenText: summarizeCliTraceText(screenText, 500),
|
|
771
|
+
});
|
|
772
|
+
this.scheduleStartupSettleCheck();
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
764
775
|
this.startupParseGate = false;
|
|
765
776
|
if (this.startupSettleTimer) {
|
|
766
777
|
clearTimeout(this.startupSettleTimer);
|
|
@@ -956,6 +967,38 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
956
967
|
return true;
|
|
957
968
|
}
|
|
958
969
|
|
|
970
|
+
private clearParsedIdleResponseGuard(reason: string, parsedStatus: any): boolean {
|
|
971
|
+
const parsedRawStatus = typeof parsedStatus?.status === 'string' ? parsedStatus.status.trim() : '';
|
|
972
|
+
const parsedModal = parsedStatus?.activeModal ?? parsedStatus?.modal ?? null;
|
|
973
|
+
const blockingModal = this.activeModal || this.runParseApproval(this.recentOutputBuffer);
|
|
974
|
+
if (
|
|
975
|
+
!this.isWaitingForResponse
|
|
976
|
+
|| parsedRawStatus !== 'idle'
|
|
977
|
+
|| !!parsedModal
|
|
978
|
+
|| !!blockingModal
|
|
979
|
+
|| !this.parsedStatusHasFinalAssistantMessage(parsedStatus)
|
|
980
|
+
) {
|
|
981
|
+
return false;
|
|
982
|
+
}
|
|
983
|
+
this.clearAllTimers();
|
|
984
|
+
this.clearIdleFinishCandidate(reason);
|
|
985
|
+
this.responseBuffer = '';
|
|
986
|
+
this.isWaitingForResponse = false;
|
|
987
|
+
this.responseSettleIgnoreUntil = 0;
|
|
988
|
+
this.submitRetryUsed = false;
|
|
989
|
+
this.submitRetryPromptSnippet = '';
|
|
990
|
+
this.finishRetryCount = 0;
|
|
991
|
+
this.currentTurnScope = null;
|
|
992
|
+
this.activeModal = null;
|
|
993
|
+
this.setStatus('idle', reason);
|
|
994
|
+
this.recordTrace('parsed_idle_response_cleared', {
|
|
995
|
+
reason,
|
|
996
|
+
parsedStatus: parsedRawStatus,
|
|
997
|
+
parsedMessageCount: Array.isArray(parsedStatus?.messages) ? parsedStatus.messages.length : 0,
|
|
998
|
+
});
|
|
999
|
+
return true;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
959
1002
|
private hasMeaningfulResponseBuffer(promptSnippet: string): boolean {
|
|
960
1003
|
const raw = String(this.responseBuffer || '').trim();
|
|
961
1004
|
if (!raw) return false;
|
|
@@ -1489,6 +1532,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1489
1532
|
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
1490
1533
|
recentOutputBuffer: this.recentOutputBuffer,
|
|
1491
1534
|
terminalScreenText: parseScreenText,
|
|
1535
|
+
workingDir: this.workingDir,
|
|
1492
1536
|
baseMessages: [],
|
|
1493
1537
|
partialResponse: this.responseBuffer,
|
|
1494
1538
|
isWaitingForResponse: this.isWaitingForResponse,
|
|
@@ -1552,6 +1596,15 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1552
1596
|
return !!(startupModal || this.activeModal);
|
|
1553
1597
|
}
|
|
1554
1598
|
|
|
1599
|
+
private parsedStatusHasFinalAssistantMessage(parsed: any): boolean {
|
|
1600
|
+
const messages = Array.isArray(parsed?.messages) ? parsed.messages : [];
|
|
1601
|
+
const lastAssistant = [...messages].reverse().find((message: any) => {
|
|
1602
|
+
if (!message || message.role !== 'assistant') return false;
|
|
1603
|
+
return typeof message.content === 'string' && message.content.trim().length > 0;
|
|
1604
|
+
});
|
|
1605
|
+
return !!lastAssistant;
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1555
1608
|
private projectEffectiveStatus(startupModal: { message: string; buttons: string[] } | null = null): CliSessionStatus['status'] {
|
|
1556
1609
|
if (this.parseErrorMessage) return 'error';
|
|
1557
1610
|
if (this.hasActionableApproval(startupModal)) return 'waiting_approval';
|
|
@@ -1564,8 +1617,14 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1564
1617
|
getStatus(options: { allowParse?: boolean } = {}): CliSessionStatus {
|
|
1565
1618
|
const allowParse = options.allowParse !== false;
|
|
1566
1619
|
const startupModal = allowParse && this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
|
|
1620
|
+
const startupDetectedStatus = allowParse && this.startupParseGate && !startupModal
|
|
1621
|
+
? this.runDetectStatus(this.recentOutputBuffer || this.terminalScreen.getText())
|
|
1622
|
+
: null;
|
|
1567
1623
|
let effectiveStatus = this.projectEffectiveStatus(startupModal);
|
|
1568
1624
|
let effectiveModal = startupModal || this.activeModal;
|
|
1625
|
+
if (startupDetectedStatus === 'waiting_approval') {
|
|
1626
|
+
effectiveStatus = 'waiting_approval';
|
|
1627
|
+
}
|
|
1569
1628
|
if (allowParse && !startupModal && !effectiveModal) {
|
|
1570
1629
|
const parsed = this.getFreshParsedStatusCache();
|
|
1571
1630
|
const parsedModal = parsed?.activeModal && Array.isArray(parsed.activeModal.buttons)
|
|
@@ -1575,6 +1634,18 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1575
1634
|
if (parsed?.status === 'waiting_approval' && parsedModal) {
|
|
1576
1635
|
effectiveStatus = 'waiting_approval';
|
|
1577
1636
|
effectiveModal = parsedModal;
|
|
1637
|
+
} else if (
|
|
1638
|
+
effectiveStatus === 'idle'
|
|
1639
|
+
&& parsed?.status === 'generating'
|
|
1640
|
+
&& !this.parsedStatusHasFinalAssistantMessage(parsed)
|
|
1641
|
+
) {
|
|
1642
|
+
effectiveStatus = 'generating';
|
|
1643
|
+
} else if (
|
|
1644
|
+
effectiveStatus === 'generating'
|
|
1645
|
+
&& parsed?.status === 'idle'
|
|
1646
|
+
&& this.parsedStatusHasFinalAssistantMessage(parsed)
|
|
1647
|
+
) {
|
|
1648
|
+
effectiveStatus = 'idle';
|
|
1578
1649
|
}
|
|
1579
1650
|
}
|
|
1580
1651
|
const bufferState = this.getBufferState();
|
|
@@ -1665,6 +1736,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1665
1736
|
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
1666
1737
|
recentOutputBuffer: this.recentOutputBuffer,
|
|
1667
1738
|
terminalScreenText: this.getParseScreenText(this.terminalScreen.getText()),
|
|
1739
|
+
workingDir: this.workingDir,
|
|
1668
1740
|
baseMessages: [],
|
|
1669
1741
|
partialResponse: this.responseBuffer,
|
|
1670
1742
|
isWaitingForResponse: this.isWaitingForResponse,
|
|
@@ -1979,7 +2051,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1979
2051
|
}
|
|
1980
2052
|
}
|
|
1981
2053
|
if (this.isWaitingForResponse && !allowInputDuringGeneration) {
|
|
1982
|
-
if (
|
|
2054
|
+
if (
|
|
2055
|
+
!this.clearStaleIdleResponseGuard('send_message_guard')
|
|
2056
|
+
&& !this.clearParsedIdleResponseGuard('send_message_parsed_idle_guard', parsedStatusBeforeSend)
|
|
2057
|
+
) {
|
|
1983
2058
|
throw new Error(`${this.cliName} is still processing the previous prompt`);
|
|
1984
2059
|
}
|
|
1985
2060
|
}
|
|
@@ -2369,10 +2444,23 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2369
2444
|
getDebugState(): Record<string, any> {
|
|
2370
2445
|
const screenText = sanitizeTerminalText(this.terminalScreen.getText());
|
|
2371
2446
|
const startupModal = this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
|
|
2372
|
-
const
|
|
2373
|
-
|
|
2447
|
+
const startupDetectedStatus = this.startupParseGate && !startupModal
|
|
2448
|
+
? this.runDetectStatus(this.recentOutputBuffer || screenText)
|
|
2449
|
+
: null;
|
|
2450
|
+
const effectiveReady = this.ready || !!startupModal || startupDetectedStatus === 'waiting_approval';
|
|
2374
2451
|
const parsedDebugState = this.getParsedDebugState();
|
|
2375
2452
|
const parsedMessages = Array.isArray(parsedDebugState?.messages) ? parsedDebugState.messages : [];
|
|
2453
|
+
let effectiveStatus = this.projectEffectiveStatus(startupModal);
|
|
2454
|
+
if (startupDetectedStatus === 'waiting_approval') {
|
|
2455
|
+
effectiveStatus = 'waiting_approval';
|
|
2456
|
+
}
|
|
2457
|
+
if (
|
|
2458
|
+
effectiveStatus === 'idle'
|
|
2459
|
+
&& parsedDebugState?.status === 'generating'
|
|
2460
|
+
&& !this.parsedStatusHasFinalAssistantMessage(parsedDebugState)
|
|
2461
|
+
) {
|
|
2462
|
+
effectiveStatus = 'generating';
|
|
2463
|
+
}
|
|
2376
2464
|
return {
|
|
2377
2465
|
type: this.cliType,
|
|
2378
2466
|
name: this.cliName,
|
|
@@ -15,6 +15,7 @@ export declare function buildCliParseInput(options: {
|
|
|
15
15
|
accumulatedRawBuffer: string;
|
|
16
16
|
recentOutputBuffer: string;
|
|
17
17
|
terminalScreenText: string;
|
|
18
|
+
workingDir?: string;
|
|
18
19
|
baseMessages: CliChatMessage[];
|
|
19
20
|
partialResponse: string;
|
|
20
21
|
isWaitingForResponse?: boolean;
|
|
@@ -35,6 +35,7 @@ export function buildCliParseInput(options: {
|
|
|
35
35
|
accumulatedRawBuffer: string;
|
|
36
36
|
recentOutputBuffer: string;
|
|
37
37
|
terminalScreenText: string;
|
|
38
|
+
workingDir?: string;
|
|
38
39
|
baseMessages: CliChatMessage[];
|
|
39
40
|
partialResponse: string;
|
|
40
41
|
isWaitingForResponse?: boolean;
|
|
@@ -46,6 +47,7 @@ export function buildCliParseInput(options: {
|
|
|
46
47
|
accumulatedRawBuffer,
|
|
47
48
|
recentOutputBuffer,
|
|
48
49
|
terminalScreenText,
|
|
50
|
+
workingDir,
|
|
49
51
|
baseMessages,
|
|
50
52
|
partialResponse,
|
|
51
53
|
isWaitingForResponse,
|
|
@@ -66,6 +68,8 @@ export function buildCliParseInput(options: {
|
|
|
66
68
|
rawBuffer,
|
|
67
69
|
recentBuffer,
|
|
68
70
|
screenText,
|
|
71
|
+
workspace: workingDir,
|
|
72
|
+
workingDir,
|
|
69
73
|
screen: buildCliScreenSnapshot(screenText),
|
|
70
74
|
bufferScreen: buildCliScreenSnapshot(buffer),
|
|
71
75
|
recentScreen: buildCliScreenSnapshot(recentBuffer),
|
|
@@ -36,7 +36,9 @@ export function resolveCliSpawnPlan(options: {
|
|
|
36
36
|
: spawnConfig.command;
|
|
37
37
|
const binaryPath = findBinary(configuredCommand);
|
|
38
38
|
const isWin = os.platform() === 'win32';
|
|
39
|
-
const allArgs = [...spawnConfig.args, ...extraArgs]
|
|
39
|
+
const allArgs = [...spawnConfig.args, ...extraArgs].map((arg) =>
|
|
40
|
+
typeof arg === 'string' ? arg.replace(/\{\{workingDir\}\}/g, workingDir) : arg,
|
|
41
|
+
);
|
|
40
42
|
|
|
41
43
|
let shellCmd: string;
|
|
42
44
|
let shellArgs: string[];
|
|
@@ -94,6 +94,8 @@ export interface CliScriptInput {
|
|
|
94
94
|
rawBuffer: string;
|
|
95
95
|
recentBuffer: string;
|
|
96
96
|
screenText: string;
|
|
97
|
+
workspace?: string;
|
|
98
|
+
workingDir?: string;
|
|
97
99
|
screen: CliScreenSnapshot;
|
|
98
100
|
bufferScreen: CliScreenSnapshot;
|
|
99
101
|
recentScreen: CliScreenSnapshot;
|
|
@@ -484,17 +486,25 @@ export function findBinary(name: string): string {
|
|
|
484
486
|
return path.isAbsolute(expanded) ? expanded : path.resolve(expanded);
|
|
485
487
|
}
|
|
486
488
|
const isWin = os.platform() === 'win32';
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
489
|
+
const paths = (process.env.PATH || '').split(path.delimiter);
|
|
490
|
+
const exes = isWin ? ['.exe', '.cmd', '.bat', ''] : [''];
|
|
491
|
+
|
|
492
|
+
for (const p of paths) {
|
|
493
|
+
if (!p) continue;
|
|
494
|
+
for (const ext of exes) {
|
|
495
|
+
const fullPath = path.join(p, trimmed + ext);
|
|
496
|
+
try {
|
|
497
|
+
const fs = require('fs');
|
|
498
|
+
if (fs.existsSync(fullPath)) {
|
|
499
|
+
const stat = fs.statSync(fullPath);
|
|
500
|
+
if (stat.isFile() && (isWin || (stat.mode & 0o111))) {
|
|
501
|
+
return fullPath;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
} catch { }
|
|
505
|
+
}
|
|
497
506
|
}
|
|
507
|
+
return isWin ? `${trimmed}.cmd` : trimmed;
|
|
498
508
|
}
|
|
499
509
|
|
|
500
510
|
export function isScriptBinary(binaryPath: string): boolean {
|
|
@@ -24,6 +24,8 @@ import { filterUserFacingChatMessages, normalizeChatMessages } from '../provider
|
|
|
24
24
|
const RECENT_SEND_WINDOW_MS = 1200;
|
|
25
25
|
export const READ_CHAT_PROVIDER_EVAL_TIMEOUT_MS = 25_000;
|
|
26
26
|
const HERMES_CLI_STARTING_SEND_SETTLE_MS = 2_000;
|
|
27
|
+
const CLI_NATIVE_HISTORY_FRESH_MS = 5 * 60_000;
|
|
28
|
+
const CLI_NATIVE_TRANSCRIPT_PROVIDERS = new Set(['codex-cli', 'claude-cli']);
|
|
27
29
|
const recentSendByTarget = new Map<string, number>();
|
|
28
30
|
|
|
29
31
|
interface ApprovalSelectableInstance extends ProviderInstance {
|
|
@@ -151,7 +153,17 @@ function getHistorySessionId(h: CommandHelpers, args: any): string | undefined {
|
|
|
151
153
|
const instance = h.ctx.instanceManager?.getInstance(targetSessionId);
|
|
152
154
|
const state = instance?.getState?.();
|
|
153
155
|
const providerSessionId = typeof state?.providerSessionId === 'string' ? state.providerSessionId.trim() : '';
|
|
154
|
-
|
|
156
|
+
if (providerSessionId) return providerSessionId;
|
|
157
|
+
|
|
158
|
+
const currentSession = h.currentSession as any;
|
|
159
|
+
if (currentSession?.sessionId === targetSessionId) {
|
|
160
|
+
const currentProviderSessionId = typeof currentSession.providerSessionId === 'string'
|
|
161
|
+
? currentSession.providerSessionId.trim()
|
|
162
|
+
: '';
|
|
163
|
+
if (currentProviderSessionId) return currentProviderSessionId;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return targetSessionId;
|
|
155
167
|
}
|
|
156
168
|
|
|
157
169
|
function getInteractionId(args: any): string | undefined {
|
|
@@ -221,6 +233,116 @@ function normalizeReadChatMessages(payload: Record<string, any>): ChatMessage[]
|
|
|
221
233
|
return normalizeChatMessages(messages);
|
|
222
234
|
}
|
|
223
235
|
|
|
236
|
+
function getMessageNewestReceivedAt(messages: Array<{ receivedAt?: unknown; timestamp?: unknown }>): number {
|
|
237
|
+
let newest = 0;
|
|
238
|
+
for (const message of messages) {
|
|
239
|
+
const receivedAt = Number(message?.receivedAt ?? message?.timestamp ?? 0);
|
|
240
|
+
if (Number.isFinite(receivedAt) && receivedAt > newest) newest = receivedAt;
|
|
241
|
+
}
|
|
242
|
+
return newest;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function buildCliMessageSourceProvenance(args: {
|
|
246
|
+
selected: 'native-history' | 'pty-parser';
|
|
247
|
+
provider: string;
|
|
248
|
+
nativeHandle?: string;
|
|
249
|
+
fallbackReason?: string;
|
|
250
|
+
nativeSource?: string;
|
|
251
|
+
sourcePath?: string;
|
|
252
|
+
sourceMtimeMs?: number;
|
|
253
|
+
nativeMessages?: ChatMessage[];
|
|
254
|
+
ptyMessages?: ChatMessage[];
|
|
255
|
+
returnedMessages?: ChatMessage[];
|
|
256
|
+
safeMapping?: boolean;
|
|
257
|
+
freshEnough?: boolean;
|
|
258
|
+
ptyStatusApprovalOnly?: boolean;
|
|
259
|
+
}): Record<string, unknown> {
|
|
260
|
+
const sourceMtimeMs = Number(args.sourceMtimeMs || 0);
|
|
261
|
+
const sourceMtimeAgeMs = sourceMtimeMs > 0 ? Math.max(0, Date.now() - sourceMtimeMs) : undefined;
|
|
262
|
+
const nativeMessages = args.nativeMessages || [];
|
|
263
|
+
const ptyMessages = args.ptyMessages || [];
|
|
264
|
+
const returnedMessages = args.returnedMessages || [];
|
|
265
|
+
return {
|
|
266
|
+
selected: args.selected,
|
|
267
|
+
provider: args.provider,
|
|
268
|
+
providerType: args.provider,
|
|
269
|
+
...(args.nativeHandle ? { nativeHandle: args.nativeHandle } : {}),
|
|
270
|
+
...(args.nativeHandle ? { nativeSessionId: args.nativeHandle } : {}),
|
|
271
|
+
...(args.fallbackReason ? { fallbackReason: args.fallbackReason } : {}),
|
|
272
|
+
...(args.nativeSource ? { nativeSource: args.nativeSource } : {}),
|
|
273
|
+
...(args.sourcePath ? { sourcePath: args.sourcePath } : {}),
|
|
274
|
+
ptyStatusApprovalOnly: args.ptyStatusApprovalOnly === true,
|
|
275
|
+
staleness: {
|
|
276
|
+
sourceMtimeMs: sourceMtimeMs || undefined,
|
|
277
|
+
sourceMtimeAgeMs,
|
|
278
|
+
nativeNewestMessageAt: getMessageNewestReceivedAt(nativeMessages),
|
|
279
|
+
ptyNewestMessageAt: getMessageNewestReceivedAt(ptyMessages),
|
|
280
|
+
freshEnough: args.freshEnough === true,
|
|
281
|
+
},
|
|
282
|
+
coverage: {
|
|
283
|
+
nativeMessageCount: nativeMessages.length,
|
|
284
|
+
ptyMessageCount: ptyMessages.length,
|
|
285
|
+
returnedMessageCount: returnedMessages.length,
|
|
286
|
+
safeMapping: args.safeMapping === true,
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function buildNativeHistoryFallbackReason(args: {
|
|
292
|
+
providerType: string;
|
|
293
|
+
nativeSource?: string;
|
|
294
|
+
nativeMessageCount: number;
|
|
295
|
+
safeMapping: boolean;
|
|
296
|
+
freshEnough: boolean;
|
|
297
|
+
}): string {
|
|
298
|
+
if (!supportsCliNativeTranscript(args.providerType)) return 'provider_native_transcript_not_supported';
|
|
299
|
+
if (args.nativeSource === 'native-unavailable') return 'native_history_unavailable';
|
|
300
|
+
if (args.nativeSource && args.nativeSource !== 'provider-native') return `native_history_source_${args.nativeSource}`;
|
|
301
|
+
if (args.nativeMessageCount <= 0) return 'native_history_empty';
|
|
302
|
+
if (!args.safeMapping) return 'native_history_not_safely_mapped';
|
|
303
|
+
if (!args.freshEnough) return 'native_history_stale';
|
|
304
|
+
return 'native_history_not_selected';
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function supportsCliNativeTranscript(providerType: string): boolean {
|
|
308
|
+
return CLI_NATIVE_TRANSCRIPT_PROVIDERS.has(providerType);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function hasSafeNativeHistoryMapping(args: {
|
|
312
|
+
historySessionId?: string;
|
|
313
|
+
providerSessionId?: string;
|
|
314
|
+
workspace?: string;
|
|
315
|
+
nativeMessages: ChatMessage[];
|
|
316
|
+
}): boolean {
|
|
317
|
+
const explicitSessionId = String(args.historySessionId || args.providerSessionId || '').trim();
|
|
318
|
+
if (explicitSessionId) {
|
|
319
|
+
const messageSessionIds = args.nativeMessages
|
|
320
|
+
.map((message: any) => typeof message?.historySessionId === 'string' ? message.historySessionId.trim() : '')
|
|
321
|
+
.filter(Boolean);
|
|
322
|
+
if (messageSessionIds.length === 0) return true;
|
|
323
|
+
return messageSessionIds.some((id) => id === explicitSessionId);
|
|
324
|
+
}
|
|
325
|
+
const workspace = String(args.workspace || '').trim();
|
|
326
|
+
if (!workspace) return false;
|
|
327
|
+
return args.nativeMessages.some((message: any) => String(message?.workspace || '').trim() === workspace);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function isNativeHistoryFreshEnough(args: {
|
|
331
|
+
sourceMtimeMs?: number;
|
|
332
|
+
nativeMessages: ChatMessage[];
|
|
333
|
+
ptyMessages: ChatMessage[];
|
|
334
|
+
}): boolean {
|
|
335
|
+
const nativeNewest = getMessageNewestReceivedAt(args.nativeMessages);
|
|
336
|
+
const ptyNewest = getMessageNewestReceivedAt(args.ptyMessages);
|
|
337
|
+
if (nativeNewest > 0 && nativeNewest >= ptyNewest) return true;
|
|
338
|
+
const sourceMtimeMs = Number(args.sourceMtimeMs || 0);
|
|
339
|
+
if (sourceMtimeMs > 0 && Date.now() - sourceMtimeMs <= CLI_NATIVE_HISTORY_FRESH_MS) return true;
|
|
340
|
+
return ptyNewest === 0 && nativeNewest > 0;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function shouldPreserveReadChatPayloadField(key: string): boolean {
|
|
344
|
+
return key === 'messageSource' || key === 'transcriptProvenance';
|
|
345
|
+
}
|
|
224
346
|
|
|
225
347
|
function deriveHistoryDedupKey(message: ChatMessage & { _unitKey?: string; _turnKey?: string }): string | undefined {
|
|
226
348
|
const unitKey = typeof message._unitKey === 'string' ? message._unitKey.trim() : '';
|
|
@@ -281,7 +403,7 @@ function normalizeReadChatCommandStatus(status: unknown, activeModal: unknown):
|
|
|
281
403
|
}
|
|
282
404
|
switch (raw) {
|
|
283
405
|
case 'starting':
|
|
284
|
-
return hasNonEmptyModalButtons(activeModal) ? 'waiting_approval' : '
|
|
406
|
+
return hasNonEmptyModalButtons(activeModal) ? 'waiting_approval' : 'starting';
|
|
285
407
|
case 'stopped':
|
|
286
408
|
case 'disconnected':
|
|
287
409
|
case 'not_monitored':
|
|
@@ -304,7 +426,18 @@ function shouldTrustCliAdapterTerminalStatus(parsedStatus: unknown, activeModal:
|
|
|
304
426
|
return true;
|
|
305
427
|
}
|
|
306
428
|
|
|
307
|
-
function normalizeCliReadChatStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any): string {
|
|
429
|
+
function normalizeCliReadChatStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any, parsedMessages?: unknown[]): string {
|
|
430
|
+
const adapterRawStatus = typeof adapterStatus?.status === 'string' ? adapterStatus.status.trim() : '';
|
|
431
|
+
if (adapterRawStatus === 'starting'
|
|
432
|
+
&& isGeneratingLikeStatus(parsedStatus)
|
|
433
|
+
&& !hasNonEmptyModalButtons(activeModal)
|
|
434
|
+
&& Array.isArray(parsedMessages)
|
|
435
|
+
&& parsedMessages.length === 0
|
|
436
|
+
&& Array.isArray(adapterStatus?.messages)
|
|
437
|
+
&& adapterStatus.messages.length === 0
|
|
438
|
+
&& !(typeof adapter.isProcessing === 'function' && adapter.isProcessing())) {
|
|
439
|
+
return 'starting';
|
|
440
|
+
}
|
|
308
441
|
if (shouldTrustCliAdapterTerminalStatus(parsedStatus, activeModal, adapter, adapterStatus)) return 'idle';
|
|
309
442
|
return typeof parsedStatus === 'string' && parsedStatus.trim() ? parsedStatus : 'idle';
|
|
310
443
|
}
|
|
@@ -356,6 +489,7 @@ function buildReadChatCommandResult(payload: Record<string, any>, args: any): Co
|
|
|
356
489
|
return {
|
|
357
490
|
success: true,
|
|
358
491
|
...validatedPayload,
|
|
492
|
+
...Object.fromEntries(Object.entries(payload).filter(([key]) => shouldPreserveReadChatPayloadField(key))),
|
|
359
493
|
messages: sync.messages,
|
|
360
494
|
totalMessages: sync.totalMessages,
|
|
361
495
|
...(returnedDebugReadChat ? { debugReadChat: returnedDebugReadChat } : {}),
|
|
@@ -584,6 +718,8 @@ function buildChatDebugBundleSummary(bundle: Record<string, unknown>): Record<st
|
|
|
584
718
|
adapterStatus: debugReadChat.adapterStatus,
|
|
585
719
|
parsedStatus: debugReadChat.parsedStatus,
|
|
586
720
|
returnedStatus: debugReadChat.returnedStatus,
|
|
721
|
+
selectedMessageSource: debugReadChat.selectedMessageSource,
|
|
722
|
+
messageSource: debugReadChat.messageSource,
|
|
587
723
|
parsedMsgCount: debugReadChat.parsedMsgCount,
|
|
588
724
|
returnedMsgCount: debugReadChat.returnedMsgCount,
|
|
589
725
|
shouldPreferAdapterMessages: debugReadChat.shouldPreferAdapterMessages,
|
|
@@ -646,6 +782,8 @@ export async function handleGetChatDebugBundle(h: CommandHelpers, args: any): Pr
|
|
|
646
782
|
providerSessionId: readResult.providerSessionId,
|
|
647
783
|
transcriptAuthority: readResult.transcriptAuthority,
|
|
648
784
|
coverage: readResult.coverage,
|
|
785
|
+
messageSource: readResult.messageSource,
|
|
786
|
+
transcriptProvenance: readResult.transcriptProvenance,
|
|
649
787
|
activeModal: readResult.activeModal,
|
|
650
788
|
messagesTail: Array.isArray(readResult.messages) ? readResult.messages.slice(-20) : [],
|
|
651
789
|
debugReadChat: readResult.debugReadChat,
|
|
@@ -874,7 +1012,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
874
1012
|
? parsedRecord.coverage
|
|
875
1013
|
: undefined;
|
|
876
1014
|
const activeModal = parsedRecord.activeModal ?? parsedRecord.modal ?? null;
|
|
877
|
-
const returnedStatus = normalizeCliReadChatStatus(parsedRecord.status, activeModal, adapter, adapterStatus);
|
|
1015
|
+
const returnedStatus = normalizeCliReadChatStatus(parsedRecord.status, activeModal, adapter, adapterStatus, parsedRecord.messages);
|
|
878
1016
|
const runtimeMessageMerger = getTargetInstance(h, args) as RuntimeChatMessageMerger | null;
|
|
879
1017
|
const parsedMessages = finalizeStreamingMessagesWhenIdle(parsedRecord.messages as ChatMessage[], returnedStatus);
|
|
880
1018
|
const returnedMessages = runtimeMessageMerger?.category === 'cli'
|
|
@@ -882,25 +1020,146 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
882
1020
|
&& typeof runtimeMessageMerger.mergeRuntimeChatMessages === 'function'
|
|
883
1021
|
? runtimeMessageMerger.mergeRuntimeChatMessages(parsedMessages)
|
|
884
1022
|
: parsedMessages;
|
|
1023
|
+
const providerType = provider?.type || adapter.cliType;
|
|
1024
|
+
let selectedMessages = returnedMessages;
|
|
1025
|
+
let selectedTitle = title;
|
|
1026
|
+
let selectedProviderSessionId = providerSessionId;
|
|
1027
|
+
let selectedTranscriptAuthority = transcriptAuthority;
|
|
1028
|
+
let selectedCoverage = coverage;
|
|
1029
|
+
let messageSource = buildCliMessageSourceProvenance({
|
|
1030
|
+
selected: 'pty-parser',
|
|
1031
|
+
provider: adapter.cliType,
|
|
1032
|
+
fallbackReason: supportsCliNativeTranscript(providerType) ? 'native_history_not_checked' : 'provider_native_transcript_not_supported',
|
|
1033
|
+
ptyMessages: returnedMessages,
|
|
1034
|
+
returnedMessages,
|
|
1035
|
+
ptyStatusApprovalOnly: false,
|
|
1036
|
+
});
|
|
1037
|
+
|
|
1038
|
+
if (supportsCliNativeTranscript(providerType)) {
|
|
1039
|
+
const agentStr = provider?.type || args?.agentType || getCurrentProviderType(h, adapter.cliType);
|
|
1040
|
+
const workspace = typeof args?.workspace === 'string'
|
|
1041
|
+
? args.workspace
|
|
1042
|
+
: typeof (h.currentSession as any)?.workspace === 'string'
|
|
1043
|
+
? (h.currentSession as any).workspace
|
|
1044
|
+
: typeof adapter.workingDir === 'string'
|
|
1045
|
+
? adapter.workingDir
|
|
1046
|
+
: undefined;
|
|
1047
|
+
const nativeHistoryLimit = Math.max(
|
|
1048
|
+
normalizeReadChatTailLimit(args) || 0,
|
|
1049
|
+
returnedMessages.length,
|
|
1050
|
+
200,
|
|
1051
|
+
);
|
|
1052
|
+
let nativeHistory: ReturnType<typeof readProviderChatHistory> | null = null;
|
|
1053
|
+
try {
|
|
1054
|
+
nativeHistory = readProviderChatHistory(agentStr, {
|
|
1055
|
+
canonicalHistory: provider?.canonicalHistory,
|
|
1056
|
+
historySessionId,
|
|
1057
|
+
workspace,
|
|
1058
|
+
offset: 0,
|
|
1059
|
+
limit: nativeHistoryLimit,
|
|
1060
|
+
excludeRecentCount: 0,
|
|
1061
|
+
historyBehavior: provider?.historyBehavior,
|
|
1062
|
+
scripts: provider?.scripts as any,
|
|
1063
|
+
});
|
|
1064
|
+
} catch (error: any) {
|
|
1065
|
+
const fallbackReason = `native_history_error:${error?.message || String(error)}`;
|
|
1066
|
+
messageSource = buildCliMessageSourceProvenance({
|
|
1067
|
+
selected: 'pty-parser',
|
|
1068
|
+
provider: adapter.cliType,
|
|
1069
|
+
fallbackReason,
|
|
1070
|
+
ptyMessages: returnedMessages,
|
|
1071
|
+
returnedMessages,
|
|
1072
|
+
ptyStatusApprovalOnly: false,
|
|
1073
|
+
});
|
|
1074
|
+
nativeHistory = null;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
if (nativeHistory) {
|
|
1078
|
+
const nativeMessages = Array.isArray((nativeHistory as any).messages)
|
|
1079
|
+
? normalizeChatMessages((nativeHistory as any).messages as ChatMessage[])
|
|
1080
|
+
: [];
|
|
1081
|
+
const historyProviderSessionId = typeof (nativeHistory as any)?.providerSessionId === 'string'
|
|
1082
|
+
? (nativeHistory as any).providerSessionId
|
|
1083
|
+
: historySessionId;
|
|
1084
|
+
const safeMapping = hasSafeNativeHistoryMapping({
|
|
1085
|
+
historySessionId,
|
|
1086
|
+
providerSessionId,
|
|
1087
|
+
workspace,
|
|
1088
|
+
nativeMessages,
|
|
1089
|
+
});
|
|
1090
|
+
const freshEnough = isNativeHistoryFreshEnough({
|
|
1091
|
+
sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
|
|
1092
|
+
nativeMessages,
|
|
1093
|
+
ptyMessages: returnedMessages,
|
|
1094
|
+
});
|
|
1095
|
+
if ((nativeHistory as any).source === 'provider-native' && nativeMessages.length > 0 && safeMapping && freshEnough) {
|
|
1096
|
+
selectedMessages = finalizeStreamingMessagesWhenIdle(nativeMessages, returnedStatus);
|
|
1097
|
+
selectedProviderSessionId = historyProviderSessionId || providerSessionId;
|
|
1098
|
+
selectedTranscriptAuthority = 'provider';
|
|
1099
|
+
selectedCoverage = (nativeHistory as any).hasMore ? 'tail' : 'full';
|
|
1100
|
+
messageSource = buildCliMessageSourceProvenance({
|
|
1101
|
+
selected: 'native-history',
|
|
1102
|
+
provider: adapter.cliType,
|
|
1103
|
+
nativeHandle: selectedProviderSessionId || historySessionId,
|
|
1104
|
+
nativeSource: (nativeHistory as any).source,
|
|
1105
|
+
sourcePath: (nativeHistory as any).sourcePath,
|
|
1106
|
+
sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
|
|
1107
|
+
nativeMessages,
|
|
1108
|
+
ptyMessages: returnedMessages,
|
|
1109
|
+
returnedMessages: selectedMessages,
|
|
1110
|
+
safeMapping,
|
|
1111
|
+
freshEnough,
|
|
1112
|
+
ptyStatusApprovalOnly: true,
|
|
1113
|
+
});
|
|
1114
|
+
} else {
|
|
1115
|
+
const fallbackReason = buildNativeHistoryFallbackReason({
|
|
1116
|
+
providerType,
|
|
1117
|
+
nativeSource: (nativeHistory as any).source,
|
|
1118
|
+
nativeMessageCount: nativeMessages.length,
|
|
1119
|
+
safeMapping,
|
|
1120
|
+
freshEnough,
|
|
1121
|
+
});
|
|
1122
|
+
messageSource = buildCliMessageSourceProvenance({
|
|
1123
|
+
selected: 'pty-parser',
|
|
1124
|
+
provider: adapter.cliType,
|
|
1125
|
+
nativeHandle: historyProviderSessionId || historySessionId,
|
|
1126
|
+
fallbackReason,
|
|
1127
|
+
nativeSource: (nativeHistory as any).source,
|
|
1128
|
+
sourcePath: (nativeHistory as any).sourcePath,
|
|
1129
|
+
sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
|
|
1130
|
+
nativeMessages,
|
|
1131
|
+
ptyMessages: returnedMessages,
|
|
1132
|
+
returnedMessages,
|
|
1133
|
+
safeMapping,
|
|
1134
|
+
freshEnough,
|
|
1135
|
+
ptyStatusApprovalOnly: false,
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
885
1140
|
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
1141
|
return buildReadChatCommandResult({
|
|
887
|
-
messages:
|
|
1142
|
+
messages: selectedMessages,
|
|
888
1143
|
status: returnedStatus,
|
|
889
1144
|
activeModal,
|
|
1145
|
+
messageSource,
|
|
1146
|
+
transcriptProvenance: messageSource,
|
|
890
1147
|
debugReadChat: {
|
|
891
1148
|
provider: adapter.cliType,
|
|
892
1149
|
targetSessionId: String(args?.targetSessionId || ''),
|
|
893
1150
|
adapterStatus: String(adapterStatus.status || ''),
|
|
894
1151
|
parsedStatus: String(parsedRecord.status || ''),
|
|
895
1152
|
returnedStatus: String(returnedStatus || ''),
|
|
896
|
-
|
|
1153
|
+
selectedMessageSource: (messageSource as any).selected,
|
|
1154
|
+
messageSource,
|
|
1155
|
+
shouldPreferAdapterMessages: supportsCliNativeTranscript(providerType) && (messageSource as any).selected !== 'native-history',
|
|
897
1156
|
parsedMsgCount: parsedRecord.messages.length,
|
|
898
|
-
returnedMsgCount:
|
|
1157
|
+
returnedMsgCount: selectedMessages.length,
|
|
899
1158
|
},
|
|
900
|
-
...(
|
|
901
|
-
...(
|
|
902
|
-
...(
|
|
903
|
-
...(
|
|
1159
|
+
...(selectedTitle ? { title: selectedTitle } : {}),
|
|
1160
|
+
...(selectedProviderSessionId ? { providerSessionId: selectedProviderSessionId } : {}),
|
|
1161
|
+
...(selectedTranscriptAuthority ? { transcriptAuthority: selectedTranscriptAuthority } : {}),
|
|
1162
|
+
...(selectedCoverage ? { coverage: selectedCoverage } : {}),
|
|
904
1163
|
}, args);
|
|
905
1164
|
}
|
|
906
1165
|
const historyLimit = normalizeReadChatTailLimit(args);
|
|
@@ -924,9 +1183,48 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
924
1183
|
const historyProviderSessionId = typeof (history as any)?.providerSessionId === 'string'
|
|
925
1184
|
? (history as any).providerSessionId
|
|
926
1185
|
: historySessionId;
|
|
1186
|
+
const historyMessages = Array.isArray((history as any)?.messages)
|
|
1187
|
+
? normalizeChatMessages((history as any).messages as ChatMessage[])
|
|
1188
|
+
: [];
|
|
1189
|
+
const safeMapping = supportsCliNativeTranscript(agentStr)
|
|
1190
|
+
? hasSafeNativeHistoryMapping({
|
|
1191
|
+
historySessionId,
|
|
1192
|
+
providerSessionId: historyProviderSessionId,
|
|
1193
|
+
workspace,
|
|
1194
|
+
nativeMessages: historyMessages,
|
|
1195
|
+
})
|
|
1196
|
+
: false;
|
|
1197
|
+
const nativeSelected = supportsCliNativeTranscript(agentStr)
|
|
1198
|
+
&& (history as any).source === 'provider-native'
|
|
1199
|
+
&& historyMessages.length > 0
|
|
1200
|
+
&& safeMapping;
|
|
1201
|
+
const messageSource = buildCliMessageSourceProvenance({
|
|
1202
|
+
selected: nativeSelected ? 'native-history' : 'pty-parser',
|
|
1203
|
+
provider: agentStr,
|
|
1204
|
+
nativeHandle: historyProviderSessionId || historySessionId,
|
|
1205
|
+
fallbackReason: nativeSelected
|
|
1206
|
+
? undefined
|
|
1207
|
+
: buildNativeHistoryFallbackReason({
|
|
1208
|
+
providerType: agentStr,
|
|
1209
|
+
nativeSource: (history as any).source,
|
|
1210
|
+
nativeMessageCount: historyMessages.length,
|
|
1211
|
+
safeMapping,
|
|
1212
|
+
freshEnough: true,
|
|
1213
|
+
}),
|
|
1214
|
+
nativeSource: (history as any).source,
|
|
1215
|
+
sourcePath: (history as any).sourcePath,
|
|
1216
|
+
sourceMtimeMs: (history as any).sourceMtimeMs,
|
|
1217
|
+
nativeMessages: historyMessages,
|
|
1218
|
+
returnedMessages: historyMessages,
|
|
1219
|
+
safeMapping,
|
|
1220
|
+
freshEnough: true,
|
|
1221
|
+
ptyStatusApprovalOnly: false,
|
|
1222
|
+
});
|
|
927
1223
|
return buildReadChatCommandResult({
|
|
928
|
-
messages:
|
|
1224
|
+
messages: historyMessages,
|
|
929
1225
|
status: 'idle',
|
|
1226
|
+
messageSource,
|
|
1227
|
+
transcriptProvenance: messageSource,
|
|
930
1228
|
...(typeof (history as any)?.title === 'string' ? { title: (history as any).title } : {}),
|
|
931
1229
|
...(historyProviderSessionId ? { providerSessionId: historyProviderSessionId } : {}),
|
|
932
1230
|
...(((provider?.historyBehavior as any)?.transcriptAuthority === 'provider' || (provider?.historyBehavior as any)?.transcriptAuthority === 'daemon')
|