koneck 2.26.0 → 2.28.0
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/doctor.d.ts +27 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +70 -9
- package/dist/doctor.js.map +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +4 -1
- package/dist/engine.js.map +1 -1
- package/dist/ink-chat.d.ts +12 -3
- package/dist/ink-chat.d.ts.map +1 -1
- package/dist/ink-chat.js +246 -30
- package/dist/ink-chat.js.map +1 -1
- package/dist/quality-gate.d.ts +5 -1
- package/dist/quality-gate.d.ts.map +1 -1
- package/dist/quality-gate.js +8 -6
- package/dist/quality-gate.js.map +1 -1
- package/dist/session.d.ts +10 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +12 -1
- package/dist/session.js.map +1 -1
- package/dist/web/api.d.ts.map +1 -1
- package/dist/web/api.js +122 -27
- package/dist/web/api.js.map +1 -1
- package/dist/web/changes.d.ts +55 -0
- package/dist/web/changes.d.ts.map +1 -0
- package/dist/web/changes.js +215 -0
- package/dist/web/changes.js.map +1 -0
- package/dist/web/files.d.ts +43 -0
- package/dist/web/files.d.ts.map +1 -0
- package/dist/web/files.js +131 -0
- package/dist/web/files.js.map +1 -0
- package/dist/web/history.d.ts +61 -0
- package/dist/web/history.d.ts.map +1 -0
- package/dist/web/history.js +197 -0
- package/dist/web/history.js.map +1 -0
- package/dist/web/mark.d.ts +15 -0
- package/dist/web/mark.d.ts.map +1 -0
- package/dist/web/mark.js +15 -0
- package/dist/web/mark.js.map +1 -0
- package/dist/web/search.d.ts +45 -0
- package/dist/web/search.d.ts.map +1 -0
- package/dist/web/search.js +147 -0
- package/dist/web/search.js.map +1 -0
- package/dist/web/sessions.d.ts +8 -0
- package/dist/web/sessions.d.ts.map +1 -1
- package/dist/web/sessions.js +9 -0
- package/dist/web/sessions.js.map +1 -1
- package/dist/web/ui-client.d.ts +14 -0
- package/dist/web/ui-client.d.ts.map +1 -0
- package/dist/web/ui-client.js +0 -0
- package/dist/web/ui-client.js.map +1 -0
- package/dist/web/ui-css.d.ts +15 -0
- package/dist/web/ui-css.d.ts.map +1 -0
- package/dist/web/ui-css.js +329 -0
- package/dist/web/ui-css.js.map +1 -0
- package/dist/web/ui.d.ts +6 -9
- package/dist/web/ui.d.ts.map +1 -1
- package/dist/web/ui.js +100 -541
- package/dist/web/ui.js.map +1 -1
- package/dist/workspace-trust.d.ts +8 -0
- package/dist/workspace-trust.d.ts.map +1 -1
- package/dist/workspace-trust.js +11 -0
- package/dist/workspace-trust.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a session changed on disk, and what the change was.
|
|
3
|
+
*
|
|
4
|
+
* KONECK has a git-based checkpoint already, and it is the right thing when there is a repository:
|
|
5
|
+
* `git write-tree` is cheap and exact. But it needs one, and the workspace this was asked for has
|
|
6
|
+
* no repository of its own — the enclosing directory has one, which is a tree full of other
|
|
7
|
+
* projects and precisely what the git tools are now withheld from touching. A diff view that only
|
|
8
|
+
* works in a git repo is a diff view that does not work.
|
|
9
|
+
*
|
|
10
|
+
* So the workspace is read directly: a snapshot of the text files before a turn, another after, and
|
|
11
|
+
* the difference between them. Bounded in every direction, because a workspace can contain a
|
|
12
|
+
* node_modules and a build output and a database.
|
|
13
|
+
*/
|
|
14
|
+
import { readdir, readFile, stat } from 'fs/promises';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
/** Directories never worth reading: generated, enormous, or not the user's work. */
|
|
17
|
+
const SKIP_DIRS = new Set([
|
|
18
|
+
'node_modules', '.git', '.svn', '.hg', 'dist', 'build', 'out', 'target',
|
|
19
|
+
'.next', '.nuxt', '.venv', 'venv', '__pycache__', '.cache', '.turbo',
|
|
20
|
+
'coverage', '.pytest_cache', '.mypy_cache', '.gradle', 'vendor', '.koneck',
|
|
21
|
+
]);
|
|
22
|
+
/** A file larger than this is recorded by size alone: no diff is readable at that length. */
|
|
23
|
+
export const MAX_DIFF_BYTES = 512 * 1024;
|
|
24
|
+
/** A cap on how many files are held, so a snapshot cannot become the memory problem. */
|
|
25
|
+
export const MAX_FILES = 4_000;
|
|
26
|
+
/** Anything that is plainly not text does not get diffed. */
|
|
27
|
+
const BINARY_EXT = new Set([
|
|
28
|
+
'.png', '.jpg', '.jpeg', '.gif', '.webp', '.avif', '.ico', '.bmp', '.svgz',
|
|
29
|
+
'.pdf', '.zip', '.gz', '.tgz', '.bz2', '.xz', '.7z', '.rar', '.jar',
|
|
30
|
+
'.mp3', '.mp4', '.mov', '.avi', '.mkv', '.wav', '.flac', '.ogg',
|
|
31
|
+
'.woff', '.woff2', '.ttf', '.otf', '.eot',
|
|
32
|
+
'.so', '.dylib', '.dll', '.exe', '.bin', '.wasm', '.db', '.sqlite', '.sqlite3',
|
|
33
|
+
'.pyc', '.class', '.o', '.a', '.node',
|
|
34
|
+
]);
|
|
35
|
+
const looksBinary = (name) => BINARY_EXT.has(path.extname(name).toLowerCase());
|
|
36
|
+
/** Written as a code point so no source file needs to contain the byte itself. */
|
|
37
|
+
const NUL = String.fromCharCode(0);
|
|
38
|
+
/**
|
|
39
|
+
* Every text file under a workspace, with its contents.
|
|
40
|
+
*
|
|
41
|
+
* Content is read rather than hashed because the diff needs it anyway, and a workspace that fits
|
|
42
|
+
* in the caps above is small enough that reading it is faster than deciding not to.
|
|
43
|
+
*/
|
|
44
|
+
export async function snapshot(cwd) {
|
|
45
|
+
const out = new Map();
|
|
46
|
+
async function walk(dir, depth) {
|
|
47
|
+
if (out.size >= MAX_FILES || depth > 12)
|
|
48
|
+
return;
|
|
49
|
+
let entries;
|
|
50
|
+
try {
|
|
51
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
if (out.size >= MAX_FILES)
|
|
58
|
+
return;
|
|
59
|
+
const full = path.join(dir, entry.name);
|
|
60
|
+
if (entry.isDirectory()) {
|
|
61
|
+
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith('.'))
|
|
62
|
+
continue;
|
|
63
|
+
await walk(full, depth + 1);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (!entry.isFile())
|
|
67
|
+
continue; // a socket or a symlink loop
|
|
68
|
+
const rel = path.relative(cwd, full);
|
|
69
|
+
try {
|
|
70
|
+
const info = await stat(full);
|
|
71
|
+
const state = { size: info.size, mtimeMs: info.mtimeMs };
|
|
72
|
+
if (!looksBinary(entry.name) && info.size <= MAX_DIFF_BYTES) {
|
|
73
|
+
const text = await readFile(full, 'utf8');
|
|
74
|
+
// A NUL byte in the first stretch means it is binary whatever the extension says.
|
|
75
|
+
if (!text.slice(0, 4_000).includes(NUL))
|
|
76
|
+
state.text = text;
|
|
77
|
+
}
|
|
78
|
+
out.set(rel, state);
|
|
79
|
+
}
|
|
80
|
+
catch { /* vanished between readdir and stat, or unreadable */ }
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
await walk(cwd, 0);
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The longest common subsequence of two line arrays, as an edit script.
|
|
88
|
+
*
|
|
89
|
+
* Written out rather than pulled in: a diff is forty lines of Myers and the dependency would be
|
|
90
|
+
* another thing to keep current. Bounded by area, and above the bound it reports a whole-file
|
|
91
|
+
* replacement rather than spending a second on a file nobody will read the diff of.
|
|
92
|
+
*/
|
|
93
|
+
function diffLines(before, after) {
|
|
94
|
+
const n = before.length, m = after.length;
|
|
95
|
+
if (n * m > 4_000_000) {
|
|
96
|
+
return [...before.map(l => ['-', l]),
|
|
97
|
+
...after.map(l => ['+', l])];
|
|
98
|
+
}
|
|
99
|
+
// Classic LCS table. Memory is the reason for the area bound above.
|
|
100
|
+
const lcs = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
|
|
101
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
102
|
+
for (let j = m - 1; j >= 0; j--) {
|
|
103
|
+
lcs[i][j] = before[i] === after[j]
|
|
104
|
+
? lcs[i + 1][j + 1] + 1
|
|
105
|
+
: Math.max(lcs[i + 1][j], lcs[i][j + 1]);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const script = [];
|
|
109
|
+
let i = 0, j = 0;
|
|
110
|
+
while (i < n && j < m) {
|
|
111
|
+
if (before[i] === after[j]) {
|
|
112
|
+
script.push([' ', before[i]]);
|
|
113
|
+
i++;
|
|
114
|
+
j++;
|
|
115
|
+
}
|
|
116
|
+
else if (lcs[i + 1][j] >= lcs[i][j + 1]) {
|
|
117
|
+
script.push(['-', before[i]]);
|
|
118
|
+
i++;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
script.push(['+', after[j]]);
|
|
122
|
+
j++;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
while (i < n)
|
|
126
|
+
script.push(['-', before[i++]]);
|
|
127
|
+
while (j < m)
|
|
128
|
+
script.push(['+', after[j++]]);
|
|
129
|
+
return script;
|
|
130
|
+
}
|
|
131
|
+
/** Context lines either side of a change. Three is what everybody's diff shows. */
|
|
132
|
+
const CONTEXT = 3;
|
|
133
|
+
/** The edit script as a unified diff, with untouched stretches collapsed. */
|
|
134
|
+
export function unified(before, after) {
|
|
135
|
+
const script = diffLines(before.split('\n'), after.split('\n'));
|
|
136
|
+
let added = 0, removed = 0;
|
|
137
|
+
for (const [kind] of script) {
|
|
138
|
+
if (kind === '+')
|
|
139
|
+
added++;
|
|
140
|
+
else if (kind === '-')
|
|
141
|
+
removed++;
|
|
142
|
+
}
|
|
143
|
+
// Which lines to keep: any change, plus context around it.
|
|
144
|
+
const keep = new Array(script.length).fill(false);
|
|
145
|
+
for (let k = 0; k < script.length; k++) {
|
|
146
|
+
if (script[k][0] === ' ')
|
|
147
|
+
continue;
|
|
148
|
+
for (let c = Math.max(0, k - CONTEXT); c <= Math.min(script.length - 1, k + CONTEXT); c++) {
|
|
149
|
+
keep[c] = true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const lines = [];
|
|
153
|
+
let gap = false;
|
|
154
|
+
for (let k = 0; k < script.length; k++) {
|
|
155
|
+
if (!keep[k]) {
|
|
156
|
+
gap = true;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (gap) {
|
|
160
|
+
lines.push('@@');
|
|
161
|
+
gap = false;
|
|
162
|
+
}
|
|
163
|
+
lines.push(script[k][0] + script[k][1]);
|
|
164
|
+
}
|
|
165
|
+
return { diff: lines.join('\n'), added, removed };
|
|
166
|
+
}
|
|
167
|
+
/** What changed between two snapshots, newest interesting first. */
|
|
168
|
+
export function compare(before, after) {
|
|
169
|
+
const changes = [];
|
|
170
|
+
for (const [file, now] of after) {
|
|
171
|
+
const was = before.get(file);
|
|
172
|
+
if (!was) {
|
|
173
|
+
const lines = now.text ? now.text.split('\n').length : 0;
|
|
174
|
+
changes.push({
|
|
175
|
+
path: file, kind: 'added', added: lines, removed: 0, size: now.size,
|
|
176
|
+
diff: now.text === undefined ? null
|
|
177
|
+
: now.text.split('\n').map(l => '+' + l).join('\n'),
|
|
178
|
+
});
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
// mtime alone is not evidence: a tool can rewrite a file with identical contents.
|
|
182
|
+
if (was.size === now.size && was.text !== undefined && was.text === now.text)
|
|
183
|
+
continue;
|
|
184
|
+
if (was.text === undefined || now.text === undefined) {
|
|
185
|
+
if (was.size === now.size && was.mtimeMs === now.mtimeMs)
|
|
186
|
+
continue;
|
|
187
|
+
changes.push({ path: file, kind: 'modified', added: 0, removed: 0, diff: null, size: now.size });
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (was.text === now.text)
|
|
191
|
+
continue;
|
|
192
|
+
const { diff, added, removed } = unified(was.text, now.text);
|
|
193
|
+
changes.push({ path: file, kind: 'modified', added, removed, diff, size: now.size });
|
|
194
|
+
}
|
|
195
|
+
for (const [file, was] of before) {
|
|
196
|
+
if (after.has(file))
|
|
197
|
+
continue;
|
|
198
|
+
changes.push({
|
|
199
|
+
path: file, kind: 'deleted', added: 0,
|
|
200
|
+
removed: was.text ? was.text.split('\n').length : 0, size: 0,
|
|
201
|
+
diff: was.text === undefined ? null : was.text.split('\n').map(l => '-' + l).join('\n'),
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
// Most changed first, since that is what anybody looks at.
|
|
205
|
+
return changes.sort((a, b) => (b.added + b.removed) - (a.added + a.removed));
|
|
206
|
+
}
|
|
207
|
+
/** Totals for a header line. */
|
|
208
|
+
export function summarise(changes) {
|
|
209
|
+
return {
|
|
210
|
+
files: changes.length,
|
|
211
|
+
added: changes.reduce((n, c) => n + c.added, 0),
|
|
212
|
+
removed: changes.reduce((n, c) => n + c.removed, 0),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=changes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"changes.js","sourceRoot":"","sources":["../../src/web/changes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,oFAAoF;AACpF,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC;IACxB,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ;IACvE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ;IACpE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS;CAC3E,CAAC,CAAC;AAEH,6FAA6F;AAC7F,MAAM,CAAC,MAAM,cAAc,GAAG,GAAG,GAAG,IAAI,CAAC;AACzC,wFAAwF;AACxF,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,CAAC;AAE/B,6DAA6D;AAC7D,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC;IACzB,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO;IAC1E,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM;IACnE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;IAC/D,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IACzC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU;IAC9E,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO;CACtC,CAAC,CAAC;AAWH,MAAM,WAAW,GAAG,CAAC,IAAY,EAAW,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AAEhG,kFAAkF;AAClF,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;AAEnC;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,GAAW;IACxC,MAAM,GAAG,GAAa,IAAI,GAAG,EAAE,CAAC;IAEhC,KAAK,UAAU,IAAI,CAAC,GAAW,EAAE,KAAa;QAC5C,IAAI,GAAG,CAAC,IAAI,IAAI,SAAS,IAAI,KAAK,GAAG,EAAE;YAAE,OAAO;QAChD,IAAI,OAAO,CAAC;QACZ,IAAI,CAAC;YAAC,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO;QAAC,CAAC;QAChF,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,IAAI,GAAG,CAAC,IAAI,IAAI,SAAS;gBAAE,OAAO;YAClC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACxC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,IAAI,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;oBAAE,SAAS;gBACtE,MAAM,IAAI,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;gBAC5B,SAAS;YACX,CAAC;YACD,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;gBAAE,SAAS,CAAuB,6BAA6B;YAClF,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACrC,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC9B,MAAM,KAAK,GAAc,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;gBACpE,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,IAAI,cAAc,EAAE,CAAC;oBAC5D,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;oBAC1C,kFAAkF;oBAClF,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;wBAAE,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;gBAC7D,CAAC;gBACD,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YACtB,CAAC;YAAC,MAAM,CAAC,CAAC,sDAAsD,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAED,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IACnB,OAAO,GAAG,CAAC;AACb,CAAC;AAYD;;;;;;GAMG;AACH,SAAS,SAAS,CAAC,MAAyB,EAAE,KAAwB;IACpE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;IAC1C,IAAI,CAAC,GAAG,CAAC,GAAG,SAAS,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAA8B,CAAC;YACzD,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAA8B,CAAC,CAAC,CAAC;IACpE,CAAC;IACD,oEAAoE;IACpE,MAAM,GAAG,GAAe,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,KAAK,CAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9F,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAChC,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAChC,GAAG,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC;gBACjC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC,CAAC,GAAG,CAAC,CAAE,GAAG,CAAC;gBACzB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC,CAAC,CAAE,EAAE,GAAG,CAAC,CAAC,CAAE,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAqC,EAAE,CAAC;IACpD,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IACjB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACtB,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC;YAAC,CAAC,EAAE,CAAC;YAAC,CAAC,EAAE,CAAC;QAAC,CAAC;aACpE,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC,CAAC,CAAE,IAAI,GAAG,CAAC,CAAC,CAAE,CAAC,CAAC,GAAG,CAAC,CAAE,EAAE,CAAC;YAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC;YAAC,CAAC,EAAE,CAAC;QAAC,CAAC;aAChF,CAAC;YAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC;YAAC,CAAC,EAAE,CAAC;QAAC,CAAC;IAC9C,CAAC;IACD,OAAO,CAAC,GAAG,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,EAAE,CAAE,CAAC,CAAC,CAAC;IAC/C,OAAO,CAAC,GAAG,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,EAAE,CAAE,CAAC,CAAC,CAAC;IAC9C,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,mFAAmF;AACnF,MAAM,OAAO,GAAG,CAAC,CAAC;AAElB,6EAA6E;AAC7E,MAAM,UAAU,OAAO,CAAC,MAAc,EAAE,KAAa;IACnD,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAChE,IAAI,KAAK,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC;IAC3B,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,MAAM,EAAE,CAAC;QAAC,IAAI,IAAI,KAAK,GAAG;YAAE,KAAK,EAAE,CAAC;aAAM,IAAI,IAAI,KAAK,GAAG;YAAE,OAAO,EAAE,CAAC;IAAC,CAAC;IAE7F,2DAA2D;IAC3D,MAAM,IAAI,GAAG,IAAI,KAAK,CAAU,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,IAAI,MAAM,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE,SAAS;QACpC,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC1F,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;QACjB,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,GAAG,GAAG,KAAK,CAAC;IAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAAC,GAAG,GAAG,IAAI,CAAC;YAAC,SAAS;QAAC,CAAC;QACvC,IAAI,GAAG,EAAE,CAAC;YAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAAC,GAAG,GAAG,KAAK,CAAC;QAAC,CAAC;QAC3C,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AACpD,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,OAAO,CAAC,MAAgB,EAAE,KAAe;IACvD,MAAM,OAAO,GAAiB,EAAE,CAAC;IAEjC,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,KAAK,EAAE,CAAC;QAChC,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC7B,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YACzD,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI;gBACnE,IAAI,EAAE,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI;oBACjC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;aACtD,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QACD,kFAAkF;QAClF,IAAI,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI;YAAE,SAAS;QACvF,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YACrD,IAAI,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,OAAO,KAAK,GAAG,CAAC,OAAO;gBAAE,SAAS;YACnE,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;YACjG,SAAS;QACX,CAAC;QACD,IAAI,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI;YAAE,SAAS;QACpC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;QAC7D,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IACvF,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,EAAE,CAAC;QACjC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QAC9B,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;YACrC,OAAO,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;YAC5D,IAAI,EAAE,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;SACxF,CAAC,CAAC;IACL,CAAC;IAED,2DAA2D;IAC3D,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED,gCAAgC;AAChC,MAAM,UAAU,SAAS,CAAC,OAA8B;IAGtD,OAAO;QACL,KAAK,EAAE,OAAO,CAAC,MAAM;QACrB,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QAC/C,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;KACpD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading the workspace: a tree to browse, and a file to look at.
|
|
3
|
+
*
|
|
4
|
+
* Every path arrives from a URL, so every path is resolved and checked against the workspace root
|
|
5
|
+
* before anything is opened. That check is the whole of this module's security, and it is done once
|
|
6
|
+
* in one function rather than at each call site — `..` is the obvious attack and a symlink pointing
|
|
7
|
+
* out of the tree is the one that gets forgotten, so the resolved real path is what gets compared.
|
|
8
|
+
*
|
|
9
|
+
* Read-only. Editing from the browser is a different feature with a different risk, and mixing the
|
|
10
|
+
* two would mean a traversal bug wrote instead of leaked.
|
|
11
|
+
*/
|
|
12
|
+
/** A file this size is shown by its head, because nothing reads a megabyte in a browser panel. */
|
|
13
|
+
export declare const MAX_VIEW_BYTES: number;
|
|
14
|
+
/** Entries per directory. A folder with ten thousand files is not a tree to browse. */
|
|
15
|
+
export declare const MAX_ENTRIES = 500;
|
|
16
|
+
export interface TreeEntry {
|
|
17
|
+
name: string;
|
|
18
|
+
/** Relative to the workspace root, always with forward slashes. */
|
|
19
|
+
path: string;
|
|
20
|
+
dir: boolean;
|
|
21
|
+
size: number;
|
|
22
|
+
mtimeMs: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The resolved absolute path, or null when it would leave the workspace.
|
|
26
|
+
*
|
|
27
|
+
* Symlinks are resolved first, so a link inside the workspace pointing at /etc is caught. A path
|
|
28
|
+
* that does not exist yet cannot be realpath'd, so the parent is resolved instead and the last
|
|
29
|
+
* segment appended — which is what a browser asking for a file it saw a moment ago needs.
|
|
30
|
+
*/
|
|
31
|
+
export declare function resolveInside(root: string, rel: string): Promise<string | null>;
|
|
32
|
+
/** One directory, folders first then files, both alphabetical. */
|
|
33
|
+
export declare function listDir(root: string, rel?: string): Promise<TreeEntry[] | null>;
|
|
34
|
+
export interface FileView {
|
|
35
|
+
path: string;
|
|
36
|
+
size: number;
|
|
37
|
+
text: string | null;
|
|
38
|
+
truncated: boolean;
|
|
39
|
+
binary: boolean;
|
|
40
|
+
}
|
|
41
|
+
/** One file, as text where that means anything. */
|
|
42
|
+
export declare function viewFile(root: string, rel: string): Promise<FileView | null>;
|
|
43
|
+
//# sourceMappingURL=files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../src/web/files.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAYH,kGAAkG;AAClG,eAAO,MAAM,cAAc,QAAa,CAAC;AACzC,uFAAuF;AACvF,eAAO,MAAM,WAAW,MAAM,CAAC;AAE/B,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmBrF;AAED,kEAAkE;AAClE,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAK,GAAG,OAAO,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,CAyBjF;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;CACjB;AAWD,mDAAmD;AACnD,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAuBlF"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading the workspace: a tree to browse, and a file to look at.
|
|
3
|
+
*
|
|
4
|
+
* Every path arrives from a URL, so every path is resolved and checked against the workspace root
|
|
5
|
+
* before anything is opened. That check is the whole of this module's security, and it is done once
|
|
6
|
+
* in one function rather than at each call site — `..` is the obvious attack and a symlink pointing
|
|
7
|
+
* out of the tree is the one that gets forgotten, so the resolved real path is what gets compared.
|
|
8
|
+
*
|
|
9
|
+
* Read-only. Editing from the browser is a different feature with a different risk, and mixing the
|
|
10
|
+
* two would mean a traversal bug wrote instead of leaked.
|
|
11
|
+
*/
|
|
12
|
+
import { readdir, readFile, stat, realpath } from 'fs/promises';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
/** Never listed: generated, enormous, or not the user's work. */
|
|
15
|
+
const HIDE = new Set([
|
|
16
|
+
'node_modules', '.git', '.svn', '.hg', 'dist', 'build', 'out', 'target',
|
|
17
|
+
'.next', '.nuxt', '.venv', 'venv', '__pycache__', '.cache', '.turbo',
|
|
18
|
+
'coverage', '.pytest_cache', '.mypy_cache', '.gradle', '.idea', '.DS_Store',
|
|
19
|
+
]);
|
|
20
|
+
/** A file this size is shown by its head, because nothing reads a megabyte in a browser panel. */
|
|
21
|
+
export const MAX_VIEW_BYTES = 400 * 1024;
|
|
22
|
+
/** Entries per directory. A folder with ten thousand files is not a tree to browse. */
|
|
23
|
+
export const MAX_ENTRIES = 500;
|
|
24
|
+
/**
|
|
25
|
+
* The resolved absolute path, or null when it would leave the workspace.
|
|
26
|
+
*
|
|
27
|
+
* Symlinks are resolved first, so a link inside the workspace pointing at /etc is caught. A path
|
|
28
|
+
* that does not exist yet cannot be realpath'd, so the parent is resolved instead and the last
|
|
29
|
+
* segment appended — which is what a browser asking for a file it saw a moment ago needs.
|
|
30
|
+
*/
|
|
31
|
+
export async function resolveInside(root, rel) {
|
|
32
|
+
// A NUL in a path is a way to truncate it inside a syscall, so it never gets that far.
|
|
33
|
+
if (rel.includes(String.fromCharCode(0)))
|
|
34
|
+
return null;
|
|
35
|
+
const rootReal = await realpath(root).catch(() => path.resolve(root));
|
|
36
|
+
const wanted = path.resolve(rootReal, rel);
|
|
37
|
+
const check = (candidate) => {
|
|
38
|
+
const relative = path.relative(rootReal, candidate);
|
|
39
|
+
// Empty means the root itself, which is allowed. Anything starting with `..`, or absolute,
|
|
40
|
+
// is outside.
|
|
41
|
+
if (relative !== '' && (relative.startsWith('..') || path.isAbsolute(relative)))
|
|
42
|
+
return null;
|
|
43
|
+
return candidate;
|
|
44
|
+
};
|
|
45
|
+
const real = await realpath(wanted).catch(() => null);
|
|
46
|
+
if (real)
|
|
47
|
+
return check(real);
|
|
48
|
+
const parentReal = await realpath(path.dirname(wanted)).catch(() => null);
|
|
49
|
+
if (!parentReal)
|
|
50
|
+
return null;
|
|
51
|
+
return check(path.join(parentReal, path.basename(wanted)));
|
|
52
|
+
}
|
|
53
|
+
/** One directory, folders first then files, both alphabetical. */
|
|
54
|
+
export async function listDir(root, rel = '') {
|
|
55
|
+
const dir = await resolveInside(root, rel);
|
|
56
|
+
if (!dir)
|
|
57
|
+
return null;
|
|
58
|
+
let entries;
|
|
59
|
+
try {
|
|
60
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const out = [];
|
|
66
|
+
for (const entry of entries) {
|
|
67
|
+
if (out.length >= MAX_ENTRIES)
|
|
68
|
+
break;
|
|
69
|
+
if (HIDE.has(entry.name))
|
|
70
|
+
continue;
|
|
71
|
+
const full = path.join(dir, entry.name);
|
|
72
|
+
const childRel = path.posix.join(rel.split(path.sep).join('/'), entry.name);
|
|
73
|
+
try {
|
|
74
|
+
const info = await stat(full);
|
|
75
|
+
out.push({
|
|
76
|
+
name: entry.name,
|
|
77
|
+
path: childRel,
|
|
78
|
+
dir: info.isDirectory(),
|
|
79
|
+
size: info.isDirectory() ? 0 : info.size,
|
|
80
|
+
mtimeMs: info.mtimeMs,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch { /* vanished, or a broken symlink */ }
|
|
84
|
+
}
|
|
85
|
+
return out.sort((a, b) => a.dir === b.dir ? a.name.localeCompare(b.name) : (a.dir ? -1 : 1));
|
|
86
|
+
}
|
|
87
|
+
const BINARY_EXT = new Set([
|
|
88
|
+
'.png', '.jpg', '.jpeg', '.gif', '.webp', '.avif', '.ico', '.bmp', '.pdf',
|
|
89
|
+
'.zip', '.gz', '.tgz', '.bz2', '.xz', '.7z', '.rar', '.jar', '.wasm',
|
|
90
|
+
'.mp3', '.mp4', '.mov', '.avi', '.mkv', '.wav', '.flac', '.ogg',
|
|
91
|
+
'.woff', '.woff2', '.ttf', '.otf', '.so', '.dylib', '.dll', '.exe', '.bin',
|
|
92
|
+
'.db', '.sqlite', '.sqlite3', '.pyc', '.class', '.node',
|
|
93
|
+
]);
|
|
94
|
+
const NUL = String.fromCharCode(0);
|
|
95
|
+
/** One file, as text where that means anything. */
|
|
96
|
+
export async function viewFile(root, rel) {
|
|
97
|
+
const full = await resolveInside(root, rel);
|
|
98
|
+
if (!full)
|
|
99
|
+
return null;
|
|
100
|
+
let info;
|
|
101
|
+
try {
|
|
102
|
+
info = await stat(full);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
if (!info.isFile())
|
|
108
|
+
return null;
|
|
109
|
+
if (BINARY_EXT.has(path.extname(full).toLowerCase())) {
|
|
110
|
+
return { path: rel, size: info.size, text: null, truncated: false, binary: true };
|
|
111
|
+
}
|
|
112
|
+
let text;
|
|
113
|
+
try {
|
|
114
|
+
text = await readFile(full, 'utf8');
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
if (text.slice(0, 4_000).includes(NUL)) {
|
|
120
|
+
return { path: rel, size: info.size, text: null, truncated: false, binary: true };
|
|
121
|
+
}
|
|
122
|
+
const truncated = text.length > MAX_VIEW_BYTES;
|
|
123
|
+
return {
|
|
124
|
+
path: rel,
|
|
125
|
+
size: info.size,
|
|
126
|
+
text: truncated ? text.slice(0, MAX_VIEW_BYTES) : text,
|
|
127
|
+
truncated,
|
|
128
|
+
binary: false,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/web/files.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sessions already on disk, read back as something a browser can show.
|
|
3
|
+
*
|
|
4
|
+
* Every CLI run writes a JSONL transcript into `.koneck/sessions`, and until now the web side
|
|
5
|
+
* listed their titles and could do nothing with them. That is the larger half of what a harness is
|
|
6
|
+
* for: most of the value is in looking at a run that has already happened — what it did, in what
|
|
7
|
+
* order, and where the time went — and none of that needs an agent to be running.
|
|
8
|
+
*
|
|
9
|
+
* The file is the record, so this only reads. A past session cannot be resumed from the browser
|
|
10
|
+
* because resuming is what the CLI does, and pretending otherwise would produce a second live
|
|
11
|
+
* session pointed at the same transcript.
|
|
12
|
+
*/
|
|
13
|
+
import { type TrajectoryEvent, type TrajectorySummary } from '../trajectory.js';
|
|
14
|
+
export interface PastSessionMeta {
|
|
15
|
+
file: string;
|
|
16
|
+
id: string;
|
|
17
|
+
task: string;
|
|
18
|
+
model: string;
|
|
19
|
+
provider: string;
|
|
20
|
+
turns: number;
|
|
21
|
+
totalTokens: number;
|
|
22
|
+
at: number;
|
|
23
|
+
bytes: number;
|
|
24
|
+
}
|
|
25
|
+
/** One entry in a rendered transcript. */
|
|
26
|
+
export type PastEntry = {
|
|
27
|
+
kind: 'user';
|
|
28
|
+
text: string;
|
|
29
|
+
} | {
|
|
30
|
+
kind: 'assistant';
|
|
31
|
+
text: string;
|
|
32
|
+
} | {
|
|
33
|
+
kind: 'tool';
|
|
34
|
+
name: string;
|
|
35
|
+
args: string;
|
|
36
|
+
result: string;
|
|
37
|
+
ok: boolean;
|
|
38
|
+
} | {
|
|
39
|
+
kind: 'system';
|
|
40
|
+
text: string;
|
|
41
|
+
};
|
|
42
|
+
export interface PastSession {
|
|
43
|
+
meta: PastSessionMeta;
|
|
44
|
+
entries: PastEntry[];
|
|
45
|
+
trajectory: TrajectoryEvent[];
|
|
46
|
+
summary: TrajectorySummary | null;
|
|
47
|
+
/** True when the file held more than was read, so the view can say so. */
|
|
48
|
+
truncated: boolean;
|
|
49
|
+
}
|
|
50
|
+
/** A transcript longer than this is read to here and the rest reported as omitted. */
|
|
51
|
+
export declare const MAX_ENTRIES = 600;
|
|
52
|
+
/** Only the first line of each file is read: it holds the metadata. */
|
|
53
|
+
export declare function listPast(cwd: string, limit?: number): Promise<PastSessionMeta[]>;
|
|
54
|
+
/**
|
|
55
|
+
* One session, read into entries.
|
|
56
|
+
*
|
|
57
|
+
* The file is a sequence of records; older KONECK versions wrote slightly different shapes, so this
|
|
58
|
+
* accepts what it recognises and ignores the rest rather than refusing to open an old transcript.
|
|
59
|
+
*/
|
|
60
|
+
export declare function readPast(cwd: string, file: string): Promise<PastSession | null>;
|
|
61
|
+
//# sourceMappingURL=history.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../src/web/history.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,EAAa,KAAK,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE3F,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf;AAED,0CAA0C;AAC1C,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,OAAO,CAAA;CAAE,GACzE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErC,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAClC,0EAA0E;IAC1E,SAAS,EAAE,OAAO,CAAC;CACpB;AAID,sFAAsF;AACtF,eAAO,MAAM,WAAW,MAAM,CAAC;AAE/B,uEAAuE;AACvE,wBAAsB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAkClF;AAmBD;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAgGrF"}
|