@adhdev/daemon-core 0.5.3 → 0.5.6
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 +88 -2
- package/dist/index.js +1230 -439
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/providers/_builtin/extension/cline/scripts/read_chat.js +14 -1
- package/providers/_builtin/ide/antigravity/scripts/1.106/read_chat.js +24 -1
- package/providers/_builtin/ide/antigravity/scripts/1.107/read_chat.js +24 -1
- package/providers/_builtin/ide/cursor/scripts/0.49/focus_editor.js +3 -3
- package/providers/_builtin/ide/cursor/scripts/0.49/list_models.js +1 -1
- package/providers/_builtin/ide/cursor/scripts/0.49/list_modes.js +1 -1
- package/providers/_builtin/ide/cursor/scripts/0.49/open_panel.js +4 -4
- package/providers/_builtin/ide/cursor/scripts/0.49/read_chat.js +5 -1
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/dismiss_notification.js +30 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/focus_editor.js +13 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/list_models.js +78 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/list_modes.js +40 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/list_notifications.js +23 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/list_sessions.js +42 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/new_session.js +20 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/open_panel.js +23 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/read_chat.js +79 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/resolve_action.js +19 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/scripts.js +78 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/send_message.js +23 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/set_mode.js +38 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/set_model.js +81 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/switch_session.js +28 -0
- package/providers/_builtin/ide/windsurf/scripts/read_chat.js +18 -1
- package/src/cli-adapters/provider-cli-adapter.ts +231 -12
- package/src/commands/chat-commands.ts +36 -0
- package/src/commands/cli-manager.ts +128 -30
- package/src/commands/handler.ts +47 -3
- package/src/commands/router.ts +32 -2
- package/src/commands/workspace-commands.ts +108 -0
- package/src/config/config.ts +29 -1
- package/src/config/workspace-activity.ts +65 -0
- package/src/config/workspaces.ts +250 -0
- package/src/daemon/dev-server.ts +1 -1
- package/src/index.ts +5 -0
- package/src/launch.ts +1 -1
- package/src/providers/cli-provider-instance.ts +7 -2
- package/src/providers/ide-provider-instance.ts +11 -0
- package/src/status/reporter.ts +23 -4
- package/src/system/host-memory.ts +65 -0
- package/src/types.ts +8 -1
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Saved workspaces — shared by IDE launch, CLI, ACP (daemon-local).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as os from 'os';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import { randomUUID } from 'crypto';
|
|
9
|
+
import type { ADHDevConfig } from './config.js';
|
|
10
|
+
|
|
11
|
+
export interface WorkspaceEntry {
|
|
12
|
+
id: string;
|
|
13
|
+
path: string;
|
|
14
|
+
label?: string;
|
|
15
|
+
addedAt: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const MAX_WORKSPACES = 50;
|
|
19
|
+
|
|
20
|
+
export function expandPath(p: string): string {
|
|
21
|
+
const t = (p || '').trim();
|
|
22
|
+
if (!t) return '';
|
|
23
|
+
if (t.startsWith('~')) return path.join(os.homedir(), t.slice(1).replace(/^\//, ''));
|
|
24
|
+
return path.resolve(t);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function validateWorkspacePath(absPath: string): { ok: true } | { ok: false; error: string } {
|
|
28
|
+
try {
|
|
29
|
+
if (!absPath) return { ok: false, error: 'Path required' };
|
|
30
|
+
if (!fs.existsSync(absPath)) return { ok: false, error: 'Path does not exist' };
|
|
31
|
+
const st = fs.statSync(absPath);
|
|
32
|
+
if (!st.isDirectory()) return { ok: false, error: 'Not a directory' };
|
|
33
|
+
return { ok: true };
|
|
34
|
+
} catch (e: any) {
|
|
35
|
+
return { ok: false, error: e?.message || 'Invalid path' };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Default workspace label from path */
|
|
40
|
+
export function defaultWorkspaceLabel(absPath: string): string {
|
|
41
|
+
const base = path.basename(absPath) || absPath;
|
|
42
|
+
return base;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Ensure config.workspaces exists; seed from recentCliWorkspaces once (same paths).
|
|
47
|
+
*/
|
|
48
|
+
export function migrateWorkspacesFromRecent(config: ADHDevConfig): ADHDevConfig {
|
|
49
|
+
if (!config.workspaces) config.workspaces = [];
|
|
50
|
+
if (config.workspaces.length > 0) return config;
|
|
51
|
+
|
|
52
|
+
const recent = config.recentCliWorkspaces || [];
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
for (const raw of recent) {
|
|
55
|
+
const abs = expandPath(raw);
|
|
56
|
+
if (!abs || validateWorkspacePath(abs).ok !== true) continue;
|
|
57
|
+
if (config.workspaces.some(w => path.resolve(w.path) === abs)) continue;
|
|
58
|
+
config.workspaces.push({
|
|
59
|
+
id: randomUUID(),
|
|
60
|
+
path: abs,
|
|
61
|
+
label: defaultWorkspaceLabel(abs),
|
|
62
|
+
addedAt: now,
|
|
63
|
+
});
|
|
64
|
+
if (config.workspaces.length >= MAX_WORKSPACES) break;
|
|
65
|
+
}
|
|
66
|
+
return config;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function getDefaultWorkspacePath(config: ADHDevConfig): string | null {
|
|
70
|
+
const id = config.defaultWorkspaceId;
|
|
71
|
+
if (!id) return null;
|
|
72
|
+
const w = (config.workspaces || []).find(x => x.id === id);
|
|
73
|
+
if (!w) return null;
|
|
74
|
+
const abs = expandPath(w.path);
|
|
75
|
+
if (validateWorkspacePath(abs).ok !== true) return null;
|
|
76
|
+
return abs;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function getWorkspaceState(config: ADHDevConfig): {
|
|
80
|
+
workspaces: WorkspaceEntry[];
|
|
81
|
+
defaultWorkspaceId: string | null;
|
|
82
|
+
defaultWorkspacePath: string | null;
|
|
83
|
+
} {
|
|
84
|
+
const workspaces = [...(config.workspaces || [])].sort((a, b) => b.addedAt - a.addedAt);
|
|
85
|
+
const defaultWorkspacePath = getDefaultWorkspacePath(config);
|
|
86
|
+
return {
|
|
87
|
+
workspaces,
|
|
88
|
+
defaultWorkspaceId: config.defaultWorkspaceId ?? null,
|
|
89
|
+
defaultWorkspacePath,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type LaunchDirectorySource = 'dir' | 'workspaceId' | 'defaultWorkspace' | 'home';
|
|
94
|
+
|
|
95
|
+
export type ResolveLaunchDirectoryResult =
|
|
96
|
+
| { ok: true; path: string; source: LaunchDirectorySource }
|
|
97
|
+
| { ok: false; code: 'WORKSPACE_LAUNCH_CONTEXT_REQUIRED'; message: string };
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Resolve cwd for CLI/ACP. No implicit default workspace or home — caller must pass
|
|
101
|
+
* useDefaultWorkspace or useHome (or an explicit dir / workspaceId).
|
|
102
|
+
*/
|
|
103
|
+
export function resolveLaunchDirectory(
|
|
104
|
+
args: {
|
|
105
|
+
dir?: string;
|
|
106
|
+
workspaceId?: string;
|
|
107
|
+
useDefaultWorkspace?: boolean;
|
|
108
|
+
useHome?: boolean;
|
|
109
|
+
} | undefined,
|
|
110
|
+
config: ADHDevConfig,
|
|
111
|
+
): ResolveLaunchDirectoryResult {
|
|
112
|
+
const a = args || {};
|
|
113
|
+
if (a.dir != null && String(a.dir).trim()) {
|
|
114
|
+
const abs = expandPath(String(a.dir).trim());
|
|
115
|
+
if (abs && validateWorkspacePath(abs).ok === true) {
|
|
116
|
+
return { ok: true, path: abs, source: 'dir' };
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
code: 'WORKSPACE_LAUNCH_CONTEXT_REQUIRED',
|
|
121
|
+
message: abs ? 'Directory path is not valid or does not exist' : 'Invalid directory path',
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (a.workspaceId) {
|
|
125
|
+
const w = (config.workspaces || []).find(x => x.id === a.workspaceId);
|
|
126
|
+
if (w) {
|
|
127
|
+
const abs = expandPath(w.path);
|
|
128
|
+
if (validateWorkspacePath(abs).ok === true) {
|
|
129
|
+
return { ok: true, path: abs, source: 'workspaceId' };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
ok: false,
|
|
134
|
+
code: 'WORKSPACE_LAUNCH_CONTEXT_REQUIRED',
|
|
135
|
+
message: 'Saved workspace not found or path is no longer valid',
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (a.useDefaultWorkspace === true) {
|
|
139
|
+
const d = getDefaultWorkspacePath(config);
|
|
140
|
+
if (d) return { ok: true, path: d, source: 'defaultWorkspace' };
|
|
141
|
+
return {
|
|
142
|
+
ok: false,
|
|
143
|
+
code: 'WORKSPACE_LAUNCH_CONTEXT_REQUIRED',
|
|
144
|
+
message: 'No default workspace is set',
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (a.useHome === true) {
|
|
148
|
+
return { ok: true, path: os.homedir(), source: 'home' };
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
ok: false,
|
|
152
|
+
code: 'WORKSPACE_LAUNCH_CONTEXT_REQUIRED',
|
|
153
|
+
message: 'Choose a directory, saved workspace, default workspace, or home before launching.',
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* IDE folder from explicit args only (`workspace`, `workspaceId`, or `useDefaultWorkspace: true`).
|
|
159
|
+
*/
|
|
160
|
+
export function resolveIdeWorkspaceFromArgs(
|
|
161
|
+
args: {
|
|
162
|
+
workspace?: string;
|
|
163
|
+
workspaceId?: string;
|
|
164
|
+
useDefaultWorkspace?: boolean;
|
|
165
|
+
} | undefined,
|
|
166
|
+
config: ADHDevConfig,
|
|
167
|
+
): string | undefined {
|
|
168
|
+
const ar = args || {};
|
|
169
|
+
if (ar.workspace) {
|
|
170
|
+
const abs = expandPath(ar.workspace);
|
|
171
|
+
if (abs && validateWorkspacePath(abs).ok === true) return abs;
|
|
172
|
+
}
|
|
173
|
+
if (ar.workspaceId) {
|
|
174
|
+
const w = (config.workspaces || []).find(x => x.id === ar.workspaceId);
|
|
175
|
+
if (w) {
|
|
176
|
+
const abs = expandPath(w.path);
|
|
177
|
+
if (validateWorkspacePath(abs).ok === true) return abs;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (ar.useDefaultWorkspace === true) {
|
|
181
|
+
return getDefaultWorkspacePath(config) || undefined;
|
|
182
|
+
}
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* IDE launch folder — same saved workspaces + default as CLI/ACP.
|
|
188
|
+
* After explicit `workspace` / `workspaceId` / `useDefaultWorkspace: true`, falls back to
|
|
189
|
+
* config default workspace when set. Pass `useDefaultWorkspace: false` to open IDE without that folder.
|
|
190
|
+
*/
|
|
191
|
+
export function resolveIdeLaunchWorkspace(
|
|
192
|
+
args: {
|
|
193
|
+
workspace?: string;
|
|
194
|
+
workspaceId?: string;
|
|
195
|
+
useDefaultWorkspace?: boolean;
|
|
196
|
+
} | undefined,
|
|
197
|
+
config: ADHDevConfig,
|
|
198
|
+
): string | undefined {
|
|
199
|
+
const direct = resolveIdeWorkspaceFromArgs(args, config);
|
|
200
|
+
if (direct) return direct;
|
|
201
|
+
if (args?.useDefaultWorkspace === false) return undefined;
|
|
202
|
+
return getDefaultWorkspacePath(config) || undefined;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function findWorkspaceByPath(config: ADHDevConfig, rawPath: string): WorkspaceEntry | undefined {
|
|
206
|
+
const abs = path.resolve(expandPath(rawPath));
|
|
207
|
+
if (!abs) return undefined;
|
|
208
|
+
return (config.workspaces || []).find(w => path.resolve(expandPath(w.path)) === abs);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function addWorkspaceEntry(config: ADHDevConfig, rawPath: string, label?: string): { config: ADHDevConfig; entry: WorkspaceEntry } | { error: string } {
|
|
212
|
+
const abs = expandPath(rawPath);
|
|
213
|
+
const v = validateWorkspacePath(abs);
|
|
214
|
+
if (!v.ok) return { error: v.error };
|
|
215
|
+
|
|
216
|
+
const list = [...(config.workspaces || [])];
|
|
217
|
+
if (list.some(w => path.resolve(w.path) === abs)) {
|
|
218
|
+
return { error: 'Workspace already in list' };
|
|
219
|
+
}
|
|
220
|
+
if (list.length >= MAX_WORKSPACES) {
|
|
221
|
+
return { error: `Maximum ${MAX_WORKSPACES} workspaces` };
|
|
222
|
+
}
|
|
223
|
+
const entry: WorkspaceEntry = {
|
|
224
|
+
id: randomUUID(),
|
|
225
|
+
path: abs,
|
|
226
|
+
label: (label || '').trim() || defaultWorkspaceLabel(abs),
|
|
227
|
+
addedAt: Date.now(),
|
|
228
|
+
};
|
|
229
|
+
list.push(entry);
|
|
230
|
+
return { config: { ...config, workspaces: list }, entry };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function removeWorkspaceEntry(config: ADHDevConfig, id: string): { config: ADHDevConfig } | { error: string } {
|
|
234
|
+
const list = (config.workspaces || []).filter(w => w.id !== id);
|
|
235
|
+
if (list.length === (config.workspaces || []).length) return { error: 'Workspace not found' };
|
|
236
|
+
let defaultWorkspaceId = config.defaultWorkspaceId;
|
|
237
|
+
if (defaultWorkspaceId === id) defaultWorkspaceId = null;
|
|
238
|
+
return { config: { ...config, workspaces: list, defaultWorkspaceId } };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function setDefaultWorkspaceId(config: ADHDevConfig, id: string | null): { config: ADHDevConfig } | { error: string } {
|
|
242
|
+
if (id === null) {
|
|
243
|
+
return { config: { ...config, defaultWorkspaceId: null } };
|
|
244
|
+
}
|
|
245
|
+
const w = (config.workspaces || []).find(x => x.id === id);
|
|
246
|
+
if (!w) return { error: 'Workspace not found' };
|
|
247
|
+
const abs = expandPath(w.path);
|
|
248
|
+
if (validateWorkspacePath(abs).ok !== true) return { error: 'Workspace path is no longer valid' };
|
|
249
|
+
return { config: { ...config, defaultWorkspaceId: id } };
|
|
250
|
+
}
|
package/src/daemon/dev-server.ts
CHANGED
|
@@ -2238,7 +2238,7 @@ export class DevServer {
|
|
|
2238
2238
|
lines.push('## Required Return Format');
|
|
2239
2239
|
lines.push('| Function | Return JSON |');
|
|
2240
2240
|
lines.push('|---|---|');
|
|
2241
|
-
lines.push('| readChat | `{ id, status, title, messages: [{role, content, index}], inputContent, activeModal }` |');
|
|
2241
|
+
lines.push('| readChat | `{ id, status, title, messages: [{role, content, index, kind?, meta?}], inputContent, activeModal }` — optional `kind`: standard, thought, tool, terminal; optional `meta`: e.g. `{ label, isRunning }` for dashboard |');
|
|
2242
2242
|
lines.push('| sendMessage | `{ sent: false, needsTypeAndSend: true, selector }` |');
|
|
2243
2243
|
lines.push('| resolveAction | `{ resolved: true/false, clicked? }` |');
|
|
2244
2244
|
lines.push('| listSessions | `{ sessions: [{ id, title, active, index }] }` |');
|
package/src/index.ts
CHANGED
|
@@ -29,11 +29,16 @@ export type { IDaemonCore, DaemonCoreOptions } from './daemon-core.js';
|
|
|
29
29
|
|
|
30
30
|
// ── Config ──
|
|
31
31
|
export { loadConfig, saveConfig, resetConfig, isSetupComplete, addCliHistory, markSetupComplete, updateConfig } from './config/config.js';
|
|
32
|
+
export { getWorkspaceState } from './config/workspaces.js';
|
|
33
|
+
export { getWorkspaceActivity } from './config/workspace-activity.js';
|
|
34
|
+
export type { WorkspaceEntry } from './config/workspaces.js';
|
|
32
35
|
|
|
33
36
|
// ── Detection ──
|
|
34
37
|
export { detectIDEs } from './detection/ide-detector.js';
|
|
35
38
|
export type { IDEInfo } from './detection/ide-detector.js';
|
|
36
39
|
export { detectCLIs } from './detection/cli-detector.js';
|
|
40
|
+
export { getHostMemorySnapshot } from './system/host-memory.js';
|
|
41
|
+
export type { HostMemorySnapshot } from './system/host-memory.js';
|
|
37
42
|
|
|
38
43
|
// ── CDP ──
|
|
39
44
|
export { DaemonCdpManager } from './cdp/manager.js';
|
package/src/launch.ts
CHANGED
|
@@ -224,7 +224,7 @@ function detectCurrentWorkspace(ideId: string): string | undefined {
|
|
|
224
224
|
);
|
|
225
225
|
if (fs.existsSync(storagePath)) {
|
|
226
226
|
const data = JSON.parse(fs.readFileSync(storagePath, 'utf-8'));
|
|
227
|
-
// openedPathsList.workspaces3 has recent
|
|
227
|
+
// openedPathsList.workspaces3 has recent workspace paths
|
|
228
228
|
const workspaces = data?.openedPathsList?.workspaces3 || data?.openedPathsList?.entries || [];
|
|
229
229
|
if (workspaces.length > 0) {
|
|
230
230
|
const recent = workspaces[0];
|
|
@@ -13,6 +13,7 @@ import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
|
|
|
13
13
|
import type { CliProviderModule } from '../cli-adapters/provider-cli-adapter.js';
|
|
14
14
|
import { StatusMonitor } from './status-monitor.js';
|
|
15
15
|
import { ChatHistoryWriter } from '../config/chat-history.js';
|
|
16
|
+
import { LOG } from '../logging/logger.js';
|
|
16
17
|
|
|
17
18
|
export class CliProviderInstance implements ProviderInstance {
|
|
18
19
|
readonly type: string;
|
|
@@ -156,18 +157,22 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
156
157
|
const chatTitle = `${this.provider.name} · ${dirName}`;
|
|
157
158
|
|
|
158
159
|
if (newStatus !== this.lastStatus) {
|
|
160
|
+
LOG.info('CLI', `[${this.type}] status: ${this.lastStatus} → ${newStatus}`);
|
|
159
161
|
if (this.lastStatus === 'idle' && newStatus === 'generating') {
|
|
160
162
|
this.generatingStartedAt = now;
|
|
161
163
|
this.pushEvent({ event: 'agent:generating_started', chatTitle, timestamp: now });
|
|
162
164
|
} else if (newStatus === 'waiting_approval') {
|
|
163
165
|
if (!this.generatingStartedAt) this.generatingStartedAt = now;
|
|
166
|
+
const modal = adapterStatus.activeModal;
|
|
167
|
+
LOG.info('CLI', `[${this.type}] approval modal: "${modal?.message?.slice(0, 80) ?? 'none'}"`);
|
|
164
168
|
this.pushEvent({
|
|
165
169
|
event: 'agent:waiting_approval', chatTitle, timestamp: now,
|
|
166
|
-
modalMessage:
|
|
167
|
-
modalButtons:
|
|
170
|
+
modalMessage: modal?.message,
|
|
171
|
+
modalButtons: modal?.buttons,
|
|
168
172
|
});
|
|
169
173
|
} else if (newStatus === 'idle' && (this.lastStatus === 'generating' || this.lastStatus === 'waiting_approval')) {
|
|
170
174
|
const duration = this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1000) : 0;
|
|
175
|
+
LOG.info('CLI', `[${this.type}] completed in ${duration}s`);
|
|
171
176
|
this.pushEvent({ event: 'agent:generating_completed', chatTitle, duration, timestamp: now });
|
|
172
177
|
this.generatingStartedAt = 0;
|
|
173
178
|
} else if (newStatus === 'stopped') {
|
|
@@ -274,6 +274,17 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
274
274
|
msg.receivedAt = prevByHash.get(h) || now;
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
+
// Filter messages by provider settings (showThinking, showToolCalls, showTerminal)
|
|
278
|
+
if (raw.messages?.length > 0) {
|
|
279
|
+
const hiddenKinds = new Set<string>();
|
|
280
|
+
if (this.settings.showThinking === false) hiddenKinds.add('thought');
|
|
281
|
+
if (this.settings.showToolCalls === false) hiddenKinds.add('tool');
|
|
282
|
+
if (this.settings.showTerminal === false) hiddenKinds.add('terminal');
|
|
283
|
+
if (hiddenKinds.size > 0) {
|
|
284
|
+
raw.messages = raw.messages.filter((m: any) => !hiddenKinds.has(m.kind));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
277
288
|
this.cachedChat = { ...raw, activeModal };
|
|
278
289
|
this.detectAgentTransitions(raw, now);
|
|
279
290
|
|
package/src/status/reporter.ts
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
import * as os from 'os';
|
|
9
9
|
import * as path from 'path';
|
|
10
10
|
import { loadConfig } from '../config/config.js';
|
|
11
|
+
import { getWorkspaceState } from '../config/workspaces.js';
|
|
12
|
+
import { getHostMemorySnapshot } from '../system/host-memory.js';
|
|
13
|
+
import { getWorkspaceActivity } from '../config/workspace-activity.js';
|
|
11
14
|
import { LOG } from '../logging/logger.js';
|
|
12
15
|
|
|
13
16
|
// ─── Daemon dependency interface ──────────────────────
|
|
@@ -95,6 +98,11 @@ export class DaemonStatusReporter {
|
|
|
95
98
|
// (agent-stream polling backward compat)
|
|
96
99
|
updateAgentStreams(_ideType: string, _streams: any[]): void { /* Managed by Instance itself */ }
|
|
97
100
|
|
|
101
|
+
/** Reset P2P dedup hash — forces next send to transmit even if content unchanged */
|
|
102
|
+
resetP2PHash(): void {
|
|
103
|
+
this.lastP2PStatusHash = '';
|
|
104
|
+
}
|
|
105
|
+
|
|
98
106
|
// ─── Core ────────────────────────────────────────
|
|
99
107
|
|
|
100
108
|
private ts(): string {
|
|
@@ -197,17 +205,26 @@ export class DaemonStatusReporter {
|
|
|
197
205
|
|
|
198
206
|
|
|
199
207
|
|
|
208
|
+
const cfg = loadConfig();
|
|
209
|
+
const wsState = getWorkspaceState(cfg);
|
|
210
|
+
const memSnap = getHostMemorySnapshot();
|
|
211
|
+
|
|
200
212
|
// ═══ Assemble payload (P2P — required data only) ═══
|
|
201
213
|
const payload: Record<string, any> = {
|
|
202
214
|
daemonMode: true,
|
|
203
|
-
machineNickname:
|
|
215
|
+
machineNickname: cfg.machineNickname || null,
|
|
216
|
+
workspaces: wsState.workspaces,
|
|
217
|
+
defaultWorkspaceId: wsState.defaultWorkspaceId,
|
|
218
|
+
defaultWorkspacePath: wsState.defaultWorkspacePath,
|
|
219
|
+
workspaceActivity: getWorkspaceActivity(cfg, 15),
|
|
204
220
|
machine: {
|
|
205
221
|
hostname: os.hostname(),
|
|
206
222
|
platform: os.platform(),
|
|
207
223
|
arch: os.arch(),
|
|
208
224
|
cpus: os.cpus().length,
|
|
209
|
-
totalMem:
|
|
210
|
-
freeMem:
|
|
225
|
+
totalMem: memSnap.totalMem,
|
|
226
|
+
freeMem: memSnap.freeMem,
|
|
227
|
+
availableMem: memSnap.availableMem,
|
|
211
228
|
loadavg: os.loadavg(),
|
|
212
229
|
uptime: os.uptime(),
|
|
213
230
|
},
|
|
@@ -243,6 +260,8 @@ export class DaemonStatusReporter {
|
|
|
243
260
|
const wsPayload = {
|
|
244
261
|
daemonMode: true,
|
|
245
262
|
machineNickname: payload.machineNickname,
|
|
263
|
+
defaultWorkspaceId: wsState.defaultWorkspaceId,
|
|
264
|
+
workspaceCount: (wsState.workspaces || []).length,
|
|
246
265
|
// managedIdes: server only saves id, type, cdpConnected
|
|
247
266
|
managedIdes: managedIdes.map(ide => ({
|
|
248
267
|
ideType: ide.ideType,
|
|
@@ -271,7 +290,7 @@ export class DaemonStatusReporter {
|
|
|
271
290
|
private sendP2PPayload(payload: Record<string, any>): boolean {
|
|
272
291
|
const { timestamp: _ts, system: _sys, ...hashTarget } = payload;
|
|
273
292
|
if (hashTarget.machine) {
|
|
274
|
-
const { freeMem: _f, loadavg: _l, uptime: _u, ...stableMachine } = hashTarget.machine as any;
|
|
293
|
+
const { freeMem: _f, availableMem: _a, loadavg: _l, uptime: _u, ...stableMachine } = hashTarget.machine as any;
|
|
275
294
|
hashTarget.machine = stableMachine;
|
|
276
295
|
}
|
|
277
296
|
const h = this.simpleHash(JSON.stringify(hashTarget));
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host memory metrics — macOS-aware "available" memory.
|
|
3
|
+
*
|
|
4
|
+
* Node's os.freemem() on darwin reports only the tiny truly-free pool; most RAM
|
|
5
|
+
* sits in inactive/file-backed cache that the OS can reclaim. Dashboard "used %"
|
|
6
|
+
* based on (total - freemem) looks ~99% almost always — misleading.
|
|
7
|
+
*
|
|
8
|
+
* On macOS we parse `vm_stat` and approximate available bytes as:
|
|
9
|
+
* (free + inactive + speculative + purgeable [+ file_backed]) × page size
|
|
10
|
+
* (aligned with common Activity Monitor–style interpretations.)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import * as os from 'os';
|
|
14
|
+
import { execSync } from 'child_process';
|
|
15
|
+
|
|
16
|
+
export interface HostMemorySnapshot {
|
|
17
|
+
totalMem: number;
|
|
18
|
+
/** Raw kernel "free" — small on macOS; kept for debugging / API compat */
|
|
19
|
+
freeMem: number;
|
|
20
|
+
/** Use this for UI "used %" — on darwin from vm_stat; else equals freeMem */
|
|
21
|
+
availableMem: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function parseDarwinAvailableBytes(totalMem: number): number | null {
|
|
25
|
+
if (os.platform() !== 'darwin') return null;
|
|
26
|
+
try {
|
|
27
|
+
const out = execSync('vm_stat', {
|
|
28
|
+
encoding: 'utf-8',
|
|
29
|
+
timeout: 4000,
|
|
30
|
+
maxBuffer: 256 * 1024,
|
|
31
|
+
});
|
|
32
|
+
const pageSizeMatch = out.match(/page size of (\d+)\s*bytes/i);
|
|
33
|
+
const pageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : 4096;
|
|
34
|
+
|
|
35
|
+
const counts: Record<string, number> = {};
|
|
36
|
+
for (const line of out.split('\n')) {
|
|
37
|
+
const m = line.match(/^\s*Pages\s+([^:]+):\s+([\d,]+)\s*\.?/);
|
|
38
|
+
if (!m) continue;
|
|
39
|
+
const key = m[1].trim().toLowerCase().replace(/\s+/g, '_');
|
|
40
|
+
const n = parseInt(m[2].replace(/,/g, ''), 10);
|
|
41
|
+
if (!Number.isNaN(n)) counts[key] = n;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const free = counts['free'] ?? 0;
|
|
45
|
+
const inactive = counts['inactive'] ?? 0;
|
|
46
|
+
const speculative = counts['speculative'] ?? 0;
|
|
47
|
+
const purgeable = counts['purgeable'] ?? 0;
|
|
48
|
+
const fileBacked = counts['file_backed'] ?? 0;
|
|
49
|
+
|
|
50
|
+
const availPages = free + inactive + speculative + purgeable + fileBacked;
|
|
51
|
+
const bytes = availPages * pageSize;
|
|
52
|
+
if (!Number.isFinite(bytes) || bytes < 0) return null;
|
|
53
|
+
return Math.min(bytes, totalMem);
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getHostMemorySnapshot(): HostMemorySnapshot {
|
|
60
|
+
const totalMem = os.totalmem();
|
|
61
|
+
const freeMem = os.freemem();
|
|
62
|
+
const darwinAvail = parseDarwinAvailableBytes(totalMem);
|
|
63
|
+
const availableMem = darwinAvail != null ? darwinAvail : freeMem;
|
|
64
|
+
return { totalMem, freeMem, availableMem };
|
|
65
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -43,7 +43,7 @@ export interface IdeEntry {
|
|
|
43
43
|
agentStreams?: AgentStreamEntry[];
|
|
44
44
|
/** Extension agents monitored via CDP */
|
|
45
45
|
extensions?: ExtensionInfo[];
|
|
46
|
-
/**
|
|
46
|
+
/** IDE-reported workspace roots (name + path) */
|
|
47
47
|
workspaceFolders?: { name: string; path: string }[];
|
|
48
48
|
/** Active file path */
|
|
49
49
|
activeFile?: string | null;
|
|
@@ -150,6 +150,8 @@ export interface SystemInfo {
|
|
|
150
150
|
cpus: number;
|
|
151
151
|
totalMem: number;
|
|
152
152
|
freeMem: number;
|
|
153
|
+
/** macOS: reclaimable-inclusive; prefer for UI used% (see host-memory.ts) */
|
|
154
|
+
availableMem?: number;
|
|
153
155
|
loadavg: number[];
|
|
154
156
|
uptime: number;
|
|
155
157
|
arch: string;
|
|
@@ -183,6 +185,11 @@ export interface StatusResponse {
|
|
|
183
185
|
detectedIdes: DetectedIde[];
|
|
184
186
|
availableProviders: ProviderInfo[];
|
|
185
187
|
system: SystemInfo;
|
|
188
|
+
/** Saved workspaces (standalone WS / HTTP status) */
|
|
189
|
+
workspaces?: { id: string; path: string; label?: string; addedAt: number }[];
|
|
190
|
+
defaultWorkspaceId?: string | null;
|
|
191
|
+
defaultWorkspacePath?: string | null;
|
|
192
|
+
workspaceActivity?: { path: string; lastUsedAt: number; kind?: string; agentType?: string }[];
|
|
186
193
|
}
|
|
187
194
|
|
|
188
195
|
/** Agent stream entry within an IDE */
|