@proagentstore/cli 0.4.76 → 0.4.77
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.
|
@@ -159,8 +159,15 @@ export class InspectError extends Error {
|
|
|
159
159
|
}
|
|
160
160
|
const DEFAULT_MAX_FILE_BYTES = 64 * 1024;
|
|
161
161
|
const HARD_MAX_FILE_BYTES = 128 * 1024;
|
|
162
|
-
/**
|
|
163
|
-
|
|
162
|
+
/**
|
|
163
|
+
* Read a text file inside the repo. Rejects traversal and binary files; returns at most `maxBytes`.
|
|
164
|
+
*
|
|
165
|
+
* Ranged from #954: `startLine`/`startColumn` say where the returned bytes begin, so no part of a file
|
|
166
|
+
* is out of reach — before it, a read always began at byte 0 and nothing past the cap could be read.
|
|
167
|
+
* `firstLine`/`firstColumn`/`totalLines` are always present, which is how the cloud tells a runner that
|
|
168
|
+
* honours the range from an older one that silently started at the top.
|
|
169
|
+
*/
|
|
170
|
+
export function readRepoFile(workDir, relPath, maxBytes, from = {}) {
|
|
164
171
|
const abs = resolveInside(workDir, relPath, { checkSymlink: true });
|
|
165
172
|
const st = statSync(abs);
|
|
166
173
|
if (!st.isFile())
|
|
@@ -171,8 +178,31 @@ export function readRepoFile(workDir, relPath, maxBytes) {
|
|
|
171
178
|
const head = buf.subarray(0, 8192);
|
|
172
179
|
if (head.includes(0))
|
|
173
180
|
return { path: relPath, size: st.size, truncated: false, binary: true };
|
|
174
|
-
|
|
175
|
-
|
|
181
|
+
let newlines = 0;
|
|
182
|
+
for (let i = buf.indexOf(10); i !== -1; i = buf.indexOf(10, i + 1))
|
|
183
|
+
newlines++;
|
|
184
|
+
const totalLines = newlines + (buf.length > 0 && buf[buf.length - 1] !== 10 ? 1 : 0);
|
|
185
|
+
const firstLine = Math.max(1, Math.trunc(Number(from.startLine) || 1));
|
|
186
|
+
const firstColumn = Math.max(1, Math.trunc(Number(from.startColumn) || 1));
|
|
187
|
+
const ranged = { firstLine, firstColumn, totalLines };
|
|
188
|
+
if (firstLine > totalLines)
|
|
189
|
+
return { path: relPath, size: st.size, truncated: false, content: "", ...ranged };
|
|
190
|
+
// The byte where line `firstLine` begins…
|
|
191
|
+
let start = 0;
|
|
192
|
+
for (let line = 1; line < firstLine; line++)
|
|
193
|
+
start = buf.indexOf(10, start) + 1;
|
|
194
|
+
// …and, for a column past the first, where that CHARACTER begins — counted on the decoded line, so
|
|
195
|
+
// a multi-byte character is never split.
|
|
196
|
+
if (firstColumn > 1) {
|
|
197
|
+
const eol = buf.indexOf(10, start);
|
|
198
|
+
const lineText = buf.subarray(start, eol === -1 ? buf.length : eol).toString("utf-8");
|
|
199
|
+
start += Buffer.byteLength(lineText.slice(0, firstColumn - 1), "utf-8");
|
|
200
|
+
}
|
|
201
|
+
let end = Math.min(buf.length, start + cap);
|
|
202
|
+
// Never end inside a multi-byte character: it would decode as U+FFFD at the cut.
|
|
203
|
+
while (end < buf.length && end > start && (buf[end] & 0xc0) === 0x80)
|
|
204
|
+
end--;
|
|
205
|
+
return { path: relPath, size: st.size, truncated: end < buf.length, content: buf.subarray(start, end).toString("utf-8"), ...ranged };
|
|
176
206
|
}
|
|
177
207
|
/** Run a whitelisted read-only git command in the repo. Never uses a shell. */
|
|
178
208
|
export function runRepoGit(workDir, cmd, opts = {}) {
|
|
@@ -53,7 +53,7 @@ export class CodingRuntime {
|
|
|
53
53
|
}
|
|
54
54
|
/** Read one file inside the session's repo (traversal-guarded, size-capped). */
|
|
55
55
|
readFile(input) {
|
|
56
|
-
return readRepoFile(this.resolveWorkDir(input), input.path, input.maxBytes);
|
|
56
|
+
return readRepoFile(this.resolveWorkDir(input), input.path, input.maxBytes, { startLine: input.startLine, startColumn: input.startColumn });
|
|
57
57
|
}
|
|
58
58
|
/** Run a whitelisted read-only git command in the session's repo. */
|
|
59
59
|
git(input) {
|