@adhdev/daemon-core 0.9.48 → 0.9.50
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-adapter-types.d.ts +1 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +1 -0
- package/dist/commands/chat-commands.d.ts +9 -0
- package/dist/commands/upgrade-helper.d.ts +9 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +380 -23
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +378 -23
- package/dist/index.mjs.map +1 -1
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +1 -1
- package/src/cli-adapter-types.ts +1 -0
- package/src/cli-adapters/provider-cli-adapter.ts +82 -0
- package/src/cli-adapters/provider-cli-shared.ts +6 -1
- package/src/commands/chat-commands.ts +271 -2
- package/src/commands/cli-manager.ts +4 -1
- package/src/commands/handler.ts +2 -0
- package/src/commands/router.ts +17 -8
- package/src/commands/upgrade-helper.ts +37 -15
- package/src/detection/cli-detector.ts +5 -1
- package/src/index.d.ts +2 -2
- package/src/index.ts +10 -7
- package/src/launch.ts +2 -2
- package/src/providers/acp-provider-instance.ts +1 -0
package/package.json
CHANGED
package/src/cli-adapter-types.ts
CHANGED
|
@@ -41,6 +41,7 @@ export interface CliAdapter {
|
|
|
41
41
|
sendMessage(text: string): Promise<void>;
|
|
42
42
|
getStatus(): CliAdapterStatus;
|
|
43
43
|
getScriptParsedStatus?(): unknown;
|
|
44
|
+
getDebugSnapshot?(): unknown;
|
|
44
45
|
invokeScript?(scriptName: string, args?: Record<string, unknown>): Promise<unknown>;
|
|
45
46
|
getPartialResponse(): string;
|
|
46
47
|
saveAndStop?(): Promise<void>;
|
|
@@ -2545,6 +2545,88 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2545
2545
|
return this.responseBuffer;
|
|
2546
2546
|
}
|
|
2547
2547
|
|
|
2548
|
+
getDebugSnapshot(): Record<string, unknown> {
|
|
2549
|
+
const screenText = this.readTerminalScreenText();
|
|
2550
|
+
const parsedResult = this.parsedStatusCache?.result && typeof this.parsedStatusCache.result === 'object'
|
|
2551
|
+
? this.parsedStatusCache.result as Record<string, any>
|
|
2552
|
+
: null;
|
|
2553
|
+
return {
|
|
2554
|
+
cliType: this.cliType,
|
|
2555
|
+
cliName: this.cliName,
|
|
2556
|
+
workingDir: this.workingDir,
|
|
2557
|
+
currentStatus: this.currentStatus,
|
|
2558
|
+
ready: this.ready,
|
|
2559
|
+
isWaitingForResponse: this.isWaitingForResponse,
|
|
2560
|
+
activeModal: this.activeModal,
|
|
2561
|
+
parseErrorMessage: this.parseErrorMessage,
|
|
2562
|
+
messageCounts: {
|
|
2563
|
+
committed: this.committedMessages.length,
|
|
2564
|
+
structured: this.structuredMessages.length,
|
|
2565
|
+
visible: this.messages.length,
|
|
2566
|
+
parsedCache: Array.isArray(parsedResult?.messages) ? parsedResult.messages.length : undefined,
|
|
2567
|
+
},
|
|
2568
|
+
buffers: {
|
|
2569
|
+
accumulatedLength: this.accumulatedBuffer.length,
|
|
2570
|
+
accumulatedRawLength: this.accumulatedRawBuffer.length,
|
|
2571
|
+
recentOutputLength: this.recentOutputBuffer.length,
|
|
2572
|
+
responseLength: this.responseBuffer.length,
|
|
2573
|
+
startupLength: this.startupBuffer.length,
|
|
2574
|
+
accumulatedTail: this.accumulatedBuffer.slice(-24_000),
|
|
2575
|
+
accumulatedRawTail: this.accumulatedRawBuffer.slice(-24_000),
|
|
2576
|
+
recentOutputTail: this.recentOutputBuffer.slice(-12_000),
|
|
2577
|
+
responseTail: this.responseBuffer.slice(-12_000),
|
|
2578
|
+
},
|
|
2579
|
+
terminal: {
|
|
2580
|
+
screenText,
|
|
2581
|
+
lastScreenSnapshot: this.lastScreenSnapshot,
|
|
2582
|
+
lastScreenText: this.lastScreenText,
|
|
2583
|
+
lastOutputAt: this.lastOutputAt,
|
|
2584
|
+
lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
|
|
2585
|
+
lastScreenChangeAt: this.lastScreenChangeAt,
|
|
2586
|
+
lastScreenSnapshotReadAt: this.lastScreenSnapshotReadAt,
|
|
2587
|
+
},
|
|
2588
|
+
parser: {
|
|
2589
|
+
scriptNames: listCliScriptNames(this.cliScripts),
|
|
2590
|
+
traceSessionId: this.traceSessionId,
|
|
2591
|
+
traceSeq: this.traceSeq,
|
|
2592
|
+
currentTurnScope: this.currentTurnScope,
|
|
2593
|
+
parsedStatusCache: parsedResult
|
|
2594
|
+
? {
|
|
2595
|
+
id: parsedResult.id,
|
|
2596
|
+
status: parsedResult.status,
|
|
2597
|
+
title: parsedResult.title,
|
|
2598
|
+
providerSessionId: parsedResult.providerSessionId,
|
|
2599
|
+
transcriptAuthority: parsedResult.transcriptAuthority,
|
|
2600
|
+
coverage: parsedResult.coverage,
|
|
2601
|
+
messageCount: Array.isArray(parsedResult.messages) ? parsedResult.messages.length : undefined,
|
|
2602
|
+
activeModal: parsedResult.activeModal,
|
|
2603
|
+
}
|
|
2604
|
+
: null,
|
|
2605
|
+
pendingScriptStatus: this.pendingScriptStatus,
|
|
2606
|
+
pendingScriptStatusSince: this.pendingScriptStatusSince,
|
|
2607
|
+
},
|
|
2608
|
+
runtimeMetadata: this.getRuntimeMetadata(),
|
|
2609
|
+
statusHistory: this.statusHistory.slice(-80),
|
|
2610
|
+
traceEntries: this.traceEntries.slice(-120),
|
|
2611
|
+
timing: {
|
|
2612
|
+
spawnAt: this.spawnAt,
|
|
2613
|
+
startupFirstOutputAt: this.startupFirstOutputAt,
|
|
2614
|
+
submitPendingUntil: this.submitPendingUntil,
|
|
2615
|
+
responseSettleIgnoreUntil: this.responseSettleIgnoreUntil,
|
|
2616
|
+
responseEpoch: this.responseEpoch,
|
|
2617
|
+
resizeSuppressUntil: this.resizeSuppressUntil,
|
|
2618
|
+
lastApprovalResolvedAt: this.lastApprovalResolvedAt,
|
|
2619
|
+
committedMessagesChangedAt: this.committedMessagesChangedAt,
|
|
2620
|
+
},
|
|
2621
|
+
finish: {
|
|
2622
|
+
idleFinishCandidate: this.idleFinishCandidate,
|
|
2623
|
+
finishRetryCount: this.finishRetryCount,
|
|
2624
|
+
submitRetryUsed: this.submitRetryUsed,
|
|
2625
|
+
submitRetryPromptSnippet: this.submitRetryPromptSnippet,
|
|
2626
|
+
},
|
|
2627
|
+
};
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2548
2630
|
getRuntimeMetadata(): PtyRuntimeMetadata | null {
|
|
2549
2631
|
if (!this.ptyProcess || typeof this.ptyProcess.getMetadata !== 'function') return null;
|
|
2550
2632
|
return this.ptyProcess.getMetadata();
|
|
@@ -277,7 +277,12 @@ export function findBinary(name: string): string {
|
|
|
277
277
|
const isWin = os.platform() === 'win32';
|
|
278
278
|
try {
|
|
279
279
|
const cmd = isWin ? `where ${trimmed}` : `which ${trimmed}`;
|
|
280
|
-
return execSync(cmd, {
|
|
280
|
+
return execSync(cmd, {
|
|
281
|
+
encoding: 'utf-8',
|
|
282
|
+
timeout: 5000,
|
|
283
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
284
|
+
...(isWin ? { windowsHide: true } : {}),
|
|
285
|
+
}).trim().split('\n')[0].trim();
|
|
281
286
|
} catch {
|
|
282
287
|
return isWin ? `${trimmed}.cmd` : trimmed;
|
|
283
288
|
}
|
|
@@ -10,8 +10,8 @@ import { assertProviderSupportsDeclaredInput, assertTextOnlyInput } from '../pro
|
|
|
10
10
|
import { validateReadChatResultPayload } from '../providers/read-chat-contract.js';
|
|
11
11
|
import type { ProviderInstance } from '../providers/provider-instance.js';
|
|
12
12
|
import { readProviderChatHistory } from '../config/chat-history.js';
|
|
13
|
-
import { LOG } from '../logging/logger.js';
|
|
14
|
-
import { recordDebugTrace } from '../logging/debug-trace.js';
|
|
13
|
+
import { LOG, getRecentLogs } from '../logging/logger.js';
|
|
14
|
+
import { getRecentDebugTrace, recordDebugTrace } from '../logging/debug-trace.js';
|
|
15
15
|
import { buildChatMessageSignature } from '../chat/chat-signatures.js';
|
|
16
16
|
import type { ChatMessage } from '../types.js';
|
|
17
17
|
import type { ReadChatCursor, ReadChatSyncMode, SessionTransport } from '../shared-types.js';
|
|
@@ -489,6 +489,275 @@ function buildReadChatCommandResult(payload: Record<string, any>, args: any): Co
|
|
|
489
489
|
};
|
|
490
490
|
}
|
|
491
491
|
|
|
492
|
+
|
|
493
|
+
interface DebugSanitizeOptions {
|
|
494
|
+
maxDepth?: number;
|
|
495
|
+
maxArrayLength?: number;
|
|
496
|
+
maxObjectKeys?: number;
|
|
497
|
+
maxStringLength?: number;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const DEFAULT_DEBUG_SANITIZE_OPTIONS: Required<DebugSanitizeOptions> = {
|
|
501
|
+
maxDepth: 8,
|
|
502
|
+
maxArrayLength: 80,
|
|
503
|
+
maxObjectKeys: 120,
|
|
504
|
+
maxStringLength: 16_000,
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
const SECRET_KEY_PATTERN = /(?:token|secret|password|passwd|authorization|cookie|api[_-]?key|access[_-]?key|refresh[_-]?token|client[_-]?secret|private[_-]?key)/i;
|
|
508
|
+
|
|
509
|
+
function truncateDebugString(value: string, maxLength: number): string {
|
|
510
|
+
if (value.length <= maxLength) return value;
|
|
511
|
+
return `${value.slice(0, maxLength)}…[truncated ${value.length - maxLength} chars]`;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function redactDebugSecrets(value: string): string {
|
|
515
|
+
return value
|
|
516
|
+
.replace(/(Authorization\s*:\s*Bearer\s+)[^\s'"`]+/gi, '$1[REDACTED:bearer]')
|
|
517
|
+
.replace(/(Bearer\s+)[A-Za-z0-9._~+\/-]{16,}=*/gi, '$1[REDACTED:bearer]')
|
|
518
|
+
.replace(/\b(?:gh[pousr]|github_pat)_[A-Za-z0-9_]{20,}\b/g, '[REDACTED:github-token]')
|
|
519
|
+
.replace(/\bsk-[A-Za-z0-9_-]{16,}\b/g, '[REDACTED:api-key]')
|
|
520
|
+
.replace(/\bxox[baprs]-[A-Za-z0-9-]{12,}\b/g, '[REDACTED:slack-token]')
|
|
521
|
+
.replace(/\b(?:adk|adm)_[A-Za-z0-9_-]{16,}\b/g, '[REDACTED:adhdev-token]')
|
|
522
|
+
.replace(/((?:api[_-]?key|token|secret|password|passwd|client[_-]?secret)\s*[:=]\s*)[^\s,'"`}&]+/gi, '$1[REDACTED:secret]')
|
|
523
|
+
.replace(/([?&](?:api[_-]?key|token|secret|password|client_secret)=)[^&#\s]+/gi, '$1[REDACTED:secret]');
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
export function sanitizeDebugBundleValue(
|
|
527
|
+
value: unknown,
|
|
528
|
+
options: DebugSanitizeOptions = {},
|
|
529
|
+
depth = 0,
|
|
530
|
+
keyHint = '',
|
|
531
|
+
): unknown {
|
|
532
|
+
const normalizedOptions = { ...DEFAULT_DEBUG_SANITIZE_OPTIONS, ...options };
|
|
533
|
+
if (value === null || value === undefined) return value;
|
|
534
|
+
if (typeof value === 'number' || typeof value === 'boolean') return value;
|
|
535
|
+
if (typeof value === 'bigint') return String(value);
|
|
536
|
+
if (typeof value === 'string') {
|
|
537
|
+
if (SECRET_KEY_PATTERN.test(keyHint) && value.trim()) return '[REDACTED:secret-field]';
|
|
538
|
+
return truncateDebugString(redactDebugSecrets(value), normalizedOptions.maxStringLength);
|
|
539
|
+
}
|
|
540
|
+
if (typeof value === 'function') return `[Function ${value.name || 'anonymous'}]`;
|
|
541
|
+
if (typeof value !== 'object') return String(value);
|
|
542
|
+
if (depth >= normalizedOptions.maxDepth) return '[MaxDepth]';
|
|
543
|
+
|
|
544
|
+
if (Array.isArray(value)) {
|
|
545
|
+
const items = value
|
|
546
|
+
.slice(0, normalizedOptions.maxArrayLength)
|
|
547
|
+
.map((item) => sanitizeDebugBundleValue(item, normalizedOptions, depth + 1, keyHint));
|
|
548
|
+
if (value.length > normalizedOptions.maxArrayLength) {
|
|
549
|
+
items.push(`[truncated ${value.length - normalizedOptions.maxArrayLength} items]`);
|
|
550
|
+
}
|
|
551
|
+
return items;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const record = value as Record<string, unknown>;
|
|
555
|
+
const result: Record<string, unknown> = {};
|
|
556
|
+
const entries = Object.entries(record).slice(0, normalizedOptions.maxObjectKeys);
|
|
557
|
+
for (const [key, item] of entries) {
|
|
558
|
+
result[key] = sanitizeDebugBundleValue(item, normalizedOptions, depth + 1, key);
|
|
559
|
+
}
|
|
560
|
+
const remaining = Object.keys(record).length - entries.length;
|
|
561
|
+
if (remaining > 0) result.__truncatedKeys = remaining;
|
|
562
|
+
return result;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function summarizeProviderForDebug(provider: ProviderModule | undefined): Record<string, unknown> | null {
|
|
566
|
+
if (!provider) return null;
|
|
567
|
+
const scripts = provider.scripts && typeof provider.scripts === 'object'
|
|
568
|
+
? Object.keys(provider.scripts)
|
|
569
|
+
: [];
|
|
570
|
+
const controls = Array.isArray((provider as any).controls)
|
|
571
|
+
? (provider as any).controls.map((control: any) => ({
|
|
572
|
+
id: control?.id,
|
|
573
|
+
label: control?.label,
|
|
574
|
+
type: control?.type,
|
|
575
|
+
settingKey: control?.settingKey,
|
|
576
|
+
invokeScript: control?.invokeScript,
|
|
577
|
+
listScript: control?.listScript,
|
|
578
|
+
location: control?.location,
|
|
579
|
+
}))
|
|
580
|
+
: [];
|
|
581
|
+
return {
|
|
582
|
+
type: provider.type,
|
|
583
|
+
name: provider.name,
|
|
584
|
+
category: provider.category,
|
|
585
|
+
version: (provider as any).version,
|
|
586
|
+
canonicalHistory: provider.canonicalHistory,
|
|
587
|
+
historyBehavior: provider.historyBehavior,
|
|
588
|
+
webviewMatchText: provider.webviewMatchText,
|
|
589
|
+
scriptNames: scripts,
|
|
590
|
+
controls,
|
|
591
|
+
resume: provider.resume,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function summarizeSessionForDebug(session: any): Record<string, unknown> | null {
|
|
596
|
+
if (!session || typeof session !== 'object') return null;
|
|
597
|
+
return {
|
|
598
|
+
sessionId: session.sessionId,
|
|
599
|
+
instanceKey: session.instanceKey,
|
|
600
|
+
adapterKey: session.adapterKey,
|
|
601
|
+
providerType: session.providerType,
|
|
602
|
+
providerName: session.providerName,
|
|
603
|
+
transport: session.transport,
|
|
604
|
+
kind: session.kind,
|
|
605
|
+
cdpManagerKey: session.cdpManagerKey,
|
|
606
|
+
parentSessionId: session.parentSessionId,
|
|
607
|
+
providerSessionId: session.providerSessionId,
|
|
608
|
+
workspace: session.workspace,
|
|
609
|
+
title: session.title,
|
|
610
|
+
status: session.status,
|
|
611
|
+
mode: session.mode,
|
|
612
|
+
capabilities: session.capabilities,
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function summarizeStateForDebug(state: any): Record<string, unknown> | null {
|
|
617
|
+
if (!state || typeof state !== 'object') return null;
|
|
618
|
+
const activeChat = state.activeChat && typeof state.activeChat === 'object' ? state.activeChat : null;
|
|
619
|
+
return {
|
|
620
|
+
type: state.type,
|
|
621
|
+
name: state.name,
|
|
622
|
+
category: state.category,
|
|
623
|
+
status: state.status,
|
|
624
|
+
instanceId: state.instanceId,
|
|
625
|
+
providerSessionId: state.providerSessionId,
|
|
626
|
+
title: state.title,
|
|
627
|
+
transport: state.transport,
|
|
628
|
+
mode: state.mode,
|
|
629
|
+
workspace: state.workspace,
|
|
630
|
+
runtime: state.runtime,
|
|
631
|
+
errorMessage: state.errorMessage,
|
|
632
|
+
errorReason: state.errorReason,
|
|
633
|
+
activeChat: activeChat ? {
|
|
634
|
+
status: activeChat.status,
|
|
635
|
+
title: activeChat.title,
|
|
636
|
+
messageCount: Array.isArray(activeChat.messages) ? activeChat.messages.length : undefined,
|
|
637
|
+
activeModal: activeChat.activeModal,
|
|
638
|
+
messagesTail: Array.isArray(activeChat.messages) ? activeChat.messages.slice(-10) : undefined,
|
|
639
|
+
} : null,
|
|
640
|
+
controlValues: state.controlValues,
|
|
641
|
+
summaryMetadata: state.summaryMetadata,
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function buildDebugBundleText(bundle: Record<string, unknown>): string {
|
|
646
|
+
return [
|
|
647
|
+
'# ADHDev Chat Debug Bundle',
|
|
648
|
+
'',
|
|
649
|
+
'```json',
|
|
650
|
+
JSON.stringify(bundle, null, 2),
|
|
651
|
+
'```',
|
|
652
|
+
].join('\n');
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
export async function handleGetChatDebugBundle(h: CommandHelpers, args: any): Promise<CommandResult> {
|
|
656
|
+
const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
|
|
657
|
+
if (!targetSessionId && !h.currentSession) {
|
|
658
|
+
return { success: false, error: 'No targetSessionId specified — cannot route command' };
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const provider = h.getProvider(args?.agentType);
|
|
662
|
+
const transport = getTargetTransport(h, provider);
|
|
663
|
+
const providerType = provider?.type || getCurrentProviderType(h, args?.agentType || '');
|
|
664
|
+
const adapter = isCliLikeTransport(transport) ? getTargetedCliAdapter(h, args, provider?.type) : null;
|
|
665
|
+
const targetInstance = getTargetInstance(h, args);
|
|
666
|
+
|
|
667
|
+
let adapterStatus: unknown = null;
|
|
668
|
+
let parsedStatus: unknown = null;
|
|
669
|
+
let adapterDebugSnapshot: unknown = null;
|
|
670
|
+
let partialResponse = '';
|
|
671
|
+
if (adapter) {
|
|
672
|
+
try { adapterStatus = adapter.getStatus?.(); } catch (error: any) { adapterStatus = { error: error?.message || String(error) }; }
|
|
673
|
+
try { parsedStatus = typeof adapter.getScriptParsedStatus === 'function' ? parseMaybeJson(adapter.getScriptParsedStatus()) : null; } catch (error: any) { parsedStatus = { error: error?.message || String(error) }; }
|
|
674
|
+
try { adapterDebugSnapshot = typeof adapter.getDebugSnapshot === 'function' ? adapter.getDebugSnapshot() : null; } catch (error: any) { adapterDebugSnapshot = { error: error?.message || String(error) }; }
|
|
675
|
+
try { partialResponse = adapter.getPartialResponse?.() || ''; } catch { partialResponse = ''; }
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
let instanceState: unknown = null;
|
|
679
|
+
if (targetInstance?.getState) {
|
|
680
|
+
try { instanceState = summarizeStateForDebug(targetInstance.getState()); } catch (error: any) { instanceState = { error: error?.message || String(error) }; }
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
let readChat: unknown = null;
|
|
684
|
+
try {
|
|
685
|
+
const readResult = await handleReadChat(h, { ...args, tailLimit: Math.max(1, Math.min(40, Number(args?.tailLimit || 40))) });
|
|
686
|
+
readChat = readResult.success
|
|
687
|
+
? {
|
|
688
|
+
success: true,
|
|
689
|
+
status: readResult.status,
|
|
690
|
+
title: readResult.title,
|
|
691
|
+
totalMessages: readResult.totalMessages,
|
|
692
|
+
returnedMessages: Array.isArray(readResult.messages) ? readResult.messages.length : undefined,
|
|
693
|
+
syncMode: readResult.syncMode,
|
|
694
|
+
replaceFrom: readResult.replaceFrom,
|
|
695
|
+
lastMessageSignature: readResult.lastMessageSignature,
|
|
696
|
+
providerSessionId: readResult.providerSessionId,
|
|
697
|
+
transcriptAuthority: readResult.transcriptAuthority,
|
|
698
|
+
coverage: readResult.coverage,
|
|
699
|
+
activeModal: readResult.activeModal,
|
|
700
|
+
messagesTail: Array.isArray(readResult.messages) ? readResult.messages.slice(-20) : [],
|
|
701
|
+
debugReadChat: readResult.debugReadChat,
|
|
702
|
+
}
|
|
703
|
+
: { success: false, error: readResult.error };
|
|
704
|
+
} catch (error: any) {
|
|
705
|
+
readChat = { success: false, error: error?.message || String(error) };
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const cdp = h.getCdp();
|
|
709
|
+
const rawBundle: Record<string, unknown> = {
|
|
710
|
+
version: 1,
|
|
711
|
+
createdAt: new Date().toISOString(),
|
|
712
|
+
target: {
|
|
713
|
+
targetSessionId,
|
|
714
|
+
providerType,
|
|
715
|
+
transport,
|
|
716
|
+
routeManagerKey: h.currentManagerKey,
|
|
717
|
+
currentIdeType: h.currentIdeType,
|
|
718
|
+
},
|
|
719
|
+
session: summarizeSessionForDebug(h.currentSession),
|
|
720
|
+
provider: summarizeProviderForDebug(provider),
|
|
721
|
+
daemon: {
|
|
722
|
+
pid: process.pid,
|
|
723
|
+
platform: process.platform,
|
|
724
|
+
nodeVersion: process.version,
|
|
725
|
+
cwd: process.cwd(),
|
|
726
|
+
},
|
|
727
|
+
cdp: {
|
|
728
|
+
requested: !!cdp,
|
|
729
|
+
connected: !!cdp?.isConnected,
|
|
730
|
+
managerKey: getCurrentManagerKey(h),
|
|
731
|
+
},
|
|
732
|
+
instanceState,
|
|
733
|
+
cli: adapter ? {
|
|
734
|
+
cliType: adapter.cliType,
|
|
735
|
+
cliName: adapter.cliName,
|
|
736
|
+
workingDir: adapter.workingDir,
|
|
737
|
+
status: (adapterStatus as any)?.status,
|
|
738
|
+
activeModal: (adapterStatus as any)?.activeModal,
|
|
739
|
+
messageCount: Array.isArray((adapterStatus as any)?.messages) ? (adapterStatus as any).messages.length : undefined,
|
|
740
|
+
messagesTail: Array.isArray((adapterStatus as any)?.messages) ? (adapterStatus as any).messages.slice(-20) : undefined,
|
|
741
|
+
parsedStatus,
|
|
742
|
+
partialResponse,
|
|
743
|
+
ready: typeof adapter.isReady === 'function' ? adapter.isReady() : undefined,
|
|
744
|
+
processing: typeof adapter.isProcessing === 'function' ? adapter.isProcessing() : undefined,
|
|
745
|
+
debugSnapshot: adapterDebugSnapshot,
|
|
746
|
+
} : null,
|
|
747
|
+
readChat,
|
|
748
|
+
frontend: args?.frontendSnapshot && typeof args.frontendSnapshot === 'object' ? args.frontendSnapshot : null,
|
|
749
|
+
recentLogs: getRecentLogs(80, 'debug'),
|
|
750
|
+
recentDebugTrace: getRecentDebugTrace({ limit: 120 }),
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
const bundle = sanitizeDebugBundleValue(rawBundle) as Record<string, unknown>;
|
|
754
|
+
return {
|
|
755
|
+
success: true,
|
|
756
|
+
bundle,
|
|
757
|
+
text: buildDebugBundleText(bundle),
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
|
|
492
761
|
function didProviderConfirmSend(result: any): boolean {
|
|
493
762
|
const parsed = parseMaybeJson(result);
|
|
494
763
|
if (parsed === true) return true;
|
|
@@ -52,7 +52,10 @@ function commandExists(command: string): boolean {
|
|
|
52
52
|
return existsSync(expandExecutable(trimmed));
|
|
53
53
|
}
|
|
54
54
|
try {
|
|
55
|
-
execFileSync(process.platform === 'win32' ? 'where' : 'which', [trimmed], {
|
|
55
|
+
execFileSync(process.platform === 'win32' ? 'where' : 'which', [trimmed], {
|
|
56
|
+
stdio: 'ignore',
|
|
57
|
+
...(process.platform === 'win32' ? { windowsHide: true } : {}),
|
|
58
|
+
});
|
|
56
59
|
return true;
|
|
57
60
|
} catch {
|
|
58
61
|
return false;
|
package/src/commands/handler.ts
CHANGED
|
@@ -380,6 +380,7 @@ export class DaemonCommandHandler implements CommandHelpers {
|
|
|
380
380
|
|
|
381
381
|
const sessionScopedCommands = new Set([
|
|
382
382
|
'read_chat',
|
|
383
|
+
'get_chat_debug_bundle',
|
|
383
384
|
'send_chat',
|
|
384
385
|
'list_chats',
|
|
385
386
|
'new_chat',
|
|
@@ -431,6 +432,7 @@ export class DaemonCommandHandler implements CommandHelpers {
|
|
|
431
432
|
switch (cmd) {
|
|
432
433
|
// ─── Chat commands (chat-commands.ts) ───────────────
|
|
433
434
|
case 'read_chat': return Chat.handleReadChat(this, args);
|
|
435
|
+
case 'get_chat_debug_bundle': return Chat.handleGetChatDebugBundle(this, args);
|
|
434
436
|
case 'chat_history': return Chat.handleChatHistory(this, args);
|
|
435
437
|
case 'send_chat': return Chat.handleSendChat(this, args);
|
|
436
438
|
case 'list_chats': return Chat.handleListChats(this, args);
|
package/src/commands/router.ts
CHANGED
|
@@ -36,7 +36,7 @@ import { getSessionHostSurfaceKind, partitionSessionHostRecords } from '../sessi
|
|
|
36
36
|
import { buildSessionEntries } from '../status/builders.js';
|
|
37
37
|
import { buildMachineInfo, buildStatusSnapshot } from '../status/snapshot.js';
|
|
38
38
|
import { getSessionCompletionMarker } from '../status/snapshot.js';
|
|
39
|
-
import { spawnDetachedDaemonUpgradeHelper } from './upgrade-helper.js';
|
|
39
|
+
import { execNpmCommandSync, resolveCurrentGlobalInstallSurface, spawnDetachedDaemonUpgradeHelper } from './upgrade-helper.js';
|
|
40
40
|
import * as fs from 'fs';
|
|
41
41
|
|
|
42
42
|
// ─── Types ───
|
|
@@ -525,6 +525,12 @@ export class DaemonCommandRouter {
|
|
|
525
525
|
const wantsAll = args?.all === true;
|
|
526
526
|
const offset = wantsAll ? 0 : Math.max(0, Number(args?.offset) || 0);
|
|
527
527
|
const limit = wantsAll ? Number.MAX_SAFE_INTEGER : Math.max(1, Math.min(100, Number(args?.limit) || 30));
|
|
528
|
+
const requestedWorkspace = typeof args?.workspace === 'string' ? args.workspace.trim() : '';
|
|
529
|
+
const requestedProviderSessionId = typeof args?.providerSessionId === 'string'
|
|
530
|
+
? args.providerSessionId.trim()
|
|
531
|
+
: typeof args?.activeProviderSessionId === 'string'
|
|
532
|
+
? args.activeProviderSessionId.trim()
|
|
533
|
+
: '';
|
|
528
534
|
const providerMeta = this.deps.providerLoader.resolve?.(providerType) || this.deps.providerLoader.getMeta(providerType);
|
|
529
535
|
const { sessions: historySessions, hasMore, source } = listProviderHistorySessions(providerType, {
|
|
530
536
|
canonicalHistory: providerMeta?.canonicalHistory,
|
|
@@ -546,6 +552,10 @@ export class DaemonCommandRouter {
|
|
|
546
552
|
sessions: historySessions.map(session => {
|
|
547
553
|
const saved = savedSessionById.get(session.historySessionId);
|
|
548
554
|
const recent = recentSessionById.get(session.historySessionId);
|
|
555
|
+
const workspace = saved?.workspace
|
|
556
|
+
|| recent?.workspace
|
|
557
|
+
|| session.workspace
|
|
558
|
+
|| (requestedWorkspace && requestedProviderSessionId === session.historySessionId ? requestedWorkspace : undefined);
|
|
549
559
|
return {
|
|
550
560
|
id: session.historySessionId,
|
|
551
561
|
providerSessionId: session.historySessionId,
|
|
@@ -553,13 +563,13 @@ export class DaemonCommandRouter {
|
|
|
553
563
|
providerName: saved?.providerName || recent?.providerName || providerType,
|
|
554
564
|
kind: saved?.kind || recent?.kind || kind,
|
|
555
565
|
title: saved?.title || recent?.title || session.sessionTitle || session.preview || providerType,
|
|
556
|
-
workspace
|
|
566
|
+
workspace,
|
|
557
567
|
summaryMetadata: saved?.summaryMetadata || recent?.summaryMetadata,
|
|
558
568
|
preview: session.preview,
|
|
559
569
|
messageCount: session.messageCount,
|
|
560
570
|
firstMessageAt: session.firstMessageAt,
|
|
561
571
|
lastMessageAt: session.lastMessageAt,
|
|
562
|
-
canResume: !!
|
|
572
|
+
canResume: !!workspace && canResumeById,
|
|
563
573
|
historySource: session.source,
|
|
564
574
|
sourcePath: session.sourcePath,
|
|
565
575
|
sourceMtimeMs: session.sourceMtimeMs,
|
|
@@ -847,23 +857,22 @@ export class DaemonCommandRouter {
|
|
|
847
857
|
case 'daemon_upgrade': {
|
|
848
858
|
LOG.info('Upgrade', 'Remote upgrade requested from dashboard');
|
|
849
859
|
try {
|
|
850
|
-
const { execSync } = await import('child_process');
|
|
851
|
-
|
|
852
860
|
// Detect package name for upgrade
|
|
853
861
|
const isStandalone = this.deps.packageName === '@adhdev/daemon-standalone'
|
|
854
862
|
|| process.argv[1]?.includes('daemon-standalone');
|
|
855
863
|
const pkgName = isStandalone ? '@adhdev/daemon-standalone' : 'adhdev';
|
|
864
|
+
const npmSurface = resolveCurrentGlobalInstallSurface({ packageName: pkgName });
|
|
856
865
|
|
|
857
866
|
// Check latest version
|
|
858
|
-
const latest =
|
|
867
|
+
const latest = String(execNpmCommandSync(['view', pkgName, 'version'], { encoding: 'utf-8', timeout: 10000 }, npmSurface)).trim();
|
|
859
868
|
LOG.info('Upgrade', `Latest ${pkgName}: v${latest}`);
|
|
860
869
|
let currentInstalled: string | null = null;
|
|
861
870
|
try {
|
|
862
|
-
const currentJson =
|
|
871
|
+
const currentJson = String(execNpmCommandSync(['ls', '-g', pkgName, '--depth=0', '--json'], {
|
|
863
872
|
encoding: 'utf-8',
|
|
864
873
|
timeout: 10000,
|
|
865
874
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
866
|
-
}).trim();
|
|
875
|
+
}, npmSurface)).trim();
|
|
867
876
|
const parsed = JSON.parse(currentJson);
|
|
868
877
|
currentInstalled = parsed?.dependencies?.[pkgName]?.version || null;
|
|
869
878
|
} catch {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execFileSync } from 'child_process';
|
|
1
|
+
import { execFileSync, type ExecFileSyncOptions } from 'child_process';
|
|
2
2
|
import { spawn } from 'child_process';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import * as os from 'os';
|
|
@@ -20,16 +20,18 @@ export interface CurrentGlobalInstallSurface {
|
|
|
20
20
|
npmArgsPrefix?: string[];
|
|
21
21
|
packageRoot: string | null;
|
|
22
22
|
installPrefix: string | null;
|
|
23
|
-
execOptions?:
|
|
23
|
+
execOptions?: NpmExecOptions;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export interface PinnedGlobalInstallCommand {
|
|
27
27
|
command: string;
|
|
28
28
|
args: string[];
|
|
29
29
|
surface: CurrentGlobalInstallSurface;
|
|
30
|
-
execOptions:
|
|
30
|
+
execOptions: NpmExecOptions;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
export type NpmExecOptions = { shell: boolean; windowsHide?: boolean };
|
|
34
|
+
|
|
33
35
|
function getUpgradeLogPath(): string {
|
|
34
36
|
const home = os.homedir();
|
|
35
37
|
const dir = path.join(home, '.adhdev');
|
|
@@ -49,29 +51,29 @@ function appendUpgradeLog(message: string): void {
|
|
|
49
51
|
function resolveSiblingNpmInvocation(nodeExecutable: string, platform: NodeJS.Platform = process.platform): {
|
|
50
52
|
executable: string;
|
|
51
53
|
argsPrefix: string[];
|
|
52
|
-
execOptions:
|
|
54
|
+
execOptions: NpmExecOptions;
|
|
53
55
|
} {
|
|
54
56
|
const binDir = path.dirname(nodeExecutable);
|
|
55
57
|
if (platform === 'win32') {
|
|
56
58
|
const npmCliPath = path.join(binDir, 'node_modules', 'npm', 'bin', 'npm-cli.js');
|
|
57
59
|
if (fs.existsSync(npmCliPath)) {
|
|
58
|
-
return { executable: nodeExecutable, argsPrefix: [npmCliPath], execOptions:
|
|
60
|
+
return { executable: nodeExecutable, argsPrefix: [npmCliPath], execOptions: getNpmExecOptions(platform) };
|
|
59
61
|
}
|
|
60
62
|
for (const candidate of ['npm.exe', 'npm']) {
|
|
61
63
|
const candidatePath = path.join(binDir, candidate);
|
|
62
64
|
if (fs.existsSync(candidatePath)) {
|
|
63
|
-
return { executable: candidatePath, argsPrefix: [], execOptions:
|
|
65
|
+
return { executable: candidatePath, argsPrefix: [], execOptions: getNpmExecOptions(platform) };
|
|
64
66
|
}
|
|
65
67
|
}
|
|
66
|
-
return { executable: nodeExecutable, argsPrefix: [npmCliPath], execOptions:
|
|
68
|
+
return { executable: nodeExecutable, argsPrefix: [npmCliPath], execOptions: getNpmExecOptions(platform) };
|
|
67
69
|
}
|
|
68
70
|
for (const candidate of ['npm']) {
|
|
69
71
|
const candidatePath = path.join(binDir, candidate);
|
|
70
72
|
if (fs.existsSync(candidatePath)) {
|
|
71
|
-
return { executable: candidatePath, argsPrefix: [], execOptions:
|
|
73
|
+
return { executable: candidatePath, argsPrefix: [], execOptions: getNpmExecOptions(platform) };
|
|
72
74
|
}
|
|
73
75
|
}
|
|
74
|
-
return { executable: 'npm', argsPrefix: [], execOptions:
|
|
76
|
+
return { executable: 'npm', argsPrefix: [], execOptions: getNpmExecOptions(platform) };
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
function findCurrentPackageRoot(currentCliPath: string | undefined, packageName: string): string | null {
|
|
@@ -167,14 +169,34 @@ export function buildPinnedGlobalInstallCommand(options: {
|
|
|
167
169
|
};
|
|
168
170
|
}
|
|
169
171
|
|
|
170
|
-
function getNpmExecOptions(
|
|
172
|
+
export function getNpmExecOptions(platform: NodeJS.Platform = process.platform): NpmExecOptions {
|
|
173
|
+
if (platform === 'win32') {
|
|
174
|
+
return { shell: false, windowsHide: true };
|
|
175
|
+
}
|
|
171
176
|
return { shell: false };
|
|
172
177
|
}
|
|
173
178
|
|
|
179
|
+
export function execNpmCommandSync(
|
|
180
|
+
args: string[],
|
|
181
|
+
options: ExecFileSyncOptions = {},
|
|
182
|
+
surface?: Pick<CurrentGlobalInstallSurface, 'npmExecutable' | 'npmArgsPrefix' | 'execOptions'>,
|
|
183
|
+
): Buffer | string {
|
|
184
|
+
const execOptions = surface?.execOptions || getNpmExecOptions();
|
|
185
|
+
return execFileSync(
|
|
186
|
+
surface?.npmExecutable || 'npm',
|
|
187
|
+
[...(surface?.npmArgsPrefix || []), ...args],
|
|
188
|
+
{
|
|
189
|
+
...options,
|
|
190
|
+
...execOptions,
|
|
191
|
+
...(process.platform === 'win32' ? { windowsHide: true } : {}),
|
|
192
|
+
},
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
174
196
|
function killPid(pid: number): boolean {
|
|
175
197
|
try {
|
|
176
198
|
if (process.platform === 'win32') {
|
|
177
|
-
execFileSync('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore' });
|
|
199
|
+
execFileSync('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore', windowsHide: true });
|
|
178
200
|
} else {
|
|
179
201
|
process.kill(pid, 'SIGTERM');
|
|
180
202
|
}
|
|
@@ -193,7 +215,7 @@ function getWindowsProcessCommandLine(pid: number): string | null {
|
|
|
193
215
|
'-ExecutionPolicy', 'Bypass',
|
|
194
216
|
'-Command',
|
|
195
217
|
`(Get-CimInstance Win32_Process -Filter "${pidFilter}").CommandLine`,
|
|
196
|
-
], { encoding: 'utf8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
218
|
+
], { encoding: 'utf8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true }).trim();
|
|
197
219
|
if (psOut) return psOut;
|
|
198
220
|
} catch {
|
|
199
221
|
// fall through to wmic fallback
|
|
@@ -202,7 +224,7 @@ function getWindowsProcessCommandLine(pid: number): string | null {
|
|
|
202
224
|
try {
|
|
203
225
|
const wmicOut = execFileSync('wmic', [
|
|
204
226
|
'process', 'where', pidFilter, 'get', 'CommandLine',
|
|
205
|
-
], { encoding: 'utf8', timeout: 3000, stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
227
|
+
], { encoding: 'utf8', timeout: 3000, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true }).trim();
|
|
206
228
|
if (wmicOut) return wmicOut;
|
|
207
229
|
} catch {
|
|
208
230
|
// noop
|
|
@@ -273,10 +295,10 @@ function removeDaemonPidFile(): void {
|
|
|
273
295
|
|
|
274
296
|
function cleanupStaleGlobalInstallDirs(pkgName: string, surface: CurrentGlobalInstallSurface): void {
|
|
275
297
|
const prefixArgs = surface.installPrefix ? ['--prefix', surface.installPrefix] : [];
|
|
276
|
-
const npmRoot =
|
|
298
|
+
const npmRoot = String(execNpmCommandSync(['root', '-g', ...prefixArgs], { encoding: 'utf8' }, surface)).trim();
|
|
277
299
|
if (!npmRoot) return;
|
|
278
300
|
const npmPrefix = surface.installPrefix
|
|
279
|
-
||
|
|
301
|
+
|| String(execNpmCommandSync(['prefix', '-g', ...prefixArgs], { encoding: 'utf8' }, surface)).trim();
|
|
280
302
|
const binDir = process.platform === 'win32' ? npmPrefix : path.join(npmPrefix, 'bin');
|
|
281
303
|
const packageBaseName = pkgName.startsWith('@') ? pkgName.split('/')[1] : pkgName;
|
|
282
304
|
const binNames = new Set<string>([packageBaseName]);
|