@adhdev/daemon-core 0.7.40 → 0.7.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-adapters/provider-cli-adapter.d.ts +1 -0
- package/dist/commands/cli-manager.d.ts +3 -0
- package/dist/config/config.d.ts +2 -22
- package/dist/index.js +310 -34
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +310 -34
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +3 -0
- package/dist/providers/extension-provider-instance.d.ts +1 -0
- package/dist/shared-types.d.ts +2 -0
- package/dist/status/normalize.js +67 -1
- package/dist/status/normalize.js.map +1 -1
- package/dist/status/normalize.mjs +67 -1
- package/dist/status/normalize.mjs.map +1 -1
- package/dist/status/reporter.d.ts +1 -0
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +1 -1
- package/src/agent-stream/forward.ts +21 -1
- package/src/agent-stream/poller.ts +6 -1
- package/src/cli-adapters/provider-cli-adapter.ts +37 -2
- package/src/commands/cli-manager.ts +45 -2
- package/src/commands/router.ts +1 -0
- package/src/commands/stream-commands.ts +14 -0
- package/src/config/config.d.ts +5 -50
- package/src/config/config.ts +71 -49
- package/src/config/workspaces.d.ts +1 -4
- package/src/providers/cli-provider-instance.ts +18 -5
- package/src/providers/contracts.ts +0 -1
- package/src/providers/extension-provider-instance.ts +27 -0
- package/src/providers/ide-provider-instance.ts +12 -0
- package/src/shared-types.ts +2 -0
- package/src/status/builders.ts +8 -1
- package/src/status/normalize.ts +85 -0
- package/src/status/reporter.ts +31 -2
package/src/config/config.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ADHDev Launcher — Configuration
|
|
3
|
-
*
|
|
4
|
-
* Manages launcher config,
|
|
3
|
+
*
|
|
4
|
+
* Manages launcher config, machine auth, and user preferences.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { homedir } from 'os';
|
|
@@ -16,8 +16,6 @@ export type { RecentActivityEntry } from './recent-activity.js';
|
|
|
16
16
|
export interface ADHDevConfig {
|
|
17
17
|
// Server connection
|
|
18
18
|
serverUrl: string;
|
|
19
|
-
apiToken: string | null;
|
|
20
|
-
connectionToken: string | null;
|
|
21
19
|
|
|
22
20
|
// Selected IDE (primary)
|
|
23
21
|
selectedIde: string | null;
|
|
@@ -28,16 +26,6 @@ export interface ADHDevConfig {
|
|
|
28
26
|
// Installed extensions
|
|
29
27
|
installedExtensions: string[];
|
|
30
28
|
|
|
31
|
-
// User preferences
|
|
32
|
-
autoConnect: boolean;
|
|
33
|
-
/**
|
|
34
|
-
* @deprecated Not read at runtime. Notification preferences are now managed by:
|
|
35
|
-
* - Web UI layer: useNotificationPrefs (localStorage)
|
|
36
|
-
* - Daemon layer: per-provider settings (approvalAlert, longGeneratingAlert)
|
|
37
|
-
* Kept for backward config compat — will be removed in v0.7+.
|
|
38
|
-
*/
|
|
39
|
-
notifications: boolean;
|
|
40
|
-
|
|
41
29
|
// Auth
|
|
42
30
|
userEmail: string | null;
|
|
43
31
|
userName: string | null;
|
|
@@ -46,9 +34,6 @@ export interface ADHDevConfig {
|
|
|
46
34
|
setupCompleted: boolean;
|
|
47
35
|
setupDate: string | null;
|
|
48
36
|
|
|
49
|
-
// Configured CLI agents
|
|
50
|
-
configuredCLIs: string[];
|
|
51
|
-
|
|
52
37
|
// Daemon: which IDEs to connect (empty = all)
|
|
53
38
|
enabledIdes: string[];
|
|
54
39
|
|
|
@@ -70,20 +55,15 @@ export interface ADHDevConfig {
|
|
|
70
55
|
/**
|
|
71
56
|
* Stable local machine ID (prefix: `mach_`) — generated locally on first run.
|
|
72
57
|
* Used as daemon instance key (`daemon_<machineId>`) and in status reports.
|
|
73
|
-
* NOT the same as the server-side D1 `machines.id` — see `registeredMachineId`.
|
|
74
58
|
*/
|
|
75
59
|
machineId?: string;
|
|
76
60
|
|
|
77
|
-
// Machine secret for server auth
|
|
61
|
+
// Machine secret for server auth
|
|
78
62
|
machineSecret?: string | null;
|
|
79
63
|
|
|
80
64
|
/**
|
|
81
65
|
* Server-side D1 `machines.id` — the row ID assigned when daemon registers via
|
|
82
|
-
* `POST /cli/complete`.
|
|
83
|
-
* (`DaemonConnection.machineId`, `StatusContext.machineId`).
|
|
84
|
-
*
|
|
85
|
-
* Naming differs from server-side `machineId` to avoid confusion with the local
|
|
86
|
-
* `config.machineId` (mach_ prefix) which is a different value.
|
|
66
|
+
* `POST /cli/complete`. Used as fallback for machine lookup on re-auth.
|
|
87
67
|
*
|
|
88
68
|
* @deprecated Legacy bridge field — will be removed after 2026-04-06.
|
|
89
69
|
* Modern auth flow uses `machineSecret` (adm_) to identify machines.
|
|
@@ -108,18 +88,13 @@ export interface ADHDevConfig {
|
|
|
108
88
|
|
|
109
89
|
const DEFAULT_CONFIG: ADHDevConfig = {
|
|
110
90
|
serverUrl: 'https://api.adhf.dev',
|
|
111
|
-
apiToken: null,
|
|
112
|
-
connectionToken: null,
|
|
113
91
|
selectedIde: null,
|
|
114
92
|
configuredIdes: [],
|
|
115
93
|
installedExtensions: [],
|
|
116
|
-
autoConnect: true,
|
|
117
|
-
notifications: true,
|
|
118
94
|
userEmail: null,
|
|
119
95
|
userName: null,
|
|
120
96
|
setupCompleted: false,
|
|
121
97
|
setupDate: null,
|
|
122
|
-
configuredCLIs: [],
|
|
123
98
|
enabledIdes: [],
|
|
124
99
|
workspaces: [],
|
|
125
100
|
defaultWorkspaceId: null,
|
|
@@ -137,6 +112,68 @@ const DEFAULT_CONFIG: ADHDevConfig = {
|
|
|
137
112
|
|
|
138
113
|
const MACHINE_ID_PREFIX = 'mach_';
|
|
139
114
|
|
|
115
|
+
function isPlainObject(value: unknown): value is Record<string, any> {
|
|
116
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function asStringArray(value: unknown): string[] {
|
|
120
|
+
if (!Array.isArray(value)) return [];
|
|
121
|
+
return value.filter((item): item is string => typeof item === 'string');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function asNullableString(value: unknown): string | null {
|
|
125
|
+
return typeof value === 'string' ? value : null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function asOptionalString(value: unknown): string | undefined {
|
|
129
|
+
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function asBoolean(value: unknown, fallback: boolean): boolean {
|
|
133
|
+
return typeof value === 'boolean' ? value : fallback;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function normalizeConfig(raw: unknown): ADHDevConfig & { activeWorkspaceId?: string | null } {
|
|
137
|
+
const parsed = isPlainObject(raw) ? raw : {};
|
|
138
|
+
const legacySessionReads = isPlainObject(parsed.recentSessionReads) ? parsed.recentSessionReads : {};
|
|
139
|
+
const sessionReads = isPlainObject(parsed.sessionReads) ? parsed.sessionReads : {};
|
|
140
|
+
const mergedSessionReads = Object.fromEntries(
|
|
141
|
+
Object.entries({ ...legacySessionReads, ...sessionReads })
|
|
142
|
+
.filter(([, value]) => typeof value === 'number' && Number.isFinite(value))
|
|
143
|
+
);
|
|
144
|
+
const sessionReadMarkers = Object.fromEntries(
|
|
145
|
+
Object.entries(isPlainObject(parsed.sessionReadMarkers) ? parsed.sessionReadMarkers : {})
|
|
146
|
+
.filter(([, value]) => typeof value === 'string')
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
serverUrl: typeof parsed.serverUrl === 'string' && parsed.serverUrl.trim()
|
|
151
|
+
? parsed.serverUrl
|
|
152
|
+
: DEFAULT_CONFIG.serverUrl,
|
|
153
|
+
selectedIde: asNullableString(parsed.selectedIde),
|
|
154
|
+
configuredIdes: asStringArray(parsed.configuredIdes),
|
|
155
|
+
installedExtensions: asStringArray(parsed.installedExtensions),
|
|
156
|
+
userEmail: asNullableString(parsed.userEmail),
|
|
157
|
+
userName: asNullableString(parsed.userName),
|
|
158
|
+
setupCompleted: asBoolean(parsed.setupCompleted, DEFAULT_CONFIG.setupCompleted),
|
|
159
|
+
setupDate: asNullableString(parsed.setupDate),
|
|
160
|
+
enabledIdes: asStringArray(parsed.enabledIdes),
|
|
161
|
+
workspaces: Array.isArray(parsed.workspaces) ? parsed.workspaces as WorkspaceEntry[] : [],
|
|
162
|
+
defaultWorkspaceId: asNullableString(parsed.defaultWorkspaceId) ?? asNullableString(parsed.activeWorkspaceId),
|
|
163
|
+
recentActivity: Array.isArray(parsed.recentActivity) ? parsed.recentActivity as RecentActivityEntry[] : [],
|
|
164
|
+
sessionReads: mergedSessionReads,
|
|
165
|
+
sessionReadMarkers,
|
|
166
|
+
machineNickname: asNullableString(parsed.machineNickname),
|
|
167
|
+
machineId: asOptionalString(parsed.machineId),
|
|
168
|
+
machineSecret: parsed.machineSecret === null ? null : asOptionalString(parsed.machineSecret),
|
|
169
|
+
registeredMachineId: asOptionalString(parsed.registeredMachineId),
|
|
170
|
+
providerSettings: isPlainObject(parsed.providerSettings) ? parsed.providerSettings : {},
|
|
171
|
+
ideSettings: isPlainObject(parsed.ideSettings) ? parsed.ideSettings : {},
|
|
172
|
+
disableUpstream: asBoolean(parsed.disableUpstream, DEFAULT_CONFIG.disableUpstream ?? false),
|
|
173
|
+
providerDir: asOptionalString(parsed.providerDir),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
140
177
|
export function generateMachineId(): string {
|
|
141
178
|
return `${MACHINE_ID_PREFIX}${randomUUID().replace(/-/g, '')}`;
|
|
142
179
|
}
|
|
@@ -201,14 +238,10 @@ export function loadConfig(): ADHDevConfig {
|
|
|
201
238
|
try {
|
|
202
239
|
const raw = readFileSync(configPath, 'utf-8');
|
|
203
240
|
const parsed = JSON.parse(raw);
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
(merged as ADHDevConfig).defaultWorkspaceId = merged.activeWorkspaceId;
|
|
207
|
-
}
|
|
208
|
-
delete (merged as any).activeWorkspaceId;
|
|
209
|
-
const ensured = ensureMachineId(merged);
|
|
241
|
+
const normalizedInput = normalizeConfig(parsed);
|
|
242
|
+
const ensured = ensureMachineId(normalizedInput);
|
|
210
243
|
const normalized = ensured.config as ADHDevConfig & { activeWorkspaceId?: string | null };
|
|
211
|
-
if (ensured.changed) {
|
|
244
|
+
if (ensured.changed || JSON.stringify(parsed) !== JSON.stringify(normalized)) {
|
|
212
245
|
try {
|
|
213
246
|
saveConfig(normalized);
|
|
214
247
|
} catch { /* ignore */ }
|
|
@@ -226,12 +259,13 @@ export function loadConfig(): ADHDevConfig {
|
|
|
226
259
|
export function saveConfig(config: ADHDevConfig): void {
|
|
227
260
|
const configPath = getConfigPath();
|
|
228
261
|
const dir = getConfigDir();
|
|
262
|
+
const normalized = normalizeConfig(config);
|
|
229
263
|
|
|
230
264
|
if (!existsSync(dir)) {
|
|
231
265
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
232
266
|
}
|
|
233
267
|
|
|
234
|
-
writeFileSync(configPath, JSON.stringify(
|
|
268
|
+
writeFileSync(configPath, JSON.stringify(normalized, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
235
269
|
try { chmodSync(configPath, 0o600); } catch { /* Windows etc. not supported */ }
|
|
236
270
|
}
|
|
237
271
|
|
|
@@ -276,15 +310,3 @@ export function isSetupComplete(): boolean {
|
|
|
276
310
|
export function resetConfig(): void {
|
|
277
311
|
saveConfig({ ...DEFAULT_CONFIG });
|
|
278
312
|
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Generate a connection token for server authentication
|
|
282
|
-
*/
|
|
283
|
-
export function generateConnectionToken(): string {
|
|
284
|
-
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
285
|
-
let token = 'db_';
|
|
286
|
-
for (let i = 0; i < 32; i++) {
|
|
287
|
-
token += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
288
|
-
}
|
|
289
|
-
return token;
|
|
290
|
-
}
|
|
@@ -17,10 +17,7 @@ export declare function validateWorkspacePath(absPath: string): {
|
|
|
17
17
|
};
|
|
18
18
|
/** Default workspace label from path */
|
|
19
19
|
export declare function defaultWorkspaceLabel(absPath: string): string;
|
|
20
|
-
|
|
21
|
-
* Ensure config.workspaces exists; seed from recentCliWorkspaces once (same paths).
|
|
22
|
-
*/
|
|
23
|
-
export declare function migrateWorkspacesFromRecent(config: ADHDevConfig): ADHDevConfig;
|
|
20
|
+
|
|
24
21
|
export declare function getDefaultWorkspacePath(config: ADHDevConfig): string | null;
|
|
25
22
|
export declare function getWorkspaceState(config: ADHDevConfig): {
|
|
26
23
|
workspaces: WorkspaceEntry[];
|
|
@@ -33,6 +33,8 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
33
33
|
private historyWriter: ChatHistoryWriter;
|
|
34
34
|
readonly instanceId: string;
|
|
35
35
|
|
|
36
|
+
private presentationMode: 'terminal' | 'chat';
|
|
37
|
+
|
|
36
38
|
constructor(
|
|
37
39
|
private provider: ProviderModule,
|
|
38
40
|
private workingDir: string,
|
|
@@ -42,6 +44,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
42
44
|
) {
|
|
43
45
|
this.type = provider.type;
|
|
44
46
|
this.instanceId = instanceId || crypto.randomUUID();
|
|
47
|
+
this.presentationMode = 'terminal';
|
|
45
48
|
this.adapter = new ProviderCliAdapter(provider as any as CliProviderModule, workingDir, cliArgs, transportFactory);
|
|
46
49
|
this.monitor = new StatusMonitor();
|
|
47
50
|
this.historyWriter = new ChatHistoryWriter();
|
|
@@ -84,6 +87,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
84
87
|
|
|
85
88
|
getState(): ProviderState {
|
|
86
89
|
const adapterStatus = this.adapter.getStatus();
|
|
90
|
+
const parsedStatus = this.adapter.getScriptParsedStatus?.() || null;
|
|
87
91
|
const runtime = this.adapter.getRuntimeMetadata();
|
|
88
92
|
|
|
89
93
|
const dirName = this.workingDir.split('/').filter(Boolean).pop() || 'session';
|
|
@@ -102,13 +106,13 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
102
106
|
name: this.provider.name,
|
|
103
107
|
category: 'cli',
|
|
104
108
|
status: adapterStatus.status,
|
|
105
|
-
mode:
|
|
109
|
+
mode: this.presentationMode,
|
|
106
110
|
activeChat: {
|
|
107
111
|
id: `${this.type}_${this.workingDir}`,
|
|
108
|
-
title: `${this.provider.name} · ${dirName}`,
|
|
109
|
-
status: adapterStatus.status,
|
|
110
|
-
messages: [],
|
|
111
|
-
activeModal: adapterStatus.activeModal,
|
|
112
|
+
title: parsedStatus?.title || `${this.provider.name} · ${dirName}`,
|
|
113
|
+
status: parsedStatus?.status || adapterStatus.status,
|
|
114
|
+
messages: Array.isArray(parsedStatus?.messages) ? parsedStatus.messages : [],
|
|
115
|
+
activeModal: parsedStatus?.activeModal ?? adapterStatus.activeModal,
|
|
112
116
|
terminalHistory: adapterStatus.terminalHistory,
|
|
113
117
|
inputContent: '',
|
|
114
118
|
},
|
|
@@ -131,6 +135,15 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
131
135
|
};
|
|
132
136
|
}
|
|
133
137
|
|
|
138
|
+
setPresentationMode(mode: 'terminal' | 'chat'): void {
|
|
139
|
+
if (this.presentationMode === mode) return;
|
|
140
|
+
this.presentationMode = mode;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
getPresentationMode(): 'terminal' | 'chat' {
|
|
144
|
+
return this.presentationMode;
|
|
145
|
+
}
|
|
146
|
+
|
|
134
147
|
onEvent(event: string, data?: any): void {
|
|
135
148
|
if (event === 'send_message' && data?.text) {
|
|
136
149
|
void this.adapter.sendMessage(data.text).catch((e: any) => {
|
|
@@ -111,6 +111,8 @@ export class ExtensionProviderInstance implements ProviderInstance {
|
|
|
111
111
|
this.detectTransition(newStatus, data);
|
|
112
112
|
this.currentStatus = newStatus;
|
|
113
113
|
}
|
|
114
|
+
} else if (event === 'stream_reset') {
|
|
115
|
+
this.resetStreamState();
|
|
114
116
|
} else if (event === 'extension_connected') {
|
|
115
117
|
this.ideType = data?.ideType || '';
|
|
116
118
|
// Maintain instanceId UUID — do not overwrite
|
|
@@ -208,4 +210,29 @@ export class ExtensionProviderInstance implements ProviderInstance {
|
|
|
208
210
|
: this.chatTitle;
|
|
209
211
|
return title || this.agentName || this.provider.name;
|
|
210
212
|
}
|
|
213
|
+
|
|
214
|
+
private resetStreamState(): void {
|
|
215
|
+
if (this.currentStatus !== 'idle') {
|
|
216
|
+
this.detectTransition('idle', {
|
|
217
|
+
title: this.chatTitle,
|
|
218
|
+
agentName: this.agentName,
|
|
219
|
+
extensionId: this.extensionId,
|
|
220
|
+
messages: this.messages,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
this.agentStreams = [];
|
|
224
|
+
this.messages = [];
|
|
225
|
+
this.activeModal = null;
|
|
226
|
+
this.currentModel = '';
|
|
227
|
+
this.currentMode = '';
|
|
228
|
+
this.controlValues = {};
|
|
229
|
+
this.currentStatus = 'idle';
|
|
230
|
+
this.chatId = null;
|
|
231
|
+
this.chatTitle = null;
|
|
232
|
+
this.agentName = '';
|
|
233
|
+
this.extensionId = '';
|
|
234
|
+
this.lastAgentStatus = 'idle';
|
|
235
|
+
this.generatingStartedAt = 0;
|
|
236
|
+
this.monitor.reset();
|
|
237
|
+
}
|
|
211
238
|
}
|
|
@@ -140,12 +140,24 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
140
140
|
} else if (event === 'cdp_disconnected') {
|
|
141
141
|
this.cachedChat = null;
|
|
142
142
|
this.currentStatus = 'idle';
|
|
143
|
+
for (const ext of this.extensions.values()) {
|
|
144
|
+
ext.onEvent('stream_reset');
|
|
145
|
+
}
|
|
143
146
|
} else if (event === 'stream_update') {
|
|
144
147
|
// Forward to Extension
|
|
145
148
|
const extType = data?.extensionType;
|
|
146
149
|
if (extType && this.extensions.has(extType)) {
|
|
147
150
|
this.extensions.get(extType)!.onEvent('stream_update', data);
|
|
148
151
|
}
|
|
152
|
+
} else if (event === 'stream_reset') {
|
|
153
|
+
const extType = data?.extensionType;
|
|
154
|
+
if (extType && this.extensions.has(extType)) {
|
|
155
|
+
this.extensions.get(extType)!.onEvent('stream_reset');
|
|
156
|
+
}
|
|
157
|
+
} else if (event === 'stream_reset_all') {
|
|
158
|
+
for (const ext of this.extensions.values()) {
|
|
159
|
+
ext.onEvent('stream_reset');
|
|
160
|
+
}
|
|
149
161
|
}
|
|
150
162
|
}
|
|
151
163
|
|
package/src/shared-types.ts
CHANGED
|
@@ -102,6 +102,8 @@ export interface SessionEntry {
|
|
|
102
102
|
runtimeKey?: string;
|
|
103
103
|
runtimeDisplayName?: string;
|
|
104
104
|
runtimeWorkspaceLabel?: string;
|
|
105
|
+
/** CLI only: active presentation mode */
|
|
106
|
+
mode?: 'terminal' | 'chat';
|
|
105
107
|
runtimeWriteOwner?: RuntimeWriteOwner | null;
|
|
106
108
|
runtimeAttachedClients?: RuntimeAttachedClient[];
|
|
107
109
|
resume?: ProviderResumeCapability;
|
package/src/status/builders.ts
CHANGED
|
@@ -167,6 +167,12 @@ const PTY_SESSION_CAPABILITIES: SessionCapability[] = [
|
|
|
167
167
|
'resize_terminal',
|
|
168
168
|
];
|
|
169
169
|
|
|
170
|
+
const CLI_CHAT_SESSION_CAPABILITIES: SessionCapability[] = [
|
|
171
|
+
'read_chat',
|
|
172
|
+
'send_message',
|
|
173
|
+
'resolve_action',
|
|
174
|
+
];
|
|
175
|
+
|
|
170
176
|
const ACP_SESSION_CAPABILITIES: SessionCapability[] = [
|
|
171
177
|
'read_chat',
|
|
172
178
|
'send_message',
|
|
@@ -265,9 +271,10 @@ function buildCliSession(state: CliProviderState): SessionEntry {
|
|
|
265
271
|
runtimeWorkspaceLabel: state.runtime?.workspaceLabel,
|
|
266
272
|
runtimeWriteOwner: state.runtime?.writeOwner || null,
|
|
267
273
|
runtimeAttachedClients: state.runtime?.attachedClients || [],
|
|
274
|
+
mode: state.mode,
|
|
268
275
|
resume: state.resume,
|
|
269
276
|
activeChat,
|
|
270
|
-
capabilities: PTY_SESSION_CAPABILITIES,
|
|
277
|
+
capabilities: state.mode === 'terminal' ? PTY_SESSION_CAPABILITIES : CLI_CHAT_SESSION_CAPABILITIES,
|
|
271
278
|
controlValues: state.controlValues,
|
|
272
279
|
providerControls: buildFallbackControls(
|
|
273
280
|
state.providerControls
|
package/src/status/normalize.ts
CHANGED
|
@@ -20,6 +20,78 @@ const WORKING_STATUSES = new Set([
|
|
|
20
20
|
'active',
|
|
21
21
|
]);
|
|
22
22
|
|
|
23
|
+
// Status snapshots are sent over P2P every 5s, so keep only a recent live window here.
|
|
24
|
+
// Older history is fetched on demand via `chat_history`, and CLI terminals stream via runtime events.
|
|
25
|
+
const STATUS_ACTIVE_CHAT_MESSAGE_LIMIT = 60;
|
|
26
|
+
const STATUS_ACTIVE_CHAT_TOTAL_BYTES_LIMIT = 96 * 1024;
|
|
27
|
+
const STATUS_ACTIVE_CHAT_STRING_LIMIT = 4 * 1024;
|
|
28
|
+
const STATUS_ACTIVE_CHAT_FALLBACK_STRING_LIMIT = 1024;
|
|
29
|
+
const STATUS_TERMINAL_HISTORY_LIMIT = 8 * 1024;
|
|
30
|
+
const STATUS_INPUT_CONTENT_LIMIT = 2 * 1024;
|
|
31
|
+
const STATUS_MODAL_MESSAGE_LIMIT = 2 * 1024;
|
|
32
|
+
const STATUS_MODAL_BUTTON_LIMIT = 120;
|
|
33
|
+
|
|
34
|
+
function truncateString(value: string, maxChars: number): string {
|
|
35
|
+
if (value.length <= maxChars) return value;
|
|
36
|
+
if (maxChars <= 12) return value.slice(0, Math.max(0, maxChars));
|
|
37
|
+
return `${value.slice(0, maxChars - 12)}...[truncated]`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function truncateStringTail(value: string, maxChars: number): string {
|
|
41
|
+
if (value.length <= maxChars) return value;
|
|
42
|
+
if (maxChars <= 12) return value.slice(value.length - Math.max(0, maxChars));
|
|
43
|
+
return `...[truncated]${value.slice(value.length - (maxChars - 12))}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function trimStructuredStrings(value: unknown, maxChars: number): unknown {
|
|
47
|
+
if (typeof value === 'string') return truncateString(value, maxChars);
|
|
48
|
+
if (Array.isArray(value)) return value.map((item) => trimStructuredStrings(item, maxChars));
|
|
49
|
+
if (!value || typeof value !== 'object') return value;
|
|
50
|
+
return Object.fromEntries(
|
|
51
|
+
Object.entries(value).map(([key, nested]) => [key, trimStructuredStrings(nested, maxChars)]),
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function estimateBytes(value: unknown): number {
|
|
56
|
+
try {
|
|
57
|
+
return JSON.stringify(value).length;
|
|
58
|
+
} catch {
|
|
59
|
+
return String(value ?? '').length;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function trimMessageForStatus(message: unknown, stringLimit: number): unknown {
|
|
64
|
+
if (!message || typeof message !== 'object') return message;
|
|
65
|
+
return trimStructuredStrings(message, stringLimit);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function trimMessagesForStatus(messages: unknown[] | null | undefined): unknown[] {
|
|
69
|
+
if (!Array.isArray(messages) || messages.length === 0) return [];
|
|
70
|
+
|
|
71
|
+
const recent = messages.slice(-STATUS_ACTIVE_CHAT_MESSAGE_LIMIT);
|
|
72
|
+
const kept: unknown[] = [];
|
|
73
|
+
let totalBytes = 0;
|
|
74
|
+
|
|
75
|
+
for (let i = recent.length - 1; i >= 0; i -= 1) {
|
|
76
|
+
let normalized = trimMessageForStatus(recent[i], STATUS_ACTIVE_CHAT_STRING_LIMIT);
|
|
77
|
+
let size = estimateBytes(normalized);
|
|
78
|
+
|
|
79
|
+
if (size > STATUS_ACTIVE_CHAT_TOTAL_BYTES_LIMIT) {
|
|
80
|
+
normalized = trimMessageForStatus(recent[i], STATUS_ACTIVE_CHAT_FALLBACK_STRING_LIMIT);
|
|
81
|
+
size = estimateBytes(normalized);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (kept.length > 0 && (totalBytes + size) > STATUS_ACTIVE_CHAT_TOTAL_BYTES_LIMIT) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
kept.push(normalized);
|
|
89
|
+
totalBytes += size;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return kept.reverse();
|
|
93
|
+
}
|
|
94
|
+
|
|
23
95
|
function hasApprovalButtons(activeModal?: { buttons?: unknown[] | null } | null): boolean {
|
|
24
96
|
return (activeModal?.buttons?.length ?? 0) > 0;
|
|
25
97
|
}
|
|
@@ -60,5 +132,18 @@ export function normalizeActiveChatData<T extends ActiveChatData | null | undefi
|
|
|
60
132
|
return {
|
|
61
133
|
...activeChat,
|
|
62
134
|
status: normalizeManagedStatus(activeChat.status, { activeModal: activeChat.activeModal }),
|
|
135
|
+
messages: trimMessagesForStatus(activeChat.messages) as T extends { messages: infer M } ? M : never,
|
|
136
|
+
activeModal: activeChat.activeModal ? {
|
|
137
|
+
message: truncateString(activeChat.activeModal.message || '', STATUS_MODAL_MESSAGE_LIMIT),
|
|
138
|
+
buttons: (activeChat.activeModal.buttons || []).map((button) =>
|
|
139
|
+
truncateString(String(button || ''), STATUS_MODAL_BUTTON_LIMIT)
|
|
140
|
+
),
|
|
141
|
+
} : activeChat.activeModal,
|
|
142
|
+
terminalHistory: activeChat.terminalHistory
|
|
143
|
+
? truncateStringTail(activeChat.terminalHistory, STATUS_TERMINAL_HISTORY_LIMIT)
|
|
144
|
+
: activeChat.terminalHistory,
|
|
145
|
+
inputContent: activeChat.inputContent
|
|
146
|
+
? truncateString(activeChat.inputContent, STATUS_INPUT_CONTENT_LIMIT)
|
|
147
|
+
: activeChat.inputContent,
|
|
63
148
|
} as T;
|
|
64
149
|
}
|
package/src/status/reporter.ts
CHANGED
|
@@ -113,6 +113,26 @@ export class DaemonStatusReporter {
|
|
|
113
113
|
return new Date().toISOString().slice(11, 23); // HH:mm:ss.SSS
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
private summarizeLargePayloadSessions(payload: Record<string, any>): string {
|
|
117
|
+
const sessions = Array.isArray(payload.sessions) ? payload.sessions : [];
|
|
118
|
+
return sessions
|
|
119
|
+
.map((session: any) => ({
|
|
120
|
+
id: String(session?.id || ''),
|
|
121
|
+
providerType: String(session?.providerType || ''),
|
|
122
|
+
bytes: (() => {
|
|
123
|
+
try {
|
|
124
|
+
return JSON.stringify(session).length;
|
|
125
|
+
} catch {
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
})(),
|
|
129
|
+
}))
|
|
130
|
+
.sort((a, b) => b.bytes - a.bytes)
|
|
131
|
+
.slice(0, 3)
|
|
132
|
+
.map((session) => `${session.providerType || 'unknown'}:${session.id}=${session.bytes}b`)
|
|
133
|
+
.join(', ');
|
|
134
|
+
}
|
|
135
|
+
|
|
116
136
|
async sendUnifiedStatusReport(opts?: { p2pOnly?: boolean }): Promise<void> {
|
|
117
137
|
const { serverConn, p2p } = this.deps;
|
|
118
138
|
if (!serverConn?.isConnected()) return;
|
|
@@ -179,10 +199,17 @@ export class DaemonStatusReporter {
|
|
|
179
199
|
connectedExtensions: [],
|
|
180
200
|
};
|
|
181
201
|
|
|
182
|
-
|
|
202
|
+
// ═══ P2P transmit ═══
|
|
203
|
+
const payloadBytes = JSON.stringify(payload).length;
|
|
183
204
|
const p2pSent = this.sendP2PPayload(payload);
|
|
184
205
|
if (p2pSent) {
|
|
185
|
-
LOG.debug('P2P', `sent (${
|
|
206
|
+
LOG.debug('P2P', `sent (${payloadBytes} bytes)`);
|
|
207
|
+
if (payloadBytes > 256 * 1024) {
|
|
208
|
+
LOG.warn(
|
|
209
|
+
'P2P',
|
|
210
|
+
`large status payload (${payloadBytes} bytes) top sessions: ${this.summarizeLargePayloadSessions(payload) || 'n/a'}`,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
186
213
|
}
|
|
187
214
|
|
|
188
215
|
// ═══ Server transmit (minimal routing meta only) ═══
|
|
@@ -215,6 +242,8 @@ export class DaemonStatusReporter {
|
|
|
215
242
|
})),
|
|
216
243
|
p2p: payload.p2p,
|
|
217
244
|
timestamp: now,
|
|
245
|
+
detectedIdes: payload.detectedIdes,
|
|
246
|
+
availableProviders: payload.availableProviders,
|
|
218
247
|
};
|
|
219
248
|
serverConn.sendMessage('status_report', wsPayload);
|
|
220
249
|
LOG.debug('Server', `sent status_report (${JSON.stringify(wsPayload).length} bytes)`);
|