nterminal 1.3.1 → 1.3.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.
Files changed (71) hide show
  1. package/README.md +4 -2
  2. package/bin/nterminal.js +5 -0
  3. package/dist/client/assets/MarkdownPreview-CWK2qIZp.js +1 -0
  4. package/dist/client/assets/{TranscriptMarkdownBody-Ca7wEE6u.js → TranscriptMarkdownBody-Ddi-rw6X.js} +1 -1
  5. package/dist/client/assets/index-CcAQThtj.js +56 -0
  6. package/dist/client/assets/{index-CFBcr64T.js → index-DDCpqDNb.js} +1 -1
  7. package/dist/client/assets/index-DTTX24tu.css +1 -0
  8. package/dist/client/index.html +2 -2
  9. package/dist/scripts/onboarding.js +9 -0
  10. package/dist/scripts/onboarding.js.map +1 -1
  11. package/dist/server/agent/agentProxy.d.ts +1 -0
  12. package/dist/server/agent/agentProxy.js +3 -1
  13. package/dist/server/agent/agentProxy.js.map +1 -1
  14. package/dist/server/agent/agentRoutes.js +21 -2
  15. package/dist/server/agent/agentRoutes.js.map +1 -1
  16. package/dist/server/index.js +2 -0
  17. package/dist/server/index.js.map +1 -1
  18. package/dist/server/routes/agentManagementRoutes.js +43 -2
  19. package/dist/server/routes/agentManagementRoutes.js.map +1 -1
  20. package/dist/server/routes/terminalRoutes.js +19 -2
  21. package/dist/server/routes/terminalRoutes.js.map +1 -1
  22. package/dist/server/routes/transcriptQuery.d.ts +14 -0
  23. package/dist/server/routes/transcriptQuery.js +57 -0
  24. package/dist/server/routes/transcriptQuery.js.map +1 -0
  25. package/dist/server/system/processIdentity.d.ts +6 -0
  26. package/dist/server/system/processIdentity.js +30 -0
  27. package/dist/server/system/processIdentity.js.map +1 -0
  28. package/dist/server/system/projectNames.d.ts +25 -0
  29. package/dist/server/system/projectNames.js +48 -0
  30. package/dist/server/system/projectNames.js.map +1 -0
  31. package/dist/server/system/serverInfo.d.ts +3 -0
  32. package/dist/server/system/serverInfo.js +172 -0
  33. package/dist/server/system/serverInfo.js.map +1 -0
  34. package/dist/server/terminal/PtyAdapter.d.ts +12 -0
  35. package/dist/server/terminal/PtyAdapter.js.map +1 -1
  36. package/dist/server/terminal/TerminalManager.d.ts +6 -7
  37. package/dist/server/terminal/TerminalManager.js +57 -11
  38. package/dist/server/terminal/TerminalManager.js.map +1 -1
  39. package/dist/server/terminal/TmuxPtyAdapter.d.ts +15 -1
  40. package/dist/server/terminal/TmuxPtyAdapter.js +284 -13
  41. package/dist/server/terminal/TmuxPtyAdapter.js.map +1 -1
  42. package/dist/server/terminal/codexTranscriptSource.d.ts +18 -1
  43. package/dist/server/terminal/codexTranscriptSource.js +263 -68
  44. package/dist/server/terminal/codexTranscriptSource.js.map +1 -1
  45. package/dist/server/terminal/sqliteReader.d.ts +21 -0
  46. package/dist/server/terminal/sqliteReader.js +158 -0
  47. package/dist/server/terminal/sqliteReader.js.map +1 -0
  48. package/dist/server/terminal/subagentSources.js +3 -2
  49. package/dist/server/terminal/subagentSources.js.map +1 -1
  50. package/dist/server/terminal/transcriptHistory.d.ts +34 -0
  51. package/dist/server/terminal/transcriptHistory.js +81 -36
  52. package/dist/server/terminal/transcriptHistory.js.map +1 -1
  53. package/dist/server/terminal/transcriptStore.d.ts +64 -0
  54. package/dist/server/terminal/transcriptStore.js +779 -0
  55. package/dist/server/terminal/transcriptStore.js.map +1 -0
  56. package/dist/shared/nodeVersion.d.ts +3 -0
  57. package/dist/shared/nodeVersion.js +12 -0
  58. package/dist/shared/nodeVersion.js.map +1 -0
  59. package/dist/shared/protocol.d.ts +56 -0
  60. package/dist/shared/protocol.js.map +1 -1
  61. package/dist/shared/transcriptSearch.d.ts +2 -0
  62. package/dist/shared/transcriptSearch.js +12 -0
  63. package/dist/shared/transcriptSearch.js.map +1 -0
  64. package/dist/src/shared/nodeVersion.js +12 -0
  65. package/dist/src/shared/nodeVersion.js.map +1 -0
  66. package/docs/terminal-history.md +22 -4
  67. package/package.json +3 -2
  68. package/scripts/nterminalctl +8 -4
  69. package/dist/client/assets/MarkdownPreview-C5AWbbg_.js +0 -1
  70. package/dist/client/assets/index-Cca8_nsS.css +0 -1
  71. package/dist/client/assets/index-XKVvN3op.js +0 -56
