@adhdev/daemon-core 0.5.3
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/index.d.ts +2662 -0
- package/dist/index.js +11341 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
- package/providers/_builtin/.github/workflows/generate-registry.yml +57 -0
- package/providers/_builtin/COMPATIBILITY.md +217 -0
- package/providers/_builtin/CONTRIBUTING.md +200 -0
- package/providers/_builtin/README.md +119 -0
- package/providers/_builtin/_helpers/index.js +188 -0
- package/providers/_builtin/acp/agentpool/provider.json +54 -0
- package/providers/_builtin/acp/amp/provider.json +52 -0
- package/providers/_builtin/acp/auggie/provider.json +57 -0
- package/providers/_builtin/acp/autodev/provider.json +54 -0
- package/providers/_builtin/acp/autohand/provider.json +52 -0
- package/providers/_builtin/acp/blackbox-ai/provider.json +54 -0
- package/providers/_builtin/acp/claude-agent/provider.json +57 -0
- package/providers/_builtin/acp/cline-acp/provider.json +54 -0
- package/providers/_builtin/acp/codebuddy/provider.json +54 -0
- package/providers/_builtin/acp/codex-cli/provider.json +57 -0
- package/providers/_builtin/acp/corust-agent/provider.json +52 -0
- package/providers/_builtin/acp/crow-cli/provider.json +54 -0
- package/providers/_builtin/acp/cursor-acp/provider.json +54 -0
- package/providers/_builtin/acp/deepagents/provider.json +52 -0
- package/providers/_builtin/acp/dimcode/provider.json +54 -0
- package/providers/_builtin/acp/docker-cagent/provider.json +57 -0
- package/providers/_builtin/acp/factory-droid/provider.json +60 -0
- package/providers/_builtin/acp/fast-agent/provider.json +52 -0
- package/providers/_builtin/acp/gemini-cli/provider.json +114 -0
- package/providers/_builtin/acp/github-copilot/provider.json +54 -0
- package/providers/_builtin/acp/goose/provider.json +57 -0
- package/providers/_builtin/acp/junie/provider.json +52 -0
- package/providers/_builtin/acp/kilo/provider.json +54 -0
- package/providers/_builtin/acp/kimi-cli/provider.json +57 -0
- package/providers/_builtin/acp/minion-code/provider.json +52 -0
- package/providers/_builtin/acp/mistral-vibe/provider.json +57 -0
- package/providers/_builtin/acp/nova/provider.json +54 -0
- package/providers/_builtin/acp/openclaw/provider.json +54 -0
- package/providers/_builtin/acp/opencode/provider.json +52 -0
- package/providers/_builtin/acp/openhands/provider.json +54 -0
- package/providers/_builtin/acp/pi-acp/provider.json +52 -0
- package/providers/_builtin/acp/qoder/provider.json +54 -0
- package/providers/_builtin/acp/qwen-code/provider.json +60 -0
- package/providers/_builtin/acp/stakpak/provider.json +54 -0
- package/providers/_builtin/acp/vtcode/provider.json +54 -0
- package/providers/_builtin/cli/claude-cli/provider.json +100 -0
- package/providers/_builtin/cli/codex-cli/provider.json +89 -0
- package/providers/_builtin/cli/gemini-cli/provider.json +93 -0
- package/providers/_builtin/docs/CDP_SELECTOR_GUIDE.md +370 -0
- package/providers/_builtin/docs/PROVIDER_GUIDE.md +916 -0
- package/providers/_builtin/extension/cline/provider.json +35 -0
- package/providers/_builtin/extension/cline/scripts/focus_editor.js +48 -0
- package/providers/_builtin/extension/cline/scripts/list_chats.js +100 -0
- package/providers/_builtin/extension/cline/scripts/list_models.js +43 -0
- package/providers/_builtin/extension/cline/scripts/list_modes.js +35 -0
- package/providers/_builtin/extension/cline/scripts/new_session.js +85 -0
- package/providers/_builtin/extension/cline/scripts/open_panel.js +25 -0
- package/providers/_builtin/extension/cline/scripts/read_chat.js +257 -0
- package/providers/_builtin/extension/cline/scripts/resolve_action.js +83 -0
- package/providers/_builtin/extension/cline/scripts/send_message.js +95 -0
- package/providers/_builtin/extension/cline/scripts/set_mode.js +36 -0
- package/providers/_builtin/extension/cline/scripts/set_model.js +36 -0
- package/providers/_builtin/extension/cline/scripts/switch_session.js +206 -0
- package/providers/_builtin/extension/cline/scripts.js +73 -0
- package/providers/_builtin/extension/roo-code/provider.json +35 -0
- package/providers/_builtin/extension/roo-code/scripts.js +659 -0
- package/providers/_builtin/ide/antigravity/provider.json +68 -0
- package/providers/_builtin/ide/antigravity/scripts/1.106/focus_editor.js +20 -0
- package/providers/_builtin/ide/antigravity/scripts/1.106/list_chats.js +137 -0
- package/providers/_builtin/ide/antigravity/scripts/1.106/list_models.js +38 -0
- package/providers/_builtin/ide/antigravity/scripts/1.106/list_modes.js +48 -0
- package/providers/_builtin/ide/antigravity/scripts/1.106/new_session.js +75 -0
- package/providers/_builtin/ide/antigravity/scripts/1.106/read_chat.js +262 -0
- package/providers/_builtin/ide/antigravity/scripts/1.106/resolve_action.js +68 -0
- package/providers/_builtin/ide/antigravity/scripts/1.106/scripts.js +57 -0
- package/providers/_builtin/ide/antigravity/scripts/1.106/send_message.js +56 -0
- package/providers/_builtin/ide/antigravity/scripts/1.106/set_mode.js +34 -0
- package/providers/_builtin/ide/antigravity/scripts/1.106/set_model.js +47 -0
- package/providers/_builtin/ide/antigravity/scripts/1.106/switch_session.js +114 -0
- package/providers/_builtin/ide/antigravity/scripts/1.107/focus_editor.js +20 -0
- package/providers/_builtin/ide/antigravity/scripts/1.107/list_chats.js +137 -0
- package/providers/_builtin/ide/antigravity/scripts/1.107/list_models.js +61 -0
- package/providers/_builtin/ide/antigravity/scripts/1.107/list_modes.js +72 -0
- package/providers/_builtin/ide/antigravity/scripts/1.107/new_session.js +75 -0
- package/providers/_builtin/ide/antigravity/scripts/1.107/read_chat.js +262 -0
- package/providers/_builtin/ide/antigravity/scripts/1.107/resolve_action.js +68 -0
- package/providers/_builtin/ide/antigravity/scripts/1.107/scripts.js +67 -0
- package/providers/_builtin/ide/antigravity/scripts/1.107/send_message.js +56 -0
- package/providers/_builtin/ide/antigravity/scripts/1.107/set_mode.js +67 -0
- package/providers/_builtin/ide/antigravity/scripts/1.107/set_model.js +72 -0
- package/providers/_builtin/ide/antigravity/scripts/1.107/switch_session.js +114 -0
- package/providers/_builtin/ide/cursor/provider.json +70 -0
- package/providers/_builtin/ide/cursor/scripts/0.49/dismiss_notification.js +30 -0
- package/providers/_builtin/ide/cursor/scripts/0.49/focus_editor.js +13 -0
- package/providers/_builtin/ide/cursor/scripts/0.49/list_models.js +78 -0
- package/providers/_builtin/ide/cursor/scripts/0.49/list_modes.js +40 -0
- package/providers/_builtin/ide/cursor/scripts/0.49/list_notifications.js +23 -0
- package/providers/_builtin/ide/cursor/scripts/0.49/list_sessions.js +42 -0
- package/providers/_builtin/ide/cursor/scripts/0.49/new_session.js +20 -0
- package/providers/_builtin/ide/cursor/scripts/0.49/open_panel.js +23 -0
- package/providers/_builtin/ide/cursor/scripts/0.49/read_chat.js +75 -0
- package/providers/_builtin/ide/cursor/scripts/0.49/resolve_action.js +19 -0
- package/providers/_builtin/ide/cursor/scripts/0.49/scripts.js +78 -0
- package/providers/_builtin/ide/cursor/scripts/0.49/send_message.js +23 -0
- package/providers/_builtin/ide/cursor/scripts/0.49/set_mode.js +38 -0
- package/providers/_builtin/ide/cursor/scripts/0.49/set_model.js +81 -0
- package/providers/_builtin/ide/cursor/scripts/0.49/switch_session.js +28 -0
- package/providers/_builtin/ide/kiro/provider.json +67 -0
- package/providers/_builtin/ide/kiro/scripts/focus_editor.js +20 -0
- package/providers/_builtin/ide/kiro/scripts/open_panel.js +47 -0
- package/providers/_builtin/ide/kiro/scripts/resolve_action.js +54 -0
- package/providers/_builtin/ide/kiro/scripts/send_message.js +29 -0
- package/providers/_builtin/ide/kiro/scripts/webview_list_models.js +39 -0
- package/providers/_builtin/ide/kiro/scripts/webview_list_modes.js +39 -0
- package/providers/_builtin/ide/kiro/scripts/webview_list_sessions.js +21 -0
- package/providers/_builtin/ide/kiro/scripts/webview_new_session.js +34 -0
- package/providers/_builtin/ide/kiro/scripts/webview_read_chat.js +68 -0
- package/providers/_builtin/ide/kiro/scripts/webview_send_message.js +72 -0
- package/providers/_builtin/ide/kiro/scripts/webview_set_mode.js +15 -0
- package/providers/_builtin/ide/kiro/scripts/webview_set_model.js +15 -0
- package/providers/_builtin/ide/kiro/scripts/webview_switch_session.js +26 -0
- package/providers/_builtin/ide/kiro/scripts.js +62 -0
- package/providers/_builtin/ide/pearai/provider.json +67 -0
- package/providers/_builtin/ide/pearai/scripts/focus_editor.js +20 -0
- package/providers/_builtin/ide/pearai/scripts/list_sessions.js +38 -0
- package/providers/_builtin/ide/pearai/scripts/new_session.js +55 -0
- package/providers/_builtin/ide/pearai/scripts/open_panel.js +46 -0
- package/providers/_builtin/ide/pearai/scripts/resolve_action.js +54 -0
- package/providers/_builtin/ide/pearai/scripts/send_message.js +29 -0
- package/providers/_builtin/ide/pearai/scripts/webview_list_models.js +43 -0
- package/providers/_builtin/ide/pearai/scripts/webview_list_modes.js +35 -0
- package/providers/_builtin/ide/pearai/scripts/webview_list_sessions.js +62 -0
- package/providers/_builtin/ide/pearai/scripts/webview_new_session.js +49 -0
- package/providers/_builtin/ide/pearai/scripts/webview_read_chat.js +92 -0
- package/providers/_builtin/ide/pearai/scripts/webview_resolve_action.js +59 -0
- package/providers/_builtin/ide/pearai/scripts/webview_send_message.js +72 -0
- package/providers/_builtin/ide/pearai/scripts/webview_set_mode.js +36 -0
- package/providers/_builtin/ide/pearai/scripts/webview_set_model.js +36 -0
- package/providers/_builtin/ide/pearai/scripts/webview_switch_session.js +34 -0
- package/providers/_builtin/ide/pearai/scripts.js +74 -0
- package/providers/_builtin/ide/trae/provider.json +66 -0
- package/providers/_builtin/ide/trae/scripts/focus_editor.js +20 -0
- package/providers/_builtin/ide/trae/scripts/list_chats.js +24 -0
- package/providers/_builtin/ide/trae/scripts/list_models.js +39 -0
- package/providers/_builtin/ide/trae/scripts/list_modes.js +39 -0
- package/providers/_builtin/ide/trae/scripts/new_session.js +30 -0
- package/providers/_builtin/ide/trae/scripts/open_panel.js +44 -0
- package/providers/_builtin/ide/trae/scripts/read_chat.js +113 -0
- package/providers/_builtin/ide/trae/scripts/resolve_action.js +54 -0
- package/providers/_builtin/ide/trae/scripts/send_message.js +69 -0
- package/providers/_builtin/ide/trae/scripts/set_mode.js +15 -0
- package/providers/_builtin/ide/trae/scripts/set_model.js +15 -0
- package/providers/_builtin/ide/trae/scripts/switch_session.js +23 -0
- package/providers/_builtin/ide/trae/scripts.js +57 -0
- package/providers/_builtin/ide/vscode/provider.json +64 -0
- package/providers/_builtin/ide/vscode-insiders/provider.json +62 -0
- package/providers/_builtin/ide/vscodium/provider.json +63 -0
- package/providers/_builtin/ide/windsurf/provider.json +53 -0
- package/providers/_builtin/ide/windsurf/scripts/focus_editor.js +30 -0
- package/providers/_builtin/ide/windsurf/scripts/list_chats.js +117 -0
- package/providers/_builtin/ide/windsurf/scripts/list_models.js +39 -0
- package/providers/_builtin/ide/windsurf/scripts/list_modes.js +39 -0
- package/providers/_builtin/ide/windsurf/scripts/new_session.js +69 -0
- package/providers/_builtin/ide/windsurf/scripts/open_panel.js +58 -0
- package/providers/_builtin/ide/windsurf/scripts/read_chat.js +297 -0
- package/providers/_builtin/ide/windsurf/scripts/resolve_action.js +68 -0
- package/providers/_builtin/ide/windsurf/scripts/send_message.js +87 -0
- package/providers/_builtin/ide/windsurf/scripts/set_mode.js +15 -0
- package/providers/_builtin/ide/windsurf/scripts/set_model.js +15 -0
- package/providers/_builtin/ide/windsurf/scripts/switch_session.js +58 -0
- package/providers/_builtin/ide/windsurf/scripts.js +57 -0
- package/providers/_builtin/registry.json +266 -0
- package/providers/_builtin/validate.js +156 -0
- package/src/agent-stream/index.ts +6 -0
- package/src/agent-stream/manager.ts +286 -0
- package/src/agent-stream/poller.ts +154 -0
- package/src/agent-stream/provider-adapter.ts +138 -0
- package/src/agent-stream/types.ts +61 -0
- package/src/boot/daemon-lifecycle.ts +252 -0
- package/src/cdp/devtools.ts +335 -0
- package/src/cdp/initializer.ts +191 -0
- package/src/cdp/manager.ts +897 -0
- package/src/cdp/scanner.ts +185 -0
- package/src/cdp/setup.ts +150 -0
- package/src/cli-adapter-types.ts +25 -0
- package/src/cli-adapters/provider-cli-adapter.ts +448 -0
- package/src/commands/cdp-commands.ts +208 -0
- package/src/commands/chat-commands.ts +675 -0
- package/src/commands/cli-manager.ts +353 -0
- package/src/commands/handler.ts +328 -0
- package/src/commands/router.ts +258 -0
- package/src/commands/stream-commands.ts +325 -0
- package/src/config/chat-history.ts +211 -0
- package/src/config/config.ts +219 -0
- package/src/daemon/dev-server.ts +2378 -0
- package/src/daemon/scaffold-template.ts +394 -0
- package/src/daemon-core.ts +50 -0
- package/src/detection/cli-detector.ts +89 -0
- package/src/detection/ide-detector.ts +157 -0
- package/src/index.ts +103 -0
- package/src/installer.ts +263 -0
- package/src/ipc-protocol.ts +133 -0
- package/src/launch.ts +433 -0
- package/src/logging/command-log.ts +180 -0
- package/src/logging/logger.ts +316 -0
- package/src/providers/acp-provider-instance.ts +1140 -0
- package/src/providers/cli-provider-instance.ts +207 -0
- package/src/providers/contracts.ts +524 -0
- package/src/providers/extension-provider-instance.ts +156 -0
- package/src/providers/ide-provider-instance.ts +377 -0
- package/src/providers/index.ts +18 -0
- package/src/providers/provider-instance-manager.ts +182 -0
- package/src/providers/provider-instance.ts +112 -0
- package/src/providers/provider-loader.ts +1031 -0
- package/src/providers/status-monitor.ts +125 -0
- package/src/providers/version-archive.ts +266 -0
- package/src/status/reporter.ts +294 -0
- package/src/types.ts +206 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StatusMonitor — status monitoring notification system
|
|
3
|
+
*
|
|
4
|
+
* Common across all Provider categories (IDE/Extension/CLI).
|
|
5
|
+
* - approval wait(waiting_approval) occur when notification
|
|
6
|
+
* - Notification when generating persists for extended duration
|
|
7
|
+
* - all config Provider Settingstoggle possible
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface MonitorConfig {
|
|
11
|
+
/** Enable awaiting-approval notification */
|
|
12
|
+
approvalAlert: boolean;
|
|
13
|
+
/** prolonged generating notification enabled */
|
|
14
|
+
longGeneratingAlert: boolean;
|
|
15
|
+
/** Prolonged threshold (seconds) */
|
|
16
|
+
longGeneratingThresholdSec: number;
|
|
17
|
+
/** repeat notification cooldown (seconds) */
|
|
18
|
+
alertCooldownSec: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const DEFAULT_MONITOR_CONFIG: MonitorConfig = {
|
|
22
|
+
approvalAlert: true,
|
|
23
|
+
longGeneratingAlert: true,
|
|
24
|
+
longGeneratingThresholdSec: 180, // 3minutes
|
|
25
|
+
alertCooldownSec: 60, // 1minutes cooldown
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export interface MonitorEvent {
|
|
29
|
+
type: string;
|
|
30
|
+
agentKey: string;
|
|
31
|
+
timestamp: number;
|
|
32
|
+
elapsedSec?: number;
|
|
33
|
+
message?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class StatusMonitor {
|
|
37
|
+
private config: MonitorConfig;
|
|
38
|
+
private lastAlertTime = new Map<string, number>();
|
|
39
|
+
private generatingStartTimes = new Map<string, number>();
|
|
40
|
+
|
|
41
|
+
constructor(config?: Partial<MonitorConfig>) {
|
|
42
|
+
this.config = { ...DEFAULT_MONITOR_CONFIG, ...config };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Config update (called from Provider Settings) */
|
|
46
|
+
updateConfig(partial: Partial<MonitorConfig>): void {
|
|
47
|
+
Object.assign(this.config, partial);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** current config return */
|
|
51
|
+
getConfig(): MonitorConfig {
|
|
52
|
+
return { ...this.config };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check status transition → return notification event array
|
|
57
|
+
* each onTick()from call.
|
|
58
|
+
*/
|
|
59
|
+
check(agentKey: string, status: string, now: number): MonitorEvent[] {
|
|
60
|
+
const events: MonitorEvent[] = [];
|
|
61
|
+
|
|
62
|
+
// 1. approval wait notification
|
|
63
|
+
if (this.config.approvalAlert && status === 'waiting_approval') {
|
|
64
|
+
if (this.shouldAlert(agentKey + ':approval', now)) {
|
|
65
|
+
events.push({
|
|
66
|
+
type: 'monitor:approval_waiting',
|
|
67
|
+
agentKey,
|
|
68
|
+
timestamp: now,
|
|
69
|
+
message: `${agentKey} is waiting for approval`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 2. Detect prolonged generating (identical for IDE/Extension/CLI)
|
|
75
|
+
if (status === 'generating' || status === 'streaming') {
|
|
76
|
+
if (!this.generatingStartTimes.has(agentKey)) {
|
|
77
|
+
this.generatingStartTimes.set(agentKey, now);
|
|
78
|
+
}
|
|
79
|
+
if (this.config.longGeneratingAlert) {
|
|
80
|
+
const startedAt = this.generatingStartTimes.get(agentKey)!;
|
|
81
|
+
const elapsedSec = Math.round((now - startedAt) / 1000);
|
|
82
|
+
if (elapsedSec > this.config.longGeneratingThresholdSec) {
|
|
83
|
+
if (this.shouldAlert(agentKey + ':long_gen', now)) {
|
|
84
|
+
events.push({
|
|
85
|
+
type: 'monitor:long_generating',
|
|
86
|
+
agentKey,
|
|
87
|
+
elapsedSec,
|
|
88
|
+
timestamp: now,
|
|
89
|
+
message: `${agentKey} has been generating for ${Math.round(elapsedSec / 60)}min`,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
// Reset timer when switching to non-generating status
|
|
96
|
+
this.generatingStartTimes.delete(agentKey);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return events;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Cooldown check — prevent sending the same notification too frequently */
|
|
103
|
+
private shouldAlert(key: string, now: number): boolean {
|
|
104
|
+
const last = this.lastAlertTime.get(key) || 0;
|
|
105
|
+
if (now - last > this.config.alertCooldownSec * 1000) {
|
|
106
|
+
this.lastAlertTime.set(key, now);
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Reset (on agent terminate/restart) */
|
|
113
|
+
reset(agentKey?: string): void {
|
|
114
|
+
if (agentKey) {
|
|
115
|
+
this.generatingStartTimes.delete(agentKey);
|
|
116
|
+
// Delete all cooldowns for this key
|
|
117
|
+
for (const k of this.lastAlertTime.keys()) {
|
|
118
|
+
if (k.startsWith(agentKey)) this.lastAlertTime.delete(k);
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
this.generatingStartTimes.clear();
|
|
122
|
+
this.lastAlertTime.clear();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider Version Detection & Archiving
|
|
3
|
+
*
|
|
4
|
+
* Detects installed versions for all provider categories (IDE, CLI, ACP, Extension).
|
|
5
|
+
* Archives version history to ~/.adhdev/version-history.json for compatibility tracking.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* const archive = new VersionArchive();
|
|
9
|
+
* const results = await detectAllVersions(providerLoader, archive);
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
import * as os from 'os';
|
|
15
|
+
import { execSync } from 'child_process';
|
|
16
|
+
import { platform } from 'os';
|
|
17
|
+
import type { ProviderLoader } from './provider-loader.js';
|
|
18
|
+
|
|
19
|
+
// ─── Types ──────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
export interface ProviderVersionInfo {
|
|
22
|
+
type: string;
|
|
23
|
+
name: string;
|
|
24
|
+
category: string;
|
|
25
|
+
installed: boolean;
|
|
26
|
+
version: string | null;
|
|
27
|
+
path: string | null;
|
|
28
|
+
binary: string | null;
|
|
29
|
+
detectedAt: string; // ISO timestamp
|
|
30
|
+
/**
|
|
31
|
+
* Set when the detected version is NOT listed in provider.json testedVersions.
|
|
32
|
+
* Means scripts may not work correctly with this version.
|
|
33
|
+
*/
|
|
34
|
+
warning?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface VersionHistoryEntry {
|
|
38
|
+
version: string;
|
|
39
|
+
detectedAt: string;
|
|
40
|
+
os: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface VersionHistory {
|
|
44
|
+
[providerType: string]: VersionHistoryEntry[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ─── Version Archive ──────────────────────────────
|
|
48
|
+
|
|
49
|
+
const ARCHIVE_PATH = path.join(os.homedir(), '.adhdev', 'version-history.json');
|
|
50
|
+
const MAX_ENTRIES_PER_PROVIDER = 20;
|
|
51
|
+
|
|
52
|
+
export class VersionArchive {
|
|
53
|
+
private history: VersionHistory = {};
|
|
54
|
+
|
|
55
|
+
constructor() {
|
|
56
|
+
this.load();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private load(): void {
|
|
60
|
+
try {
|
|
61
|
+
if (fs.existsSync(ARCHIVE_PATH)) {
|
|
62
|
+
this.history = JSON.parse(fs.readFileSync(ARCHIVE_PATH, 'utf-8'));
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
this.history = {};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Record a detected version (deduplicates same version) */
|
|
70
|
+
record(type: string, version: string): void {
|
|
71
|
+
if (!this.history[type]) this.history[type] = [];
|
|
72
|
+
|
|
73
|
+
const entries = this.history[type];
|
|
74
|
+
const last = entries[entries.length - 1];
|
|
75
|
+
|
|
76
|
+
// Skip if same version as last entry
|
|
77
|
+
if (last && last.version === version) return;
|
|
78
|
+
|
|
79
|
+
entries.push({
|
|
80
|
+
version,
|
|
81
|
+
detectedAt: new Date().toISOString(),
|
|
82
|
+
os: platform(),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Trim old entries
|
|
86
|
+
if (entries.length > MAX_ENTRIES_PER_PROVIDER) {
|
|
87
|
+
this.history[type] = entries.slice(-MAX_ENTRIES_PER_PROVIDER);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.save();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Get version history for a provider */
|
|
94
|
+
getHistory(type: string): VersionHistoryEntry[] {
|
|
95
|
+
return this.history[type] || [];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Get latest known version for a provider */
|
|
99
|
+
getLatest(type: string): string | null {
|
|
100
|
+
const entries = this.history[type];
|
|
101
|
+
return entries?.length ? entries[entries.length - 1].version : null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Get full archive */
|
|
105
|
+
getAll(): VersionHistory {
|
|
106
|
+
return { ...this.history };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private save(): void {
|
|
110
|
+
try {
|
|
111
|
+
fs.mkdirSync(path.dirname(ARCHIVE_PATH), { recursive: true });
|
|
112
|
+
fs.writeFileSync(ARCHIVE_PATH, JSON.stringify(this.history, null, 2));
|
|
113
|
+
} catch { /* ignore write errors */ }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ─── Version Detection ──────────────────────────────
|
|
118
|
+
|
|
119
|
+
function runCommand(cmd: string, timeout = 10000): string | null {
|
|
120
|
+
try {
|
|
121
|
+
return execSync(cmd, {
|
|
122
|
+
encoding: 'utf-8',
|
|
123
|
+
timeout,
|
|
124
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
125
|
+
}).trim();
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function findBinary(name: string): string | null {
|
|
132
|
+
const cmd = platform() === 'win32' ? `where ${name}` : `which ${name}`;
|
|
133
|
+
const result = runCommand(cmd, 5000);
|
|
134
|
+
return result ? result.split('\n')[0] : null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Extract version string from CLI output */
|
|
138
|
+
function parseVersion(raw: string): string {
|
|
139
|
+
// Common patterns: "1.2.3", "v1.2.3", "tool 1.2.3", "tool version 1.2.3"
|
|
140
|
+
const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
|
|
141
|
+
return match ? match[1] : raw.split('\n')[0].substring(0, 100);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function getVersion(binary: string, versionCommand?: string): string | null {
|
|
145
|
+
// Custom version command from provider.json
|
|
146
|
+
if (versionCommand) {
|
|
147
|
+
const raw = runCommand(versionCommand);
|
|
148
|
+
return raw ? parseVersion(raw) : null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Default: try --version, then -V, then -v
|
|
152
|
+
for (const flag of ['--version', '-V', '-v']) {
|
|
153
|
+
const raw = runCommand(`"${binary}" ${flag}`);
|
|
154
|
+
if (raw && raw.length < 500) return parseVersion(raw);
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function checkPathExists(paths: string[]): string | null {
|
|
160
|
+
for (const p of paths) {
|
|
161
|
+
if (p.includes('*')) {
|
|
162
|
+
const home = os.homedir();
|
|
163
|
+
const resolved = p.replace(/\*/g, home.split(path.sep).pop() || '');
|
|
164
|
+
if (fs.existsSync(resolved)) return resolved;
|
|
165
|
+
} else {
|
|
166
|
+
if (fs.existsSync(p)) return p;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** macOS: Get app version from Info.plist */
|
|
173
|
+
function getMacAppVersion(appPath: string): string | null {
|
|
174
|
+
if (platform() !== 'darwin' || !appPath.endsWith('.app')) return null;
|
|
175
|
+
const plistPath = path.join(appPath, 'Contents', 'Info.plist');
|
|
176
|
+
if (!fs.existsSync(plistPath)) return null;
|
|
177
|
+
const raw = runCommand(`/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" "${plistPath}"`);
|
|
178
|
+
return raw || null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Detect versions for all loaded providers
|
|
183
|
+
*/
|
|
184
|
+
export async function detectAllVersions(
|
|
185
|
+
loader: ProviderLoader,
|
|
186
|
+
archive?: VersionArchive,
|
|
187
|
+
): Promise<ProviderVersionInfo[]> {
|
|
188
|
+
const results: ProviderVersionInfo[] = [];
|
|
189
|
+
const currentOs = platform() as string;
|
|
190
|
+
|
|
191
|
+
for (const provider of loader.getAll()) {
|
|
192
|
+
const info: ProviderVersionInfo = {
|
|
193
|
+
type: provider.type,
|
|
194
|
+
name: provider.name,
|
|
195
|
+
category: provider.category,
|
|
196
|
+
installed: false,
|
|
197
|
+
version: null,
|
|
198
|
+
path: null,
|
|
199
|
+
binary: null,
|
|
200
|
+
detectedAt: new Date().toISOString(),
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const versionCommand = (provider as any).versionCommand;
|
|
204
|
+
|
|
205
|
+
if (provider.category === 'ide') {
|
|
206
|
+
// IDE: check app path + CLI
|
|
207
|
+
const osPaths = provider.paths?.[currentOs] || [];
|
|
208
|
+
const appPath = checkPathExists(osPaths);
|
|
209
|
+
const cliBin = provider.cli ? findBinary(provider.cli) : null;
|
|
210
|
+
|
|
211
|
+
// Also check bundled CLI inside .app for macOS
|
|
212
|
+
let resolvedBin = cliBin;
|
|
213
|
+
if (!resolvedBin && appPath && currentOs === 'darwin') {
|
|
214
|
+
const bundled = path.join(appPath, 'Contents', 'Resources', 'app', 'bin', provider.cli || '');
|
|
215
|
+
if (provider.cli && fs.existsSync(bundled)) resolvedBin = bundled;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
info.installed = !!(appPath || resolvedBin);
|
|
219
|
+
info.path = appPath || null;
|
|
220
|
+
info.binary = resolvedBin || null;
|
|
221
|
+
|
|
222
|
+
// Version: try CLI first, then plist
|
|
223
|
+
if (resolvedBin) {
|
|
224
|
+
info.version = getVersion(resolvedBin, versionCommand);
|
|
225
|
+
}
|
|
226
|
+
if (!info.version && appPath) {
|
|
227
|
+
info.version = getMacAppVersion(appPath);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
} else if (provider.category === 'cli' || provider.category === 'acp') {
|
|
231
|
+
// CLI/ACP: check binary
|
|
232
|
+
const bin = provider.binary || provider.spawn?.command || provider.cli || provider.type;
|
|
233
|
+
const binPath = findBinary(bin);
|
|
234
|
+
info.installed = !!binPath;
|
|
235
|
+
info.binary = binPath || null;
|
|
236
|
+
|
|
237
|
+
if (binPath) {
|
|
238
|
+
info.version = getVersion(binPath, versionCommand);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
} else if (provider.category === 'extension') {
|
|
242
|
+
// Extension: version detection via `code --list-extensions --show-versions`
|
|
243
|
+
// This is more complex and depends on the host IDE — skip for now
|
|
244
|
+
// Could be detected at runtime via CDP
|
|
245
|
+
info.installed = false; // Cannot reliably detect without IDE context
|
|
246
|
+
info.version = null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Archive the version if detected
|
|
250
|
+
if (info.version && archive) {
|
|
251
|
+
archive.record(provider.type, info.version);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Check testedVersions — warn if installed version is not documented
|
|
255
|
+
if (info.version && info.installed) {
|
|
256
|
+
const testedVersions: string[] = (provider as any).testedVersions || [];
|
|
257
|
+
if (testedVersions.length > 0 && !testedVersions.includes(info.version)) {
|
|
258
|
+
info.warning = `Version ${info.version} is not in testedVersions [${testedVersions.join(', ')}]. Scripts may not work correctly.`;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
results.push(info);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return results;
|
|
266
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DaemonStatusReporter — status collect & transmit (StatusReport / P2P / StatusEvent)
|
|
3
|
+
*
|
|
4
|
+
* Collect status from ProviderInstanceManager → assemble payload → transmit
|
|
5
|
+
* Each Instance manages its own status/transition. This module only assembles + transmits.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as os from 'os';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import { loadConfig } from '../config/config.js';
|
|
11
|
+
import { LOG } from '../logging/logger.js';
|
|
12
|
+
|
|
13
|
+
// ─── Daemon dependency interface ──────────────────────
|
|
14
|
+
|
|
15
|
+
export interface StatusReporterDeps {
|
|
16
|
+
serverConn: { isConnected(): boolean; sendMessage(type: string, data: any): void; getUserPlan(): string } | null;
|
|
17
|
+
cdpManagers: Map<string, { isConnected: boolean }>;
|
|
18
|
+
p2p: { isConnected: boolean; isAvailable: boolean; connectionState: string; connectedPeerCount: number; screenshotActive: boolean; sendStatus(data: any): void } | null;
|
|
19
|
+
providerLoader: { resolve(type: string): any; getAll(): any[] };
|
|
20
|
+
adapters: Map<string, { cliType: string; cliName: string; workingDir: string; getStatus(): any; getPartialResponse(): string }>;
|
|
21
|
+
detectedIdes: any[];
|
|
22
|
+
ideType: string;
|
|
23
|
+
instanceManager: { collectAllStates(): any[]; collectStatesByCategory(cat: string): any[] };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class DaemonStatusReporter {
|
|
27
|
+
private deps: StatusReporterDeps;
|
|
28
|
+
private log: (msg: string) => void;
|
|
29
|
+
|
|
30
|
+
private lastStatusSentAt = 0;
|
|
31
|
+
private statusPendingThrottle = false;
|
|
32
|
+
private lastP2PStatusHash = '';
|
|
33
|
+
private lastStatusSummary = '';
|
|
34
|
+
|
|
35
|
+
private statusTimer: NodeJS.Timeout | null = null;
|
|
36
|
+
private p2pTimer: NodeJS.Timeout | null = null;
|
|
37
|
+
|
|
38
|
+
constructor(deps: StatusReporterDeps, opts?: { logFn?: (msg: string) => void }) {
|
|
39
|
+
this.deps = deps;
|
|
40
|
+
this.log = opts?.logFn || LOG.forComponent('Status').asLogFn();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ─── Lifecycle ───────────────────────────────────
|
|
44
|
+
|
|
45
|
+
startReporting(): void {
|
|
46
|
+
setTimeout(() => {
|
|
47
|
+
this.sendUnifiedStatusReport().catch(e => LOG.warn('Status', `Initial report failed: ${e?.message}`));
|
|
48
|
+
}, 2000);
|
|
49
|
+
|
|
50
|
+
const scheduleServerReport = () => {
|
|
51
|
+
this.statusTimer = setTimeout(() => {
|
|
52
|
+
this.sendUnifiedStatusReport().catch(e => LOG.warn('Status', `Periodic report failed: ${e?.message}`));
|
|
53
|
+
scheduleServerReport();
|
|
54
|
+
}, 30_000);
|
|
55
|
+
};
|
|
56
|
+
scheduleServerReport();
|
|
57
|
+
|
|
58
|
+
this.p2pTimer = setInterval(() => {
|
|
59
|
+
if (this.deps.p2p?.isConnected) {
|
|
60
|
+
this.sendUnifiedStatusReport({ p2pOnly: true }).catch(e => LOG.warn('Status', `P2P status send failed: ${e?.message}`));
|
|
61
|
+
}
|
|
62
|
+
}, 5_000) as any;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
stopReporting(): void {
|
|
66
|
+
if (this.statusTimer) { clearTimeout(this.statusTimer); this.statusTimer = null; }
|
|
67
|
+
if (this.p2pTimer) { clearInterval(this.p2pTimer); this.p2pTimer = null; }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
onStatusChange(): void {
|
|
71
|
+
this.throttledReport();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
throttledReport(): void {
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
const elapsed = now - this.lastStatusSentAt;
|
|
77
|
+
if (elapsed >= 5_000) {
|
|
78
|
+
this.sendUnifiedStatusReport().catch(e => LOG.warn('Status', `Throttled report failed: ${e?.message}`));
|
|
79
|
+
} else if (!this.statusPendingThrottle) {
|
|
80
|
+
this.statusPendingThrottle = true;
|
|
81
|
+
setTimeout(() => {
|
|
82
|
+
this.statusPendingThrottle = false;
|
|
83
|
+
this.sendUnifiedStatusReport().catch(e => LOG.warn('Status', `Deferred report failed: ${e?.message}`));
|
|
84
|
+
}, 5_000 - elapsed);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
emitStatusEvent(event: Record<string, unknown>): void {
|
|
89
|
+
LOG.info('StatusEvent', `${event.event} (${event.providerType || event.ideType || ''})`);
|
|
90
|
+
this.deps.serverConn?.sendMessage('status_event', event);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
removeAgentTracking(_key: string): void { /* Managed by Instance itself */ }
|
|
94
|
+
|
|
95
|
+
// (agent-stream polling backward compat)
|
|
96
|
+
updateAgentStreams(_ideType: string, _streams: any[]): void { /* Managed by Instance itself */ }
|
|
97
|
+
|
|
98
|
+
// ─── Core ────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
private ts(): string {
|
|
101
|
+
return new Date().toISOString().slice(11, 23); // HH:mm:ss.SSS
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async sendUnifiedStatusReport(opts?: { p2pOnly?: boolean }): Promise<void> {
|
|
105
|
+
const { serverConn, cdpManagers, p2p, providerLoader, adapters } = this.deps;
|
|
106
|
+
if (!serverConn?.isConnected()) return;
|
|
107
|
+
this.lastStatusSentAt = Date.now();
|
|
108
|
+
const now = this.lastStatusSentAt;
|
|
109
|
+
const target = opts?.p2pOnly ? 'P2P' : 'P2P+Server';
|
|
110
|
+
|
|
111
|
+
const allStates = this.deps.instanceManager.collectAllStates();
|
|
112
|
+
const ideStates = allStates.filter((s: any) => s.category === 'ide');
|
|
113
|
+
const cliStates = allStates.filter((s: any) => s.category === 'cli');
|
|
114
|
+
const acpStates = allStates.filter((s: any) => s.category === 'acp');
|
|
115
|
+
|
|
116
|
+
// IDE summary
|
|
117
|
+
const ideSummary = ideStates.map((s: any) => {
|
|
118
|
+
const msgs = s.activeChat?.messages?.length || 0;
|
|
119
|
+
const exts = (s.extensions || []).length;
|
|
120
|
+
return `${s.type}(${s.status},${msgs}msg,${exts}ext${s.currentModel ? ',model=' + s.currentModel : ''})`;
|
|
121
|
+
}).join(', ');
|
|
122
|
+
|
|
123
|
+
// CLI summary
|
|
124
|
+
const cliSummary = cliStates.map((s: any) => `${s.type}(${s.status})`).join(', ');
|
|
125
|
+
// ACP summary
|
|
126
|
+
const acpSummary = acpStates.map((s: any) => `${s.type}(${s.status})`).join(', ');
|
|
127
|
+
|
|
128
|
+
// P2P-only = 5s heartbeat → DEBUG, P2P+Server = 30s interval → INFO
|
|
129
|
+
const logLevel = opts?.p2pOnly ? 'debug' : 'info';
|
|
130
|
+
const summary = `→${target} IDE: ${ideStates.length} [${ideSummary}] CLI: ${cliStates.length} [${cliSummary}] ACP: ${acpStates.length} [${acpSummary}]`;
|
|
131
|
+
// Skip identical repeats at any level to reduce log noise
|
|
132
|
+
const summaryChanged = summary !== this.lastStatusSummary;
|
|
133
|
+
if (summaryChanged) {
|
|
134
|
+
this.lastStatusSummary = summary;
|
|
135
|
+
if (logLevel === 'debug') {
|
|
136
|
+
LOG.debug('StatusReport', summary);
|
|
137
|
+
} else {
|
|
138
|
+
LOG.info('StatusReport', summary);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// IDE states → managedIdes
|
|
143
|
+
const managedIdes: any[] = ideStates.map((s: any) => ({
|
|
144
|
+
ideType: s.type,
|
|
145
|
+
ideVersion: '',
|
|
146
|
+
instanceId: s.instanceId,
|
|
147
|
+
workspaceFolders: s.workspaceFolders || [],
|
|
148
|
+
activeFile: s.activeFile || null,
|
|
149
|
+
terminals: 0,
|
|
150
|
+
aiAgents: [],
|
|
151
|
+
activeChat: s.activeChat,
|
|
152
|
+
chats: [],
|
|
153
|
+
agentStreams: (s.extensions || []).map((ext: any) => ({
|
|
154
|
+
agentType: ext.type,
|
|
155
|
+
agentName: ext.name,
|
|
156
|
+
extensionId: ext.type,
|
|
157
|
+
status: ext.status || 'idle',
|
|
158
|
+
messages: ext.activeChat?.messages || [],
|
|
159
|
+
inputContent: ext.activeChat?.inputContent || '',
|
|
160
|
+
activeModal: ext.activeChat?.activeModal || null,
|
|
161
|
+
})),
|
|
162
|
+
cdpConnected: s.cdpConnected || false,
|
|
163
|
+
currentModel: s.currentModel,
|
|
164
|
+
currentPlan: s.currentPlan,
|
|
165
|
+
currentAutoApprove: s.currentAutoApprove,
|
|
166
|
+
}));
|
|
167
|
+
|
|
168
|
+
// Merge/add Extension data
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
// CLI states → managedClis
|
|
172
|
+
const managedClis: any[] = cliStates.map((s: any) => ({
|
|
173
|
+
id: s.instanceId,
|
|
174
|
+
instanceId: s.instanceId,
|
|
175
|
+
cliType: s.type,
|
|
176
|
+
cliName: s.name,
|
|
177
|
+
status: s.status,
|
|
178
|
+
mode: s.mode || 'terminal',
|
|
179
|
+
workingDir: s.workingDir || '',
|
|
180
|
+
activeChat: s.activeChat,
|
|
181
|
+
}));
|
|
182
|
+
|
|
183
|
+
// ACP states → managedAcps (separated into per-type arrays)
|
|
184
|
+
const managedAcps: any[] = acpStates.map((s: any) => ({
|
|
185
|
+
id: s.instanceId,
|
|
186
|
+
acpType: s.type,
|
|
187
|
+
acpName: s.name,
|
|
188
|
+
status: s.status,
|
|
189
|
+
mode: 'chat',
|
|
190
|
+
workingDir: s.workingDir || '',
|
|
191
|
+
activeChat: s.activeChat,
|
|
192
|
+
currentModel: s.currentModel,
|
|
193
|
+
currentPlan: s.currentPlan,
|
|
194
|
+
acpConfigOptions: s.acpConfigOptions,
|
|
195
|
+
acpModes: s.acpModes,
|
|
196
|
+
}));
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
// ═══ Assemble payload (P2P — required data only) ═══
|
|
201
|
+
const payload: Record<string, any> = {
|
|
202
|
+
daemonMode: true,
|
|
203
|
+
machineNickname: loadConfig().machineNickname || null,
|
|
204
|
+
machine: {
|
|
205
|
+
hostname: os.hostname(),
|
|
206
|
+
platform: os.platform(),
|
|
207
|
+
arch: os.arch(),
|
|
208
|
+
cpus: os.cpus().length,
|
|
209
|
+
totalMem: os.totalmem(),
|
|
210
|
+
freeMem: os.freemem(),
|
|
211
|
+
loadavg: os.loadavg(),
|
|
212
|
+
uptime: os.uptime(),
|
|
213
|
+
},
|
|
214
|
+
managedIdes,
|
|
215
|
+
managedClis,
|
|
216
|
+
managedAcps,
|
|
217
|
+
p2p: {
|
|
218
|
+
available: p2p?.isAvailable || false,
|
|
219
|
+
state: p2p?.connectionState || 'unavailable',
|
|
220
|
+
peers: p2p?.connectedPeerCount || 0,
|
|
221
|
+
screenshotActive: p2p?.screenshotActive || false,
|
|
222
|
+
},
|
|
223
|
+
cdpConnected: [...cdpManagers.values()].some(c => c.isConnected),
|
|
224
|
+
connectedExtensions: [],
|
|
225
|
+
detectedIdes: this.deps.detectedIdes || [],
|
|
226
|
+
availableProviders: this.deps.providerLoader.getAll().map((p: any) => ({
|
|
227
|
+
type: p.type, icon: p.icon || '💻', displayName: p.displayName || p.type,
|
|
228
|
+
category: p.category,
|
|
229
|
+
})),
|
|
230
|
+
timestamp: now,
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// ═══ P2P transmit ═══
|
|
234
|
+
const p2pSent = this.sendP2PPayload(payload);
|
|
235
|
+
if (p2pSent) {
|
|
236
|
+
LOG.debug('P2P', `sent (${JSON.stringify(payload).length} bytes)`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ═══ Server transmit (minimal routing meta only — sanitizeForRelay removes everything else) ═══
|
|
240
|
+
if (opts?.p2pOnly) return;
|
|
241
|
+
const plan = serverConn.getUserPlan();
|
|
242
|
+
if (plan !== 'free') {
|
|
243
|
+
const wsPayload = {
|
|
244
|
+
daemonMode: true,
|
|
245
|
+
machineNickname: payload.machineNickname,
|
|
246
|
+
// managedIdes: server only saves id, type, cdpConnected
|
|
247
|
+
managedIdes: managedIdes.map(ide => ({
|
|
248
|
+
ideType: ide.ideType,
|
|
249
|
+
instanceId: ide.instanceId,
|
|
250
|
+
cdpConnected: ide.cdpConnected,
|
|
251
|
+
})),
|
|
252
|
+
// managedClis: server only saves id, type, name
|
|
253
|
+
managedClis: managedClis.map(c => ({
|
|
254
|
+
id: c.id, cliType: c.cliType, cliName: c.cliName,
|
|
255
|
+
})),
|
|
256
|
+
// managedAcps: server only saves id, type, name
|
|
257
|
+
managedAcps: managedAcps?.map((a: any) => ({
|
|
258
|
+
id: a.id, acpType: a.acpType, acpName: a.acpName,
|
|
259
|
+
})),
|
|
260
|
+
p2p: payload.p2p,
|
|
261
|
+
cdpConnected: payload.cdpConnected,
|
|
262
|
+
timestamp: now,
|
|
263
|
+
};
|
|
264
|
+
serverConn.sendMessage('status_report', wsPayload);
|
|
265
|
+
LOG.debug('Server', `sent status_report (${JSON.stringify(wsPayload).length} bytes)`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ─── P2P ─────────────────────────────────────────
|
|
270
|
+
|
|
271
|
+
private sendP2PPayload(payload: Record<string, any>): boolean {
|
|
272
|
+
const { timestamp: _ts, system: _sys, ...hashTarget } = payload;
|
|
273
|
+
if (hashTarget.machine) {
|
|
274
|
+
const { freeMem: _f, loadavg: _l, uptime: _u, ...stableMachine } = hashTarget.machine as any;
|
|
275
|
+
hashTarget.machine = stableMachine;
|
|
276
|
+
}
|
|
277
|
+
const h = this.simpleHash(JSON.stringify(hashTarget));
|
|
278
|
+
if (h !== this.lastP2PStatusHash) {
|
|
279
|
+
this.lastP2PStatusHash = h;
|
|
280
|
+
this.deps.p2p?.sendStatus(payload);
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private simpleHash(s: string): string {
|
|
287
|
+
let h = 0x811c9dc5;
|
|
288
|
+
for (let i = 0; i < s.length; i++) {
|
|
289
|
+
h ^= s.charCodeAt(i);
|
|
290
|
+
h = (h * 0x01000193) >>> 0;
|
|
291
|
+
}
|
|
292
|
+
return h.toString(36);
|
|
293
|
+
}
|
|
294
|
+
}
|