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.
- package/README.md +4 -2
- package/bin/nterminal.js +5 -0
- package/dist/client/assets/MarkdownPreview-CWK2qIZp.js +1 -0
- package/dist/client/assets/{TranscriptMarkdownBody-Ca7wEE6u.js → TranscriptMarkdownBody-Ddi-rw6X.js} +1 -1
- package/dist/client/assets/index-CcAQThtj.js +56 -0
- package/dist/client/assets/{index-CFBcr64T.js → index-DDCpqDNb.js} +1 -1
- package/dist/client/assets/index-DTTX24tu.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/scripts/onboarding.js +9 -0
- package/dist/scripts/onboarding.js.map +1 -1
- package/dist/server/agent/agentProxy.d.ts +1 -0
- package/dist/server/agent/agentProxy.js +3 -1
- package/dist/server/agent/agentProxy.js.map +1 -1
- package/dist/server/agent/agentRoutes.js +21 -2
- package/dist/server/agent/agentRoutes.js.map +1 -1
- package/dist/server/index.js +2 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/routes/agentManagementRoutes.js +43 -2
- package/dist/server/routes/agentManagementRoutes.js.map +1 -1
- package/dist/server/routes/terminalRoutes.js +19 -2
- package/dist/server/routes/terminalRoutes.js.map +1 -1
- package/dist/server/routes/transcriptQuery.d.ts +14 -0
- package/dist/server/routes/transcriptQuery.js +57 -0
- package/dist/server/routes/transcriptQuery.js.map +1 -0
- package/dist/server/system/processIdentity.d.ts +6 -0
- package/dist/server/system/processIdentity.js +30 -0
- package/dist/server/system/processIdentity.js.map +1 -0
- package/dist/server/system/projectNames.d.ts +25 -0
- package/dist/server/system/projectNames.js +48 -0
- package/dist/server/system/projectNames.js.map +1 -0
- package/dist/server/system/serverInfo.d.ts +3 -0
- package/dist/server/system/serverInfo.js +172 -0
- package/dist/server/system/serverInfo.js.map +1 -0
- package/dist/server/terminal/PtyAdapter.d.ts +12 -0
- package/dist/server/terminal/PtyAdapter.js.map +1 -1
- package/dist/server/terminal/TerminalManager.d.ts +6 -7
- package/dist/server/terminal/TerminalManager.js +57 -11
- package/dist/server/terminal/TerminalManager.js.map +1 -1
- package/dist/server/terminal/TmuxPtyAdapter.d.ts +15 -1
- package/dist/server/terminal/TmuxPtyAdapter.js +284 -13
- package/dist/server/terminal/TmuxPtyAdapter.js.map +1 -1
- package/dist/server/terminal/codexTranscriptSource.d.ts +18 -1
- package/dist/server/terminal/codexTranscriptSource.js +263 -68
- package/dist/server/terminal/codexTranscriptSource.js.map +1 -1
- package/dist/server/terminal/sqliteReader.d.ts +21 -0
- package/dist/server/terminal/sqliteReader.js +158 -0
- package/dist/server/terminal/sqliteReader.js.map +1 -0
- package/dist/server/terminal/subagentSources.js +3 -2
- package/dist/server/terminal/subagentSources.js.map +1 -1
- package/dist/server/terminal/transcriptHistory.d.ts +34 -0
- package/dist/server/terminal/transcriptHistory.js +81 -36
- package/dist/server/terminal/transcriptHistory.js.map +1 -1
- package/dist/server/terminal/transcriptStore.d.ts +64 -0
- package/dist/server/terminal/transcriptStore.js +779 -0
- package/dist/server/terminal/transcriptStore.js.map +1 -0
- package/dist/shared/nodeVersion.d.ts +3 -0
- package/dist/shared/nodeVersion.js +12 -0
- package/dist/shared/nodeVersion.js.map +1 -0
- package/dist/shared/protocol.d.ts +56 -0
- package/dist/shared/protocol.js.map +1 -1
- package/dist/shared/transcriptSearch.d.ts +2 -0
- package/dist/shared/transcriptSearch.js +12 -0
- package/dist/shared/transcriptSearch.js.map +1 -0
- package/dist/src/shared/nodeVersion.js +12 -0
- package/dist/src/shared/nodeVersion.js.map +1 -0
- package/docs/terminal-history.md +22 -4
- package/package.json +3 -2
- package/scripts/nterminalctl +8 -4
- package/dist/client/assets/MarkdownPreview-C5AWbbg_.js +0 -1
- package/dist/client/assets/index-Cca8_nsS.css +0 -1
- 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":"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"}
|
|
@@ -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,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;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
699
|
-
|
|
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
|
|
703
|
-
return this.
|
|
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
|
-
|
|
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) {
|