@adhdev/daemon-core 0.7.41 → 0.7.43
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 +3 -4
- package/dist/cli-adapters/pty-transport.d.ts +1 -0
- package/dist/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +4 -0
- package/dist/cli-adapters/terminal-backends/types.d.ts +4 -0
- package/dist/cli-adapters/terminal-backends/xterm-backend.d.ts +4 -0
- package/dist/cli-adapters/terminal-screen.d.ts +4 -0
- package/dist/commands/cli-manager.d.ts +4 -2
- package/dist/config/chat-history.d.ts +0 -3
- package/dist/config/config.d.ts +2 -22
- package/dist/index.js +377 -195
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +377 -195
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +4 -10
- package/dist/providers/contracts.d.ts +0 -79
- package/dist/providers/extension-provider-instance.d.ts +1 -0
- package/dist/providers/provider-instance.d.ts +0 -3
- package/dist/shared-types.d.ts +1 -3
- package/dist/status/normalize.js +60 -1
- package/dist/status/normalize.js.map +1 -1
- package/dist/status/normalize.mjs +60 -1
- package/dist/status/normalize.mjs.map +1 -1
- package/dist/status/reporter.d.ts +1 -0
- package/node_modules/@adhdev/session-host-core/dist/index.js +2 -2
- package/node_modules/@adhdev/session-host-core/dist/index.js.map +1 -1
- package/node_modules/@adhdev/session-host-core/dist/index.mjs +2 -2
- package/node_modules/@adhdev/session-host-core/dist/index.mjs.map +1 -1
- 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 +115 -71
- package/src/cli-adapters/pty-transport.ts +2 -0
- package/src/cli-adapters/session-host-transport.ts +1 -0
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +5 -0
- package/src/cli-adapters/terminal-backends/types.ts +1 -0
- package/src/cli-adapters/terminal-backends/xterm-backend.ts +10 -0
- package/src/cli-adapters/terminal-screen.ts +4 -0
- package/src/commands/cli-manager.ts +44 -39
- package/src/commands/router.ts +1 -0
- package/src/commands/stream-commands.ts +14 -0
- package/src/config/chat-history.ts +3 -55
- 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 -42
- package/src/providers/contracts.ts +0 -81
- package/src/providers/extension-provider-instance.ts +27 -0
- package/src/providers/ide-provider-instance.ts +12 -0
- package/src/providers/provider-instance.d.ts +0 -1
- package/src/providers/provider-instance.ts +0 -3
- package/src/shared-types.ts +1 -3
- package/src/status/builders.ts +7 -2
- package/src/status/normalize.ts +81 -0
- package/src/status/reporter.ts +31 -2
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import * as path from 'path';
|
|
9
9
|
import * as crypto from 'crypto';
|
|
10
|
-
import type { ProviderModule
|
|
10
|
+
import type { ProviderModule } from './contracts.js';
|
|
11
11
|
import type { ProviderInstance, ProviderState, ProviderEvent, InstanceContext } from './provider-instance.js';
|
|
12
12
|
import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
|
|
13
13
|
import type { CliProviderModule } from '../cli-adapters/provider-cli-adapter.js';
|
|
@@ -33,8 +33,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
33
33
|
private historyWriter: ChatHistoryWriter;
|
|
34
34
|
readonly instanceId: string;
|
|
35
35
|
|
|
36
|
-
private
|
|
37
|
-
private resolvedOutputFormat: 'terminal' | 'stream-json';
|
|
36
|
+
private presentationMode: 'terminal' | 'chat';
|
|
38
37
|
|
|
39
38
|
constructor(
|
|
40
39
|
private provider: ProviderModule,
|
|
@@ -42,37 +41,15 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
42
41
|
private cliArgs: string[] = [],
|
|
43
42
|
instanceId?: string,
|
|
44
43
|
transportFactory?: PtyTransportFactory,
|
|
45
|
-
launchModeId?: string,
|
|
46
44
|
) {
|
|
47
45
|
this.type = provider.type;
|
|
48
46
|
this.instanceId = instanceId || crypto.randomUUID();
|
|
49
|
-
this.
|
|
50
|
-
this.resolvedOutputFormat = this.resolveOutputFormat();
|
|
47
|
+
this.presentationMode = 'chat';
|
|
51
48
|
this.adapter = new ProviderCliAdapter(provider as any as CliProviderModule, workingDir, cliArgs, transportFactory);
|
|
52
49
|
this.monitor = new StatusMonitor();
|
|
53
50
|
this.historyWriter = new ChatHistoryWriter();
|
|
54
51
|
}
|
|
55
52
|
|
|
56
|
-
/**
|
|
57
|
-
* Determine output rendering format from:
|
|
58
|
-
* 1. launchMode.outputFormat (explicit override)
|
|
59
|
-
* 2. launchOptions[].outputFormatMap — check actual args for matching values
|
|
60
|
-
* 3. Default: 'terminal'
|
|
61
|
-
*/
|
|
62
|
-
private resolveOutputFormat(): 'terminal' | 'stream-json' {
|
|
63
|
-
if (this.launchMode?.outputFormat) return this.launchMode.outputFormat;
|
|
64
|
-
if (this.provider.launchOptions?.length) {
|
|
65
|
-
for (const opt of this.provider.launchOptions) {
|
|
66
|
-
if (!opt.outputFormatMap) continue;
|
|
67
|
-
// Check if any cliArg matches a value with an outputFormatMap entry
|
|
68
|
-
for (const [val, fmt] of Object.entries(opt.outputFormatMap)) {
|
|
69
|
-
if (this.cliArgs.includes(val)) return fmt;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return 'terminal';
|
|
74
|
-
}
|
|
75
|
-
|
|
76
53
|
// ─── Lifecycle ─────────────────────────────────
|
|
77
54
|
|
|
78
55
|
async init(context: InstanceContext): Promise<void> {
|
|
@@ -110,33 +87,23 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
110
87
|
|
|
111
88
|
getState(): ProviderState {
|
|
112
89
|
const adapterStatus = this.adapter.getStatus();
|
|
90
|
+
const parsedStatus = this.adapter.getScriptParsedStatus?.() || null;
|
|
113
91
|
const runtime = this.adapter.getRuntimeMetadata();
|
|
114
92
|
|
|
115
93
|
const dirName = this.workingDir.split('/').filter(Boolean).pop() || 'session';
|
|
116
94
|
|
|
117
|
-
if (adapterStatus.terminalHistory?.trim()) {
|
|
118
|
-
this.historyWriter.appendTerminalHistory(
|
|
119
|
-
this.type,
|
|
120
|
-
adapterStatus.terminalHistory,
|
|
121
|
-
`${this.provider.name} · ${dirName}`,
|
|
122
|
-
this.instanceId,
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
95
|
return {
|
|
127
96
|
type: this.type,
|
|
128
97
|
name: this.provider.name,
|
|
129
98
|
category: 'cli',
|
|
130
99
|
status: adapterStatus.status,
|
|
131
|
-
mode: this.
|
|
132
|
-
launchMode: this.launchMode?.id,
|
|
100
|
+
mode: this.presentationMode,
|
|
133
101
|
activeChat: {
|
|
134
102
|
id: `${this.type}_${this.workingDir}`,
|
|
135
|
-
title: `${this.provider.name} · ${dirName}`,
|
|
136
|
-
status: adapterStatus.status,
|
|
137
|
-
messages: [],
|
|
138
|
-
activeModal: adapterStatus.activeModal,
|
|
139
|
-
terminalHistory: adapterStatus.terminalHistory,
|
|
103
|
+
title: parsedStatus?.title || `${this.provider.name} · ${dirName}`,
|
|
104
|
+
status: parsedStatus?.status || adapterStatus.status,
|
|
105
|
+
messages: Array.isArray(parsedStatus?.messages) ? parsedStatus.messages : [],
|
|
106
|
+
activeModal: parsedStatus?.activeModal ?? adapterStatus.activeModal,
|
|
140
107
|
inputContent: '',
|
|
141
108
|
},
|
|
142
109
|
workspace: this.workingDir,
|
|
@@ -158,6 +125,15 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
158
125
|
};
|
|
159
126
|
}
|
|
160
127
|
|
|
128
|
+
setPresentationMode(mode: 'terminal' | 'chat'): void {
|
|
129
|
+
if (this.presentationMode === mode) return;
|
|
130
|
+
this.presentationMode = mode;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
getPresentationMode(): 'terminal' | 'chat' {
|
|
134
|
+
return this.presentationMode;
|
|
135
|
+
}
|
|
136
|
+
|
|
161
137
|
onEvent(event: string, data?: any): void {
|
|
162
138
|
if (event === 'send_message' && data?.text) {
|
|
163
139
|
void this.adapter.sendMessage(data.text).catch((e: any) => {
|
|
@@ -317,20 +317,6 @@ export interface ProviderModule {
|
|
|
317
317
|
shell?: boolean;
|
|
318
318
|
env?: Record<string, string>;
|
|
319
319
|
};
|
|
320
|
-
/**
|
|
321
|
-
* Configurable options shown at session launch time (schema declaration).
|
|
322
|
-
* The frontend renders these as a launch config UI.
|
|
323
|
-
* Values are passed to launchArgBuilder to produce the final args.
|
|
324
|
-
*/
|
|
325
|
-
launchOptions?: ProviderLaunchOption[];
|
|
326
|
-
/**
|
|
327
|
-
* Builds extra spawn args from user-selected launch option values.
|
|
328
|
-
* Called with the merged defaults + mode preset + user overrides.
|
|
329
|
-
* When defined, takes precedence over launchMode.extraArgs.
|
|
330
|
-
*/
|
|
331
|
-
launchArgBuilder?: (options: Record<string, string | boolean | number>) => string[];
|
|
332
|
-
/** Named presets — shortcuts that set launchOption values in bulk */
|
|
333
|
-
launchModes?: ProviderLaunchMode[];
|
|
334
320
|
patterns?: {
|
|
335
321
|
prompt?: RegExp[];
|
|
336
322
|
generating?: RegExp[];
|
|
@@ -407,72 +393,6 @@ export interface ProviderModule {
|
|
|
407
393
|
auth?: AcpAuthMethod[];
|
|
408
394
|
}
|
|
409
395
|
|
|
410
|
-
// ─── CLI Launch Options (individual configurable flags) ────────────────
|
|
411
|
-
|
|
412
|
-
export type ProviderLaunchOptionType = 'select' | 'boolean' | 'string' | 'number';
|
|
413
|
-
|
|
414
|
-
/**
|
|
415
|
-
* A single configurable option exposed at session launch time.
|
|
416
|
-
* Providers declare these so the frontend can render a launch config UI.
|
|
417
|
-
* The final args are built by launchArgBuilder(selectedValues).
|
|
418
|
-
*
|
|
419
|
-
* Example — Claude Code:
|
|
420
|
-
* { id: 'outputFormat', type: 'select', options: [
|
|
421
|
-
* { value: 'terminal', label: 'Terminal' },
|
|
422
|
-
* { value: 'stream-json', label: 'Chat' },
|
|
423
|
-
* ], default: 'terminal' }
|
|
424
|
-
*/
|
|
425
|
-
export interface ProviderLaunchOption {
|
|
426
|
-
/** Unique identifier — key used in launchArgBuilder options map */
|
|
427
|
-
id: string;
|
|
428
|
-
/** Display label */
|
|
429
|
-
name: string;
|
|
430
|
-
description?: string;
|
|
431
|
-
type: ProviderLaunchOptionType;
|
|
432
|
-
/** Options for 'select' type */
|
|
433
|
-
options?: { value: string; label: string; description?: string }[];
|
|
434
|
-
/** Default value — applied when not explicitly set */
|
|
435
|
-
default?: string | boolean | number;
|
|
436
|
-
/**
|
|
437
|
-
* Maps specific values of this option to a frontend rendering hint.
|
|
438
|
-
* e.g. { 'stream-json': 'stream-json' } tells the UI to switch to Chat mode.
|
|
439
|
-
*/
|
|
440
|
-
outputFormatMap?: Record<string, 'terminal' | 'stream-json'>;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* A named launch preset — shorthand for a specific set of launchOption values.
|
|
445
|
-
* When selected, its `options` are merged with user-configured values before
|
|
446
|
-
* calling launchArgBuilder. Falls back to `extraArgs` if no launchArgBuilder defined.
|
|
447
|
-
*/
|
|
448
|
-
export interface ProviderLaunchMode {
|
|
449
|
-
/** Unique mode identifier (e.g. 'terminal', 'chat') */
|
|
450
|
-
id: string;
|
|
451
|
-
/** Display name */
|
|
452
|
-
name: string;
|
|
453
|
-
description?: string;
|
|
454
|
-
/**
|
|
455
|
-
* Preset option values — merged over defaults before calling launchArgBuilder.
|
|
456
|
-
* Keys correspond to ProviderLaunchOption.id.
|
|
457
|
-
*/
|
|
458
|
-
options?: Record<string, string | boolean | number>;
|
|
459
|
-
/**
|
|
460
|
-
* Fallback: raw args appended when no launchArgBuilder is defined.
|
|
461
|
-
* Use launchArgBuilder + options for anything more than trivial cases.
|
|
462
|
-
*/
|
|
463
|
-
extraArgs?: string[];
|
|
464
|
-
/** Env var overrides applied on top of spawn.env */
|
|
465
|
-
env?: Record<string, string>;
|
|
466
|
-
/**
|
|
467
|
-
* Output rendering hint (shorthand when not using launchOptions/outputFormatMap).
|
|
468
|
-
* - 'terminal' — raw PTY stream (default)
|
|
469
|
-
* - 'stream-json' — structured JSON events → chat messages
|
|
470
|
-
*/
|
|
471
|
-
outputFormat?: 'terminal' | 'stream-json';
|
|
472
|
-
/** Whether this is the default mode when none is specified */
|
|
473
|
-
default?: boolean;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
396
|
export interface ProviderResumeCapability {
|
|
477
397
|
supported: boolean;
|
|
478
398
|
stopStrategy?: 'command' | 'ctrl_c';
|
|
@@ -688,4 +608,3 @@ export interface ProviderControlDef {
|
|
|
688
608
|
/** Hide this control when condition not met */
|
|
689
609
|
hidden?: boolean;
|
|
690
610
|
}
|
|
691
|
-
|
|
@@ -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
|
|
|
@@ -42,7 +42,6 @@ export interface ActiveChatData {
|
|
|
42
42
|
status: string;
|
|
43
43
|
messages: ChatMessage[];
|
|
44
44
|
activeModal: { message: string; buttons: string[] } | null;
|
|
45
|
-
terminalHistory?: string;
|
|
46
45
|
inputContent?: string;
|
|
47
46
|
}
|
|
48
47
|
|
|
@@ -103,8 +102,6 @@ export interface CliProviderState extends ProviderStateBase {
|
|
|
103
102
|
category: 'cli';
|
|
104
103
|
/** terminal = PTY stream, chat = parsed conversation */
|
|
105
104
|
mode: 'terminal' | 'chat';
|
|
106
|
-
/** Active launch mode id (e.g. 'chat') — undefined means default terminal */
|
|
107
|
-
launchMode?: string;
|
|
108
105
|
}
|
|
109
106
|
|
|
110
107
|
/** ACP provider state */
|
package/src/shared-types.ts
CHANGED
|
@@ -102,9 +102,7 @@ export interface SessionEntry {
|
|
|
102
102
|
runtimeKey?: string;
|
|
103
103
|
runtimeDisplayName?: string;
|
|
104
104
|
runtimeWorkspaceLabel?: string;
|
|
105
|
-
/** CLI only: active
|
|
106
|
-
launchMode?: string;
|
|
107
|
-
/** CLI only: output rendering mode derived from launchMode.outputFormat */
|
|
105
|
+
/** CLI only: active presentation mode */
|
|
108
106
|
mode?: 'terminal' | 'chat';
|
|
109
107
|
runtimeWriteOwner?: RuntimeWriteOwner | null;
|
|
110
108
|
runtimeAttachedClients?: RuntimeAttachedClient[];
|
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,11 +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 || [],
|
|
268
|
-
launchMode: state.launchMode,
|
|
269
274
|
mode: state.mode,
|
|
270
275
|
resume: state.resume,
|
|
271
276
|
activeChat,
|
|
272
|
-
capabilities: PTY_SESSION_CAPABILITIES,
|
|
277
|
+
capabilities: state.mode === 'terminal' ? PTY_SESSION_CAPABILITIES : CLI_CHAT_SESSION_CAPABILITIES,
|
|
273
278
|
controlValues: state.controlValues,
|
|
274
279
|
providerControls: buildFallbackControls(
|
|
275
280
|
state.providerControls
|
package/src/status/normalize.ts
CHANGED
|
@@ -20,6 +20,77 @@ 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_INPUT_CONTENT_LIMIT = 2 * 1024;
|
|
30
|
+
const STATUS_MODAL_MESSAGE_LIMIT = 2 * 1024;
|
|
31
|
+
const STATUS_MODAL_BUTTON_LIMIT = 120;
|
|
32
|
+
|
|
33
|
+
function truncateString(value: string, maxChars: number): string {
|
|
34
|
+
if (value.length <= maxChars) return value;
|
|
35
|
+
if (maxChars <= 12) return value.slice(0, Math.max(0, maxChars));
|
|
36
|
+
return `${value.slice(0, maxChars - 12)}...[truncated]`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function truncateStringTail(value: string, maxChars: number): string {
|
|
40
|
+
if (value.length <= maxChars) return value;
|
|
41
|
+
if (maxChars <= 12) return value.slice(value.length - Math.max(0, maxChars));
|
|
42
|
+
return `...[truncated]${value.slice(value.length - (maxChars - 12))}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function trimStructuredStrings(value: unknown, maxChars: number): unknown {
|
|
46
|
+
if (typeof value === 'string') return truncateString(value, maxChars);
|
|
47
|
+
if (Array.isArray(value)) return value.map((item) => trimStructuredStrings(item, maxChars));
|
|
48
|
+
if (!value || typeof value !== 'object') return value;
|
|
49
|
+
return Object.fromEntries(
|
|
50
|
+
Object.entries(value).map(([key, nested]) => [key, trimStructuredStrings(nested, maxChars)]),
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function estimateBytes(value: unknown): number {
|
|
55
|
+
try {
|
|
56
|
+
return JSON.stringify(value).length;
|
|
57
|
+
} catch {
|
|
58
|
+
return String(value ?? '').length;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function trimMessageForStatus(message: unknown, stringLimit: number): unknown {
|
|
63
|
+
if (!message || typeof message !== 'object') return message;
|
|
64
|
+
return trimStructuredStrings(message, stringLimit);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function trimMessagesForStatus(messages: unknown[] | null | undefined): unknown[] {
|
|
68
|
+
if (!Array.isArray(messages) || messages.length === 0) return [];
|
|
69
|
+
|
|
70
|
+
const recent = messages.slice(-STATUS_ACTIVE_CHAT_MESSAGE_LIMIT);
|
|
71
|
+
const kept: unknown[] = [];
|
|
72
|
+
let totalBytes = 0;
|
|
73
|
+
|
|
74
|
+
for (let i = recent.length - 1; i >= 0; i -= 1) {
|
|
75
|
+
let normalized = trimMessageForStatus(recent[i], STATUS_ACTIVE_CHAT_STRING_LIMIT);
|
|
76
|
+
let size = estimateBytes(normalized);
|
|
77
|
+
|
|
78
|
+
if (size > STATUS_ACTIVE_CHAT_TOTAL_BYTES_LIMIT) {
|
|
79
|
+
normalized = trimMessageForStatus(recent[i], STATUS_ACTIVE_CHAT_FALLBACK_STRING_LIMIT);
|
|
80
|
+
size = estimateBytes(normalized);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (kept.length > 0 && (totalBytes + size) > STATUS_ACTIVE_CHAT_TOTAL_BYTES_LIMIT) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
kept.push(normalized);
|
|
88
|
+
totalBytes += size;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return kept.reverse();
|
|
92
|
+
}
|
|
93
|
+
|
|
23
94
|
function hasApprovalButtons(activeModal?: { buttons?: unknown[] | null } | null): boolean {
|
|
24
95
|
return (activeModal?.buttons?.length ?? 0) > 0;
|
|
25
96
|
}
|
|
@@ -60,5 +131,15 @@ export function normalizeActiveChatData<T extends ActiveChatData | null | undefi
|
|
|
60
131
|
return {
|
|
61
132
|
...activeChat,
|
|
62
133
|
status: normalizeManagedStatus(activeChat.status, { activeModal: activeChat.activeModal }),
|
|
134
|
+
messages: trimMessagesForStatus(activeChat.messages) as T extends { messages: infer M } ? M : never,
|
|
135
|
+
activeModal: activeChat.activeModal ? {
|
|
136
|
+
message: truncateString(activeChat.activeModal.message || '', STATUS_MODAL_MESSAGE_LIMIT),
|
|
137
|
+
buttons: (activeChat.activeModal.buttons || []).map((button) =>
|
|
138
|
+
truncateString(String(button || ''), STATUS_MODAL_BUTTON_LIMIT)
|
|
139
|
+
),
|
|
140
|
+
} : activeChat.activeModal,
|
|
141
|
+
inputContent: activeChat.inputContent
|
|
142
|
+
? truncateString(activeChat.inputContent, STATUS_INPUT_CONTENT_LIMIT)
|
|
143
|
+
: activeChat.inputContent,
|
|
63
144
|
} as T;
|
|
64
145
|
}
|
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)`);
|