@adhdev/daemon-core 0.9.48 → 0.9.49
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 +371 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +369 -21
- 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 +267 -2
- package/src/commands/cli-manager.ts +4 -1
- package/src/commands/handler.ts +1 -0
- package/src/commands/router.ts +5 -6
- 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,271 @@ 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 provider = h.getProvider(args?.agentType);
|
|
657
|
+
const transport = getTargetTransport(h, provider);
|
|
658
|
+
const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
|
|
659
|
+
const providerType = provider?.type || getCurrentProviderType(h, args?.agentType || '');
|
|
660
|
+
const adapter = isCliLikeTransport(transport) ? getTargetedCliAdapter(h, args, provider?.type) : null;
|
|
661
|
+
const targetInstance = getTargetInstance(h, args);
|
|
662
|
+
|
|
663
|
+
let adapterStatus: unknown = null;
|
|
664
|
+
let parsedStatus: unknown = null;
|
|
665
|
+
let adapterDebugSnapshot: unknown = null;
|
|
666
|
+
let partialResponse = '';
|
|
667
|
+
if (adapter) {
|
|
668
|
+
try { adapterStatus = adapter.getStatus?.(); } catch (error: any) { adapterStatus = { error: error?.message || String(error) }; }
|
|
669
|
+
try { parsedStatus = typeof adapter.getScriptParsedStatus === 'function' ? parseMaybeJson(adapter.getScriptParsedStatus()) : null; } catch (error: any) { parsedStatus = { error: error?.message || String(error) }; }
|
|
670
|
+
try { adapterDebugSnapshot = typeof adapter.getDebugSnapshot === 'function' ? adapter.getDebugSnapshot() : null; } catch (error: any) { adapterDebugSnapshot = { error: error?.message || String(error) }; }
|
|
671
|
+
try { partialResponse = adapter.getPartialResponse?.() || ''; } catch { partialResponse = ''; }
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
let instanceState: unknown = null;
|
|
675
|
+
if (targetInstance?.getState) {
|
|
676
|
+
try { instanceState = summarizeStateForDebug(targetInstance.getState()); } catch (error: any) { instanceState = { error: error?.message || String(error) }; }
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
let readChat: unknown = null;
|
|
680
|
+
try {
|
|
681
|
+
const readResult = await handleReadChat(h, { ...args, tailLimit: Math.max(1, Math.min(40, Number(args?.tailLimit || 40))) });
|
|
682
|
+
readChat = readResult.success
|
|
683
|
+
? {
|
|
684
|
+
success: true,
|
|
685
|
+
status: readResult.status,
|
|
686
|
+
title: readResult.title,
|
|
687
|
+
totalMessages: readResult.totalMessages,
|
|
688
|
+
returnedMessages: Array.isArray(readResult.messages) ? readResult.messages.length : undefined,
|
|
689
|
+
syncMode: readResult.syncMode,
|
|
690
|
+
replaceFrom: readResult.replaceFrom,
|
|
691
|
+
lastMessageSignature: readResult.lastMessageSignature,
|
|
692
|
+
providerSessionId: readResult.providerSessionId,
|
|
693
|
+
transcriptAuthority: readResult.transcriptAuthority,
|
|
694
|
+
coverage: readResult.coverage,
|
|
695
|
+
activeModal: readResult.activeModal,
|
|
696
|
+
messagesTail: Array.isArray(readResult.messages) ? readResult.messages.slice(-20) : [],
|
|
697
|
+
debugReadChat: readResult.debugReadChat,
|
|
698
|
+
}
|
|
699
|
+
: { success: false, error: readResult.error };
|
|
700
|
+
} catch (error: any) {
|
|
701
|
+
readChat = { success: false, error: error?.message || String(error) };
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const cdp = h.getCdp();
|
|
705
|
+
const rawBundle: Record<string, unknown> = {
|
|
706
|
+
version: 1,
|
|
707
|
+
createdAt: new Date().toISOString(),
|
|
708
|
+
target: {
|
|
709
|
+
targetSessionId,
|
|
710
|
+
providerType,
|
|
711
|
+
transport,
|
|
712
|
+
routeManagerKey: h.currentManagerKey,
|
|
713
|
+
currentIdeType: h.currentIdeType,
|
|
714
|
+
},
|
|
715
|
+
session: summarizeSessionForDebug(h.currentSession),
|
|
716
|
+
provider: summarizeProviderForDebug(provider),
|
|
717
|
+
daemon: {
|
|
718
|
+
pid: process.pid,
|
|
719
|
+
platform: process.platform,
|
|
720
|
+
nodeVersion: process.version,
|
|
721
|
+
cwd: process.cwd(),
|
|
722
|
+
},
|
|
723
|
+
cdp: {
|
|
724
|
+
requested: !!cdp,
|
|
725
|
+
connected: !!cdp?.isConnected,
|
|
726
|
+
managerKey: getCurrentManagerKey(h),
|
|
727
|
+
},
|
|
728
|
+
instanceState,
|
|
729
|
+
cli: adapter ? {
|
|
730
|
+
cliType: adapter.cliType,
|
|
731
|
+
cliName: adapter.cliName,
|
|
732
|
+
workingDir: adapter.workingDir,
|
|
733
|
+
status: (adapterStatus as any)?.status,
|
|
734
|
+
activeModal: (adapterStatus as any)?.activeModal,
|
|
735
|
+
messageCount: Array.isArray((adapterStatus as any)?.messages) ? (adapterStatus as any).messages.length : undefined,
|
|
736
|
+
messagesTail: Array.isArray((adapterStatus as any)?.messages) ? (adapterStatus as any).messages.slice(-20) : undefined,
|
|
737
|
+
parsedStatus,
|
|
738
|
+
partialResponse,
|
|
739
|
+
ready: typeof adapter.isReady === 'function' ? adapter.isReady() : undefined,
|
|
740
|
+
processing: typeof adapter.isProcessing === 'function' ? adapter.isProcessing() : undefined,
|
|
741
|
+
debugSnapshot: adapterDebugSnapshot,
|
|
742
|
+
} : null,
|
|
743
|
+
readChat,
|
|
744
|
+
frontend: args?.frontendSnapshot && typeof args.frontendSnapshot === 'object' ? args.frontendSnapshot : null,
|
|
745
|
+
recentLogs: getRecentLogs(80, 'debug'),
|
|
746
|
+
recentDebugTrace: getRecentDebugTrace({ limit: 120 }),
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
const bundle = sanitizeDebugBundleValue(rawBundle) as Record<string, unknown>;
|
|
750
|
+
return {
|
|
751
|
+
success: true,
|
|
752
|
+
bundle,
|
|
753
|
+
text: buildDebugBundleText(bundle),
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
|
|
492
757
|
function didProviderConfirmSend(result: any): boolean {
|
|
493
758
|
const parsed = parseMaybeJson(result);
|
|
494
759
|
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
|
@@ -431,6 +431,7 @@ export class DaemonCommandHandler implements CommandHelpers {
|
|
|
431
431
|
switch (cmd) {
|
|
432
432
|
// ─── Chat commands (chat-commands.ts) ───────────────
|
|
433
433
|
case 'read_chat': return Chat.handleReadChat(this, args);
|
|
434
|
+
case 'get_chat_debug_bundle': return Chat.handleGetChatDebugBundle(this, args);
|
|
434
435
|
case 'chat_history': return Chat.handleChatHistory(this, args);
|
|
435
436
|
case 'send_chat': return Chat.handleSendChat(this, args);
|
|
436
437
|
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 ───
|
|
@@ -847,23 +847,22 @@ export class DaemonCommandRouter {
|
|
|
847
847
|
case 'daemon_upgrade': {
|
|
848
848
|
LOG.info('Upgrade', 'Remote upgrade requested from dashboard');
|
|
849
849
|
try {
|
|
850
|
-
const { execSync } = await import('child_process');
|
|
851
|
-
|
|
852
850
|
// Detect package name for upgrade
|
|
853
851
|
const isStandalone = this.deps.packageName === '@adhdev/daemon-standalone'
|
|
854
852
|
|| process.argv[1]?.includes('daemon-standalone');
|
|
855
853
|
const pkgName = isStandalone ? '@adhdev/daemon-standalone' : 'adhdev';
|
|
854
|
+
const npmSurface = resolveCurrentGlobalInstallSurface({ packageName: pkgName });
|
|
856
855
|
|
|
857
856
|
// Check latest version
|
|
858
|
-
const latest =
|
|
857
|
+
const latest = String(execNpmCommandSync(['view', pkgName, 'version'], { encoding: 'utf-8', timeout: 10000 }, npmSurface)).trim();
|
|
859
858
|
LOG.info('Upgrade', `Latest ${pkgName}: v${latest}`);
|
|
860
859
|
let currentInstalled: string | null = null;
|
|
861
860
|
try {
|
|
862
|
-
const currentJson =
|
|
861
|
+
const currentJson = String(execNpmCommandSync(['ls', '-g', pkgName, '--depth=0', '--json'], {
|
|
863
862
|
encoding: 'utf-8',
|
|
864
863
|
timeout: 10000,
|
|
865
864
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
866
|
-
}).trim();
|
|
865
|
+
}, npmSurface)).trim();
|
|
867
866
|
const parsed = JSON.parse(currentJson);
|
|
868
867
|
currentInstalled = parsed?.dependencies?.[pkgName]?.version || null;
|
|
869
868
|
} 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]);
|
|
@@ -60,7 +60,11 @@ function resolveCommandPath(command: string): string | null {
|
|
|
60
60
|
/** Run a shell command with timeout, returning stdout or null on failure */
|
|
61
61
|
function execAsync(cmd: string, timeoutMs = 5000): Promise<string | null> {
|
|
62
62
|
return new Promise((resolve) => {
|
|
63
|
-
const child = exec(cmd, {
|
|
63
|
+
const child = exec(cmd, {
|
|
64
|
+
encoding: 'utf-8',
|
|
65
|
+
timeout: timeoutMs,
|
|
66
|
+
...(process.platform === 'win32' ? { windowsHide: true } : {}),
|
|
67
|
+
}, (err, stdout) => {
|
|
64
68
|
if (err || !stdout?.trim()) {
|
|
65
69
|
resolve(null);
|
|
66
70
|
} else {
|
package/src/index.d.ts
CHANGED
|
@@ -41,8 +41,8 @@ export { DaemonCommandHandler } from './commands/handler.js';
|
|
|
41
41
|
export type { CommandResult, CommandContext } from './commands/handler.js';
|
|
42
42
|
export { DaemonCommandRouter } from './commands/router.js';
|
|
43
43
|
export type { CommandRouterDeps, CommandRouterResult } from './commands/router.js';
|
|
44
|
-
export { maybeRunDaemonUpgradeHelperFromEnv, spawnDetachedDaemonUpgradeHelper, resolveCurrentGlobalInstallSurface, buildPinnedGlobalInstallCommand } from './commands/upgrade-helper.js';
|
|
45
|
-
export type { DaemonUpgradeHelperPayload, CurrentGlobalInstallSurface, PinnedGlobalInstallCommand } from './commands/upgrade-helper.js';
|
|
44
|
+
export { maybeRunDaemonUpgradeHelperFromEnv, spawnDetachedDaemonUpgradeHelper, resolveCurrentGlobalInstallSurface, buildPinnedGlobalInstallCommand, execNpmCommandSync, getNpmExecOptions } from './commands/upgrade-helper.js';
|
|
45
|
+
export type { DaemonUpgradeHelperPayload, CurrentGlobalInstallSurface, PinnedGlobalInstallCommand, NpmExecOptions } from './commands/upgrade-helper.js';
|
|
46
46
|
export { DaemonStatusReporter } from './status/reporter.js';
|
|
47
47
|
export { buildSessionEntries, findCdpManager, hasCdpManager, isCdpConnected } from './status/builders.js';
|
|
48
48
|
export { buildStatusSnapshot, buildMachineInfo } from './status/snapshot.js';
|
package/src/index.ts
CHANGED
|
@@ -140,15 +140,18 @@ export type { CommandResult, CommandContext } from './commands/handler.js';
|
|
|
140
140
|
export { DaemonCommandRouter } from './commands/router.js';
|
|
141
141
|
export type { CommandRouterDeps, CommandRouterResult } from './commands/router.js';
|
|
142
142
|
export {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
143
|
+
maybeRunDaemonUpgradeHelperFromEnv,
|
|
144
|
+
spawnDetachedDaemonUpgradeHelper,
|
|
145
|
+
resolveCurrentGlobalInstallSurface,
|
|
146
|
+
buildPinnedGlobalInstallCommand,
|
|
147
|
+
execNpmCommandSync,
|
|
148
|
+
getNpmExecOptions,
|
|
147
149
|
} from './commands/upgrade-helper.js';
|
|
148
150
|
export type {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
151
|
+
DaemonUpgradeHelperPayload,
|
|
152
|
+
CurrentGlobalInstallSurface,
|
|
153
|
+
PinnedGlobalInstallCommand,
|
|
154
|
+
NpmExecOptions,
|
|
152
155
|
} from './commands/upgrade-helper.js';
|
|
153
156
|
|
|
154
157
|
// ── Status ──
|