@@ -0,0 +1,30 @@
1
+ import path from 'node:path';
2
+ export function identifyProcess(command, executable, args, pm2) {
3
+ const executablePath = executable.replace(/ \(deleted\)$/, '');
4
+ const binary = path.basename(executablePath);
5
+ const node = binary === 'node' || binary === 'nodejs';
6
+ // Only inspect the executable and a Node wrapper's script, not prompt text.
7
+ const entrypoint = node ? args[1] ?? '' : executablePath;
8
+ const entryName = path.basename(entrypoint);
9
+ const codex = entryName === 'codex' || (node && entrypoint.includes('/@openai/codex/'));
10
+ const nativeClaude = !node && /\/claude\/versions\/\d+\.\d+\.\d+(?:[-+][\w.-]+)?$/.test(executablePath);
11
+ const claude = entryName === 'claude' || nativeClaude || (node && entrypoint.includes('/@anthropic-ai/claude-code/'));
12
+ const daemonOffset = node ? 2 : 1;
13
+ const daemon = codex && args[daemonOffset] === 'app-server'
14
+ && (args.includes('--managed-daemon') || args[daemonOffset + 1] === 'daemon');
15
+ const codeServer = node && (entrypoint.includes('/code-server/') || entryName === 'code-server');
16
+ const category = daemon ? 'shared' : codex || claude || codeServer || binary === 'codex-code-mode' ? 'tools' : 'services';
17
+ const excluded = ['bash', 'sh', 'dash', 'zsh', 'fish', 'sleep', 'tmux', 'systemd', 'dbus-daemon'].includes(binary)
18
+ || (node && /^PM2 v[^\s]+: God/.test(command));
19
+ // PM2 sets process.title to "node /absolute/script.js", often leaving no argv[1].
20
+ // Extract only the script basename; never return the title or argument string.
21
+ const pm2Script = pm2 && node ? args[0]?.match(/^node(?:js)? (\/[^\r\n]+?\.(?:mjs|cjs|js|ts))(?:\s|$)/)?.[1] : undefined;
22
+ const script = node || /^python(?:\d+(?:\.\d+)*)?$/.test(binary)
23
+ ? args.slice(1, 3).find((arg) => !arg.startsWith('-') && /\.(?:m?js|cjs|ts|py)$/.test(arg) && !/[\r\n]/.test(arg))
24
+ : undefined;
25
+ const name = codex ? 'codex' : claude ? 'claude' : codeServer ? 'code-server'
26
+ : pm2Script ? path.basename(pm2Script) : script ? path.basename(script)
27
+ : command === 'MainThread' ? binary || 'process' : command;
28
+ return { name, category, excluded };
29
+ }
30
+ //# sourceMappingURL=processIdentity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"processIdentity.js","sourceRoot":"","sources":["../../../src/server/system/processIdentity.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAQ7B,MAAM,UAAU,eAAe,CAAC,OAAe,EAAE,UAAkB,EAAE,IAAc,EAAE,GAAY;IAC/F,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC;IAC/D,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;IAC7C,MAAM,IAAI,GAAG,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,QAAQ,CAAC;IACtD,4EAA4E;IAC5E,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC;IACzD,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC5C,MAAM,KAAK,GAAG,SAAS,KAAK,OAAO,IAAI,CAAC,IAAI,IAAI,UAAU,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC,CAAC;IACxF,MAAM,YAAY,GAAG,CAAC,IAAI,IAAI,oDAAoD,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACxG,MAAM,MAAM,GAAG,SAAS,KAAK,QAAQ,IAAI,YAAY,IAAI,CAAC,IAAI,IAAI,UAAU,CAAC,QAAQ,CAAC,6BAA6B,CAAC,CAAC,CAAC;IACtH,MAAM,YAAY,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,KAAK,IAAI,IAAI,CAAC,YAAY,CAAC,KAAK,YAAY;WACtD,CAAC,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC;IAChF,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,SAAS,KAAK,aAAa,CAAC,CAAC;IACjG,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,MAAM,IAAI,UAAU,IAAI,MAAM,KAAK,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC;IAC1H,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;WAC7G,CAAC,IAAI,IAAI,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IACjD,kFAAkF;IAClF,+EAA+E;IAC/E,MAAM,SAAS,GAAG,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,uDAAuD,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACzH,MAAM,MAAM,GAAG,IAAI,IAAI,4BAA4B,CAAC,IAAI,CAAC,MAAM,CAAC;QAC9D,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,uBAAuB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClH,CAAC,CAAC,SAAS,CAAC;IACd,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,aAAa;QAC3E,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;YACvE,CAAC,CAAC,OAAO,KAAK,YAAY,CAAC,CAAC,CAAC,MAAM,IAAI,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;IAC7D,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AACtC,CAAC"}
@@ -0,0 +1,25 @@
1
+ export declare const projectNameSchema: {
2
+ body: {
3
+ type: string;
4
+ additionalProperties: boolean;
5
+ required: string[];
6
+ properties: {
7
+ path: {
8
+ type: string;
9
+ minLength: number;
10
+ maxLength: number;
11
+ pattern: string;
12
+ };
13
+ name: {
14
+ type: string;
15
+ minLength: number;
16
+ maxLength: number;
17
+ pattern: string;
18
+ };
19
+ };
20
+ };
21
+ };
22
+ export declare function readProjectNames(statePath: string): Promise<Map<string, string>>;
23
+ export declare function saveProjectName(statePath: string, projectPath: string, label: string): Promise<{
24
+ name: string;
25
+ }>;
@@ -0,0 +1,48 @@
1
+ import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
2
+ import { randomUUID } from 'node:crypto';
3
+ import path from 'node:path';
4
+ const writes = new Map();
5
+ const filename = (statePath) => path.join(path.dirname(statePath), 'project-names.json');
6
+ export const projectNameSchema = { body: {
7
+ type: 'object', additionalProperties: false, required: ['path', 'name'],
8
+ properties: { path: { type: 'string', minLength: 1, maxLength: 4096, pattern: '^/' }, name: { type: 'string', minLength: 1, maxLength: 80, pattern: '\\S' } }
9
+ } };
10
+ export async function readProjectNames(statePath) {
11
+ let raw;
12
+ try {
13
+ raw = JSON.parse(await readFile(filename(statePath), 'utf8'));
14
+ }
15
+ catch (error) {
16
+ if (error.code === 'ENOENT')
17
+ return new Map();
18
+ throw error;
19
+ }
20
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
21
+ throw new Error('Invalid project names');
22
+ return new Map(Object.entries(raw).filter((entry) => path.isAbsolute(entry[0]) && typeof entry[1] === 'string'));
23
+ }
24
+ export function saveProjectName(statePath, projectPath, label) {
25
+ const name = label.trim();
26
+ if (!path.isAbsolute(projectPath) || projectPath.length > 4096 || !name || name.length > 80)
27
+ return Promise.reject(new Error('Invalid project name'));
28
+ const file = filename(statePath);
29
+ const operation = (writes.get(file) ?? Promise.resolve()).catch(() => undefined).then(async () => {
30
+ const names = await readProjectNames(statePath);
31
+ names.set(projectPath, name);
32
+ await mkdir(path.dirname(file), { recursive: true, mode: 0o700 });
33
+ const temporary = `${file}.${randomUUID()}.tmp`;
34
+ try {
35
+ await writeFile(temporary, JSON.stringify(Object.fromEntries(names)), { mode: 0o600 });
36
+ await rename(temporary, file);
37
+ }
38
+ finally {
39
+ await rm(temporary, { force: true });
40
+ }
41
+ return { name };
42
+ });
43
+ writes.set(file, operation);
44
+ void operation.finally(() => { if (writes.get(file) === operation)
45
+ writes.delete(file); }).catch(() => undefined);
46
+ return operation;
47
+ }
48
+ //# sourceMappingURL=projectNames.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projectNames.js","sourceRoot":"","sources":["../../../src/server/system/projectNames.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { ServerProcesses, ServerExternalIp } from '../../shared/protocol.js';
2
+ export declare function externalIp(statePath: string, refresh: boolean): Promise<ServerExternalIp>;
3
+ export declare function serverProcesses(statePath?: string): Promise<ServerProcesses>;
@@ -0,0 +1,172 @@
1
+ import { readFile, readdir, readlink, stat, mkdir, rename, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import { isIPv4 } from 'node:net';
5
+ import { execFile } from 'node:child_process';
6
+ import { promisify } from 'node:util';
7
+ import { identifyProcess } from './processIdentity.js';
8
+ import { readProjectNames } from './projectNames.js';
9
+ const exec = promisify(execFile);
10
+ let pending;
11
+ const ipRequests = new Map();
12
+ export function externalIp(statePath, refresh) {
13
+ const key = path.join(path.dirname(statePath), 'external-ip.json');
14
+ const existing = ipRequests.get(key);
15
+ if (existing)
16
+ return existing;
17
+ const operation = (async () => {
18
+ let cached = { address: null, checkedAt: null, error: null };
19
+ try {
20
+ const value = JSON.parse(await readFile(key, 'utf8'));
21
+ if (typeof value.address === 'string' && isIPv4(value.address) && typeof value.checkedAt === 'string')
22
+ cached = { address: value.address, checkedAt: value.checkedAt, error: null };
23
+ }
24
+ catch { /* First lookup or invalid cache. */ }
25
+ if (cached.address && !refresh)
26
+ return cached;
27
+ try {
28
+ const { stdout } = await exec('curl', ['-4', '--fail', '--silent', '--show-error', '--max-time', '5', '--noproxy', '*', 'https://ifconfig.me/ip'], { timeout: 6000, maxBuffer: 1024 });
29
+ const address = stdout.trim();
30
+ if (!isIPv4(address))
31
+ throw new Error('Invalid IPv4');
32
+ const result = { address, checkedAt: new Date().toISOString(), error: null };
33
+ await mkdir(path.dirname(key), { recursive: true });
34
+ await writeFile(`${key}.tmp`, JSON.stringify(result), { mode: 0o600 });
35
+ await rename(`${key}.tmp`, key);
36
+ return result;
37
+ }
38
+ catch {
39
+ return { ...cached, error: 'Could not refresh external IPv4.' };
40
+ }
41
+ })();
42
+ ipRequests.set(key, operation);
43
+ void operation.finally(() => ipRequests.delete(key)).catch(() => undefined);
44
+ return operation;
45
+ }
46
+ async function sample() {
47
+ const result = [];
48
+ const ids = (await readdir('/proc')).filter((id) => /^\d+$/.test(id));
49
+ // Bound file I/O concurrency even on hosts with many processes.
50
+ let next = 0;
51
+ await Promise.all(Array.from({ length: 8 }, async () => {
52
+ while (next < ids.length) {
53
+ const id = ids[next++];
54
+ try {
55
+ const base = `/proc/${id}`;
56
+ const status = await readFile(`${base}/status`, 'utf8');
57
+ if (Number(status.match(/^Uid:\s+(\d+)/m)?.[1]) !== process.getuid?.())
58
+ continue;
59
+ const raw = await readFile(`${base}/stat`, 'utf8');
60
+ const fields = raw.slice(raw.lastIndexOf(')') + 2).split(' ');
61
+ if (fields[0] === 'Z')
62
+ continue;
63
+ const cwd = await readlink(`${base}/cwd`);
64
+ const cgroup = await readFile(`${base}/cgroup`, 'utf8');
65
+ const name = status.match(/^Name:\s+(.+)/m)?.[1]?.trim() ?? 'process';
66
+ result.push({ pid: Number(id), ppid: Number(fields[1]), name, ticks: Number(fields[11]) + Number(fields[12]), start: fields[19], memory: Number(status.match(/^VmRSS:\s+(\d+)/m)?.[1] ?? 0) * 1024, cwd, manager: cgroup.match(/\/([^/\n]+\.service)(?:\/|$)/m)?.[1] ?? null });
67
+ }
68
+ catch { /* Processes can exit during a snapshot. */ }
69
+ }
70
+ }));
71
+ return result;
72
+ }
73
+ async function cpuTicks() {
74
+ const line = (await readFile('/proc/stat', 'utf8')).split('\n')[0];
75
+ // Guest counters are already included in user/nice.
76
+ return line.trim().split(/\s+/).slice(1, 9).reduce((sum, value) => sum + Number(value), 0);
77
+ }
78
+ export async function serverProcesses(statePath) {
79
+ if (!pending) {
80
+ const work = collect();
81
+ pending = work;
82
+ void work.finally(() => { if (pending === work)
83
+ pending = undefined; }).catch(() => undefined);
84
+ }
85
+ const data = await pending;
86
+ if (!statePath)
87
+ return data;
88
+ const names = await readProjectNames(statePath);
89
+ return { ...data, projects: data.projects.map((project) => ({ ...project, name: names.get(project.path) ?? project.name })) };
90
+ }
91
+ async function collect() {
92
+ const startTicks = await cpuTicks();
93
+ const before = new Map((await sample()).map((entry) => [entry.pid, entry]));
94
+ await new Promise((resolve) => setTimeout(resolve, 250));
95
+ const entries = await sample();
96
+ const elapsed = Math.max(1, await cpuTicks() - startTicks);
97
+ const byId = new Map(entries.map((entry) => [entry.pid, entry]));
98
+ const roots = new Map();
99
+ function project(cwd) {
100
+ const cached = roots.get(cwd);
101
+ if (cached)
102
+ return cached;
103
+ const lookup = (async () => {
104
+ for (let dir = cwd; dir !== path.dirname(dir); dir = path.dirname(dir)) {
105
+ try {
106
+ await stat(path.join(dir, '.git'));
107
+ return dir;
108
+ }
109
+ catch { /* No Git root here. */ }
110
+ }
111
+ return cwd;
112
+ })();
113
+ roots.set(cwd, lookup);
114
+ return lookup;
115
+ }
116
+ const groups = new Map();
117
+ const packageNames = new Map();
118
+ function isNTerminal(script) {
119
+ if (!path.isAbsolute(script) || !script.endsWith('/dist/server/index.js'))
120
+ return Promise.resolve(false);
121
+ const packageFile = path.resolve(path.dirname(script), '../../package.json');
122
+ if (!packageNames.has(packageFile))
123
+ packageNames.set(packageFile, readFile(packageFile, 'utf8').then((value) => JSON.parse(value).name === 'nterminal').catch(() => false));
124
+ return packageNames.get(packageFile);
125
+ }
126
+ const coreCount = os.cpus().length;
127
+ for (const entry of entries) {
128
+ let args = [];
129
+ let executable;
130
+ try {
131
+ args = (await readFile(`/proc/${entry.pid}/cmdline`, 'utf8')).split('\0');
132
+ executable = await readlink(`/proc/${entry.pid}/exe`);
133
+ }
134
+ catch {
135
+ continue;
136
+ }
137
+ const parent = byId.get(entry.ppid);
138
+ const pm2 = parent?.name.startsWith('PM2') === true;
139
+ const identity = identifyProcess(entry.name, executable, args, pm2);
140
+ const nterminal = ['node', 'nodejs'].includes(path.basename(executable)) && await isNTerminal(args[1] ?? '');
141
+ const { name, category, excluded } = nterminal ? { name: 'NTerminal', category: 'tools', excluded: false } : identity;
142
+ if (excluded)
143
+ continue;
144
+ const root = category === 'shared' ? '' : await project(entry.cwd);
145
+ const key = `${category}:${root}`;
146
+ let group = groups.get(key);
147
+ if (!group) {
148
+ group = { path: root, name: root ? path.basename(root) : 'Shared tools', category, cpuPercent: 0, memoryBytes: 0, processes: [] };
149
+ groups.set(key, group);
150
+ }
151
+ const old = before.get(entry.pid);
152
+ const cpuPercent = old?.start === entry.start ? Math.max(0, entry.ticks - old.ticks) / elapsed * coreCount * 100 : 0;
153
+ group.cpuPercent += cpuPercent;
154
+ group.memoryBytes += entry.memory;
155
+ group.processes.push({ pid: entry.pid, ppid: entry.ppid, name, cpuPercent, memoryBytes: entry.memory, manager: pm2 ? 'PM2' : entry.manager });
156
+ }
157
+ // Link through omitted shell wrappers, without merging independent PM2 apps.
158
+ for (const group of groups.values()) {
159
+ const members = new Set(group.processes.map((entry) => entry.pid));
160
+ for (const proc of group.processes) {
161
+ let parent = proc.ppid;
162
+ const seen = new Set([proc.pid]);
163
+ while (parent > 1 && !seen.has(parent) && !members.has(parent)) {
164
+ seen.add(parent);
165
+ parent = byId.get(parent)?.ppid ?? 0;
166
+ }
167
+ proc.ppid = members.has(parent) && !seen.has(parent) ? parent : 0;
168
+ }
169
+ }
170
+ return { checkedAt: new Date().toISOString(), projects: [...groups.values()].sort((a, b) => b.memoryBytes - a.memoryBytes) };
171
+ }
172
+ //# sourceMappingURL=serverInfo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serverInfo.js","sourceRoot":"","sources":["../../../src/server/system/serverInfo.ts"],"names":[],"mappings":"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"}
@@ -56,6 +56,17 @@ export interface PtyTranscriptSource {
56
56
  sessionId: string;
57
57
  filePath: string;
58
58
  }
59
+ export interface PtyTranscriptCandidate extends PtyTranscriptSource {
60
+ cwd?: string;
61
+ title?: string;
62
+ updatedAt?: string;
63
+ verification?: 'verified' | 'unknown';
64
+ readable?: boolean;
65
+ }
66
+ export interface PtyTranscriptCandidates {
67
+ binding: string;
68
+ candidates: PtyTranscriptCandidate[];
69
+ }
59
70
  export interface PtyAdapter {
60
71
  spawn(options: PtySpawnOptions & {
61
72
  title?: string;
@@ -72,6 +83,7 @@ export interface PtyAdapter {
72
83
  }>>;
73
84
  captureHistory?(sessionId: string, options: PtyHistoryCaptureOptions): MaybePromise<PtyHistoryCapture | null>;
74
85
  resolveTranscriptSource?(sessionId: string): MaybePromise<PtyTranscriptSource | null>;
86
+ resolveTranscriptCandidates?(sessionId: string): Promise<PtyTranscriptCandidates>;
75
87
  scrollbackScroll?(sessionId: string, lines: number): MaybePromise<boolean>;
76
88
  cancelScrollbackMode?(sessionId: string): MaybePromise<boolean>;
77
89
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PtyAdapter.js","sourceRoot":"","sources":["../../../src/server/terminal/PtyAdapter.ts"],"names":[],"mappings":"AA6BA,MAAM,OAAO,0BAA2B,SAAQ,KAAK;IACnD,YAAY,SAAiB;QAC3B,KAAK,CAAC,+BAA+B,SAAS,iBAAiB,CAAC,CAAC;QACjE,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAC;IAC3C,CAAC;CACF;AAgHD,4EAA4E;AAC5E,+DAA+D;AAC/D,MAAM,UAAU,sBAAsB,CAAC,GAAW;IAChD,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACrE,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,UAAU,CAAC;AAC9C,CAAC"}
1
+ {"version":3,"file":"PtyAdapter.js","sourceRoot":"","sources":["../../../src/server/terminal/PtyAdapter.ts"],"names":[],"mappings":"AA6BA,MAAM,OAAO,0BAA2B,SAAQ,KAAK;IACnD,YAAY,SAAiB;QAC3B,KAAK,CAAC,+BAA+B,SAAS,iBAAiB,CAAC,CAAC;QACjE,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAC;IAC3C,CAAC;CACF;AAkID,4EAA4E;AAC5E,+DAA+D;AAC/D,MAAM,UAAU,sBAAsB,CAAC,GAAW;IAChD,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACrE,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,UAAU,CAAC;AAC9C,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import type { AppConfig } from '../config.js';
2
- import type { ServerTerminalMessage, TerminalHistoryCursor, TerminalHistoryResponse, TerminalSubagentsResponse, TerminalTranscriptHistoryResponse, TerminalTranscriptSourceResponse } from '../../shared/protocol.js';
2
+ import type { ServerTerminalMessage, TerminalHistoryCursor, TerminalHistoryResponse, TerminalSubagentsResponse, TerminalTranscriptHistoryRequest, TerminalTranscriptHistoryResponse, TerminalTranscriptSourceResponse, TerminalTranscriptSearchRequest, TerminalTranscriptSearchResponse } from '../../shared/protocol.js';
3
3
  import type { TerminalCompletionOutcome, TerminalId, TerminalSummary } from '../../shared/types.js';
4
4
  import { type PtyAdapter } from './PtyAdapter.js';
5
5
  export interface CreateTerminalOptions {
@@ -69,6 +69,7 @@ export declare class TerminalManager {
69
69
  private readonly shellBasename;
70
70
  private readonly terminalCapabilities;
71
71
  private readonly agentTranscriptTracker;
72
+ private readonly transcriptStore;
72
73
  private readonly transcriptQueue;
73
74
  private readonly lastDiagnosticAt;
74
75
  private readonly completionStates;
@@ -117,13 +118,11 @@ export declare class TerminalManager {
117
118
  limit?: number;
118
119
  }): Promise<TerminalHistoryResponse | null>;
119
120
  private resolveLiveTranscriptSource;
121
+ private transcriptCandidateId;
122
+ private transcriptSelectionStillLive;
120
123
  private transcriptRequestStillLive;
121
- getTerminalTranscriptHistory(id: TerminalId, options?: {
122
- cursor?: string;
123
- limit?: number;
124
- maxLines?: number;
125
- includeTools?: boolean;
126
- }): Promise<TerminalTranscriptHistoryResponse | null>;
124
+ getTerminalTranscriptHistory(id: TerminalId, options?: TerminalTranscriptHistoryRequest, signal?: AbortSignal): Promise<TerminalTranscriptHistoryResponse | null>;
125
+ searchTerminalTranscript(id: TerminalId, options: TerminalTranscriptSearchRequest, signal?: AbortSignal): Promise<TerminalTranscriptSearchResponse | null>;
127
126
  getTerminalTranscriptSource(id: TerminalId): Promise<TerminalTranscriptSourceResponse | null>;
128
127
  getTerminalSubagents(id: TerminalId): Promise<TerminalSubagentsResponse | null>;
129
128
  getTerminalSubagentTranscript(id: TerminalId, subagentId: string, options?: {
@@ -1,9 +1,10 @@
1
- import { randomUUID } from 'node:crypto';
1
+ import { createHash, randomUUID } from 'node:crypto';
2
2
  import path from 'node:path';
3
3
  import { normalizeTerminalTitle } from './PtyAdapter.js';
4
4
  import { resolveProcessCwd } from './cwdResolver.js';
5
5
  import { OutputBuffer } from './outputBuffer.js';
6
6
  import { readTranscriptHistoryPage } from './transcriptHistory.js';
7
+ import { TranscriptStore } from './transcriptStore.js';
7
8
  import { AgentTranscriptTracker } from './agentTranscriptStatus.js';
8
9
  import { listTerminalSubagents, resolveSubagentTranscriptSource, subagentSummary } from './subagentSources.js';
9
10
  import { mapWithConcurrency } from './asyncCommand.js';
@@ -43,6 +44,7 @@ export class TerminalManager {
43
44
  shellBasename;
44
45
  terminalCapabilities;
45
46
  agentTranscriptTracker = new AgentTranscriptTracker();
47
+ transcriptStore;
46
48
  transcriptQueue = [];
47
49
  lastDiagnosticAt = new Map();
48
50
  completionStates = new Map();
@@ -63,6 +65,7 @@ export class TerminalManager {
63
65
  constructor(config, ptyAdapter, options = {}) {
64
66
  this.config = config;
65
67
  this.ptyAdapter = ptyAdapter;
68
+ this.transcriptStore = new TranscriptStore(path.join(path.dirname(config.statePath), 'transcript-cache'));
66
69
  this.resolveCwd = options.resolveCwd ?? resolveProcessCwd;
67
70
  this.now = options.now ?? (() => new Date());
68
71
  this.onCompletion = options.onCompletion ?? null;
@@ -279,6 +282,7 @@ export class TerminalManager {
279
282
  // interval — called by the server's graceful-shutdown path.
280
283
  dispose() {
281
284
  this.disposed = true;
285
+ this.transcriptStore.dispose();
282
286
  this.shutdownRequested = true;
283
287
  if (this.statusPollTimer) {
284
288
  clearInterval(this.statusPollTimer);
@@ -674,42 +678,84 @@ export class TerminalManager {
674
678
  // Shared liveness guard for the transcript endpoints: resolve the pane's
675
679
  // source, re-validating the record after the await so a pane that closed
676
680
  // or switched sessions mid-request never serves another session's data.
677
- async resolveLiveTranscriptSource(id) {
681
+ async resolveLiveTranscriptSource(id, selection = {}) {
678
682
  const record = this.terminals.get(id);
679
683
  if (!record || record.summary.status !== 'running' || record.closing) {
680
684
  return null;
681
685
  }
682
686
  const generation = record.transcriptGeneration;
683
- const source = await this.ptyAdapter.resolveTranscriptSource?.(id);
687
+ let source;
688
+ if (selection.candidateId) {
689
+ const result = await this.ptyAdapter.resolveTranscriptCandidates?.(id);
690
+ source = result?.candidates.find((candidate) => this.transcriptCandidateId(id, result.binding, candidate) === selection.candidateId);
691
+ }
692
+ else {
693
+ source = await this.ptyAdapter.resolveTranscriptSource?.(id);
694
+ }
684
695
  if (!source ||
696
+ (selection.expectedSessionId !== undefined && source.sessionId !== selection.expectedSessionId) ||
685
697
  !transcriptSourceMatchesForeground(record.summary, source.provider) ||
686
698
  !this.transcriptRequestStillLive(id, record, generation)) {
687
699
  return null;
688
700
  }
689
701
  return { record, generation, source };
690
702
  }
703
+ transcriptCandidateId(id, binding, source) {
704
+ return createHash('sha256').update(JSON.stringify([id, binding, source.provider, source.sessionId, source.filePath])).digest('hex');
705
+ }
706
+ async transcriptSelectionStillLive(id, live, selection) {
707
+ if (!this.transcriptRequestStillLive(id, live.record, live.generation))
708
+ return false;
709
+ const current = await this.resolveLiveTranscriptSource(id, selection);
710
+ return Boolean(current && current.source.provider === live.source.provider &&
711
+ current.source.sessionId === live.source.sessionId && current.source.filePath === live.source.filePath);
712
+ }
691
713
  transcriptRequestStillLive(id, record, generation) {
692
714
  return this.terminals.get(id) === record &&
693
715
  record.summary.status === 'running' &&
694
716
  !record.closing &&
695
717
  record.transcriptGeneration === generation;
696
718
  }
697
- async getTerminalTranscriptHistory(id, options = {}) {
698
- const live = await this.resolveLiveTranscriptSource(id);
699
- if (!live) {
719
+ async getTerminalTranscriptHistory(id, options = {}, signal) {
720
+ if (signal?.aborted)
721
+ return null;
722
+ const live = await this.resolveLiveTranscriptSource(id, options);
723
+ if (!live || signal?.aborted) {
700
724
  return null;
701
725
  }
702
- const page = await readTranscriptHistoryPage(id, live.source, options);
703
- return this.transcriptRequestStillLive(id, live.record, live.generation) ? page : null;
726
+ const page = await this.transcriptStore.readPage(id, live.source, { ...options, includeTools: options.includeTools ?? false }, signal);
727
+ return !signal?.aborted && await this.transcriptSelectionStillLive(id, live, options) ? page : null;
728
+ }
729
+ async searchTerminalTranscript(id, options, signal) {
730
+ if (signal?.aborted)
731
+ return null;
732
+ const live = await this.resolveLiveTranscriptSource(id, options);
733
+ if (!live || signal?.aborted)
734
+ return null;
735
+ const result = await this.transcriptStore.search(id, live.source, options, signal);
736
+ if (!result || signal?.aborted || !await this.transcriptSelectionStillLive(id, live, options))
737
+ return null;
738
+ return { ...result, terminalId: id, source: { provider: live.source.provider, sessionId: live.source.sessionId } };
704
739
  }
705
740
  async getTerminalTranscriptSource(id) {
741
+ const record = this.terminals.get(id);
742
+ if (!record || record.summary.status !== 'running' || record.closing)
743
+ return null;
744
+ const generation = record.transcriptGeneration;
706
745
  const live = await this.resolveLiveTranscriptSource(id);
707
- if (!live) {
746
+ // Exact matches need no extra process scan on every source poll. Manual
747
+ // selections still use the candidate resolver above for fresh validation.
748
+ const choices = live ? undefined : await this.ptyAdapter.resolveTranscriptCandidates?.(id);
749
+ if (!this.transcriptRequestStillLive(id, record, generation))
708
750
  return null;
709
- }
710
751
  return {
711
752
  terminalId: id,
712
- source: { provider: live.source.provider, sessionId: live.source.sessionId }
753
+ source: live ? { provider: live.source.provider, sessionId: live.source.sessionId } : null,
754
+ candidates: (choices?.candidates ?? []).filter((source) => transcriptSourceMatchesForeground(record.summary, source.provider)).map((source) => ({
755
+ id: this.transcriptCandidateId(id, choices.binding, source),
756
+ provider: source.provider, sessionId: source.sessionId,
757
+ title: (source.title ?? '').slice(0, 240), cwd: source.cwd ?? '', updatedAt: source.updatedAt ?? null
758
+ }))
713
759
  };
714
760
  }
715
761
  async getTerminalSubagents(id) {