@ai-sdk/harness-cline 1.0.7 → 1.0.9
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/CHANGELOG.md +21 -0
- package/dist/index.js +162 -28
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/cline-remote-ops.ts +204 -26
- package/src/cline-session.ts +8 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @ai-sdk/harness-cline
|
|
2
2
|
|
|
3
|
+
## 1.0.9
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- @ai-sdk/harness@1.0.82
|
|
8
|
+
|
|
9
|
+
## 1.0.8
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- cb78370: fix(harness-cline): fix bug with reading and writing files
|
|
14
|
+
- Updated dependencies [7f50d28]
|
|
15
|
+
- @ai-sdk/harness@1.0.81
|
|
16
|
+
|
|
17
|
+
## 1.0.8
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- 016b944: fix(harness-cline): fix bug with reading and writing files
|
|
22
|
+
- @ai-sdk/harness@1.0.80
|
|
23
|
+
|
|
3
24
|
## 1.0.7
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
package/dist/index.js
CHANGED
|
@@ -229,23 +229,124 @@ import path2 from "path";
|
|
|
229
229
|
import { shellQuote } from "@ai-sdk/harness/utils";
|
|
230
230
|
function createClineRemoteOps(options) {
|
|
231
231
|
const { sandbox, workDir } = options;
|
|
232
|
-
const
|
|
233
|
-
|
|
232
|
+
const normalizedWorkDir = path2.posix.normalize(workDir);
|
|
233
|
+
const readableRoots = options.readableRoots?.map((root) => path2.posix.normalize(root.sandboxDir)) ?? [];
|
|
234
|
+
const isInsidePath = ({
|
|
235
|
+
parent,
|
|
236
|
+
candidate
|
|
237
|
+
}) => {
|
|
238
|
+
const relative = path2.posix.relative(parent, candidate);
|
|
239
|
+
return relative === "" || relative !== ".." && !relative.startsWith("../") && !path2.posix.isAbsolute(relative);
|
|
240
|
+
};
|
|
241
|
+
const assertWorkspacePath = (inputPath) => {
|
|
242
|
+
const normalized = path2.posix.normalize(inputPath);
|
|
243
|
+
if (!isInsidePath({ parent: normalizedWorkDir, candidate: normalized })) {
|
|
244
|
+
throw new Error(`Cline path escapes the workspace: ${inputPath}`);
|
|
245
|
+
}
|
|
246
|
+
return normalized;
|
|
247
|
+
};
|
|
248
|
+
const assertReadablePath = (inputPath) => {
|
|
249
|
+
const normalized = path2.posix.normalize(inputPath);
|
|
250
|
+
if (!isInsidePath({ parent: normalizedWorkDir, candidate: normalized }) && !readableRoots.some(
|
|
251
|
+
(parent) => isInsidePath({ parent, candidate: normalized })
|
|
252
|
+
)) {
|
|
253
|
+
throw new Error(`Cline path escapes the readable roots: ${inputPath}`);
|
|
254
|
+
}
|
|
234
255
|
return normalized;
|
|
235
256
|
};
|
|
236
|
-
const
|
|
257
|
+
const resolvePath = (inputPath) => {
|
|
258
|
+
const resolved = path2.posix.isAbsolute(inputPath) ? path2.posix.normalize(inputPath) : path2.posix.normalize(path2.posix.join(normalizedWorkDir, inputPath));
|
|
259
|
+
return assertWorkspacePath(resolved);
|
|
260
|
+
};
|
|
261
|
+
const resolveReadablePath = (inputPath) => {
|
|
262
|
+
if (!path2.posix.isAbsolute(inputPath)) {
|
|
263
|
+
return resolvePath(inputPath);
|
|
264
|
+
}
|
|
265
|
+
return assertReadablePath(inputPath);
|
|
266
|
+
};
|
|
267
|
+
const runShell = async ({
|
|
268
|
+
command,
|
|
269
|
+
signal
|
|
270
|
+
}) => {
|
|
237
271
|
const result = await sandbox.run({
|
|
238
272
|
command,
|
|
239
|
-
workingDirectory:
|
|
240
|
-
...
|
|
273
|
+
workingDirectory: normalizedWorkDir,
|
|
274
|
+
...signal ? { abortSignal: signal } : {}
|
|
241
275
|
});
|
|
242
276
|
return {
|
|
243
277
|
exitCode: result.exitCode,
|
|
244
278
|
output: `${result.stdout}${result.stderr}`
|
|
245
279
|
};
|
|
246
280
|
};
|
|
281
|
+
const lastOutputLine = (output) => output.trim().split("\n").filter(Boolean).at(-1);
|
|
282
|
+
const resolveExistingSandboxPath = async ({
|
|
283
|
+
remotePath,
|
|
284
|
+
inputPath,
|
|
285
|
+
missingMessage
|
|
286
|
+
}) => {
|
|
287
|
+
const result = await runShell({
|
|
288
|
+
command: [
|
|
289
|
+
`target=${shellQuote(remotePath)}`,
|
|
290
|
+
'if [ ! -e "$target" ]; then echo "__CLINE_REALPATH_NOT_FOUND__"; exit 2; fi',
|
|
291
|
+
'resolved=$(realpath "$target" 2>/dev/null) || { echo "__CLINE_REALPATH_FAILED__"; exit 3; }',
|
|
292
|
+
`printf '%s\\n' "$resolved"`
|
|
293
|
+
].join("; ")
|
|
294
|
+
});
|
|
295
|
+
if (result.output.includes("__CLINE_REALPATH_NOT_FOUND__")) {
|
|
296
|
+
throw new Error(missingMessage ?? `Path not found: ${inputPath}`);
|
|
297
|
+
}
|
|
298
|
+
if (result.output.includes("__CLINE_REALPATH_FAILED__") || result.exitCode !== 0) {
|
|
299
|
+
throw new Error(`Unable to resolve path: ${inputPath}`);
|
|
300
|
+
}
|
|
301
|
+
const resolvedPath = lastOutputLine(result.output);
|
|
302
|
+
if (!resolvedPath) {
|
|
303
|
+
throw new Error(`Unable to resolve path: ${inputPath}`);
|
|
304
|
+
}
|
|
305
|
+
return resolvedPath;
|
|
306
|
+
};
|
|
307
|
+
const resolveReadableSandboxPath = async ({
|
|
308
|
+
remotePath,
|
|
309
|
+
inputPath,
|
|
310
|
+
missingMessage
|
|
311
|
+
}) => assertReadablePath(
|
|
312
|
+
await resolveExistingSandboxPath({
|
|
313
|
+
remotePath,
|
|
314
|
+
inputPath,
|
|
315
|
+
...missingMessage ? { missingMessage } : {}
|
|
316
|
+
})
|
|
317
|
+
);
|
|
318
|
+
const resolveWritableSandboxPath = async ({
|
|
319
|
+
remotePath,
|
|
320
|
+
inputPath
|
|
321
|
+
}) => {
|
|
322
|
+
const result = await runShell({
|
|
323
|
+
command: [
|
|
324
|
+
`target=${shellQuote(remotePath)}`,
|
|
325
|
+
`if [ -e "$target" ] || [ -L "$target" ]; then resolved=$(realpath "$target" 2>/dev/null) || { echo "__CLINE_REALPATH_FAILED__"; exit 3; }; printf '%s\\n' "$resolved"; exit 0; fi`,
|
|
326
|
+
'dir=$(dirname "$target")',
|
|
327
|
+
'base=$(basename "$target")',
|
|
328
|
+
'missing="$base"',
|
|
329
|
+
'while [ ! -e "$dir" ] && [ ! -L "$dir" ]; do parent=$(dirname "$dir"); if [ "$parent" = "$dir" ]; then echo "__CLINE_REALPATH_NOT_FOUND__"; exit 2; fi; missing="$(basename "$dir")/$missing"; dir="$parent"; done',
|
|
330
|
+
'resolved_dir=$(realpath "$dir" 2>/dev/null) || { echo "__CLINE_REALPATH_FAILED__"; exit 3; }',
|
|
331
|
+
`printf '%s/%s\\n' "$resolved_dir" "$missing"`
|
|
332
|
+
].join("; ")
|
|
333
|
+
});
|
|
334
|
+
if (result.output.includes("__CLINE_REALPATH_NOT_FOUND__") || result.output.includes("__CLINE_REALPATH_FAILED__") || result.exitCode !== 0) {
|
|
335
|
+
throw new Error(`Unable to resolve path: ${inputPath}`);
|
|
336
|
+
}
|
|
337
|
+
const resolvedPath = lastOutputLine(result.output);
|
|
338
|
+
if (!resolvedPath) {
|
|
339
|
+
throw new Error(`Unable to resolve path: ${inputPath}`);
|
|
340
|
+
}
|
|
341
|
+
return assertWorkspacePath(resolvedPath);
|
|
342
|
+
};
|
|
247
343
|
const readFile = async (inputPath) => {
|
|
248
|
-
const
|
|
344
|
+
const remotePath = resolveReadablePath(inputPath);
|
|
345
|
+
const resolved = await resolveReadableSandboxPath({
|
|
346
|
+
remotePath,
|
|
347
|
+
inputPath,
|
|
348
|
+
missingMessage: `File not found: ${inputPath}`
|
|
349
|
+
});
|
|
249
350
|
const content = await sandbox.readTextFile({ path: resolved });
|
|
250
351
|
if (content == null) {
|
|
251
352
|
throw new Error(`File not found: ${inputPath}`);
|
|
@@ -253,11 +354,26 @@ function createClineRemoteOps(options) {
|
|
|
253
354
|
return content;
|
|
254
355
|
};
|
|
255
356
|
const writeFile = async (inputPath, content) => {
|
|
256
|
-
const
|
|
357
|
+
const remotePath = resolvePath(inputPath);
|
|
358
|
+
const resolved = await resolveWritableSandboxPath({
|
|
359
|
+
remotePath,
|
|
360
|
+
inputPath
|
|
361
|
+
});
|
|
257
362
|
await sandbox.writeTextFile({ path: resolved, content });
|
|
258
363
|
};
|
|
259
364
|
const editFile = async (inputPath, oldText, newText) => {
|
|
260
|
-
const
|
|
365
|
+
const remotePath = resolvePath(inputPath);
|
|
366
|
+
const resolved = assertWorkspacePath(
|
|
367
|
+
await resolveExistingSandboxPath({
|
|
368
|
+
remotePath,
|
|
369
|
+
inputPath,
|
|
370
|
+
missingMessage: `File not found: ${inputPath}`
|
|
371
|
+
})
|
|
372
|
+
);
|
|
373
|
+
const current = await sandbox.readTextFile({ path: resolved });
|
|
374
|
+
if (current == null) {
|
|
375
|
+
throw new Error(`File not found: ${inputPath}`);
|
|
376
|
+
}
|
|
261
377
|
const index = current.indexOf(oldText);
|
|
262
378
|
if (index === -1) {
|
|
263
379
|
throw new Error(`Text to replace was not found in ${inputPath}`);
|
|
@@ -279,7 +395,10 @@ function createClineRemoteOps(options) {
|
|
|
279
395
|
const onAbort = () => controller.abort();
|
|
280
396
|
forwardedSignal?.addEventListener("abort", onAbort, { once: true });
|
|
281
397
|
try {
|
|
282
|
-
const result = await runShell(
|
|
398
|
+
const result = await runShell({
|
|
399
|
+
command,
|
|
400
|
+
signal: controller.signal
|
|
401
|
+
});
|
|
283
402
|
return { output: result.output, exitCode: result.exitCode };
|
|
284
403
|
} finally {
|
|
285
404
|
forwardedSignal?.removeEventListener("abort", onAbort);
|
|
@@ -289,9 +408,14 @@ function createClineRemoteOps(options) {
|
|
|
289
408
|
}
|
|
290
409
|
},
|
|
291
410
|
async grep(pattern, input = {}) {
|
|
292
|
-
const
|
|
411
|
+
const inputPath = input.path ?? ".";
|
|
412
|
+
const remotePath = resolveReadablePath(inputPath);
|
|
413
|
+
const target = await resolveReadableSandboxPath({
|
|
414
|
+
remotePath,
|
|
415
|
+
inputPath
|
|
416
|
+
});
|
|
293
417
|
const flags = [
|
|
294
|
-
"-
|
|
418
|
+
"-r",
|
|
295
419
|
"-n",
|
|
296
420
|
"--binary-files=without-match",
|
|
297
421
|
...input.ignoreCase ? ["-i"] : [],
|
|
@@ -300,8 +424,8 @@ function createClineRemoteOps(options) {
|
|
|
300
424
|
...input.glob ? ["--include", input.glob] : []
|
|
301
425
|
];
|
|
302
426
|
const limit = Math.max(1, input.limit ?? 100);
|
|
303
|
-
const result = await runShell(
|
|
304
|
-
[
|
|
427
|
+
const result = await runShell({
|
|
428
|
+
command: [
|
|
305
429
|
`if [ ! -e ${shellQuote(
|
|
306
430
|
target
|
|
307
431
|
)} ]; then echo "__CLINE_GREP_NOT_FOUND__"; exit 2; fi`,
|
|
@@ -309,7 +433,7 @@ function createClineRemoteOps(options) {
|
|
|
309
433
|
pattern
|
|
310
434
|
)} ${shellQuote(target)} 2>/dev/null | head -n ${limit}`
|
|
311
435
|
].join("; ")
|
|
312
|
-
);
|
|
436
|
+
});
|
|
313
437
|
const output = result.output.trim();
|
|
314
438
|
if (output.includes("__CLINE_GREP_NOT_FOUND__")) {
|
|
315
439
|
throw new Error(`Path not found: ${input.path ?? "."}`);
|
|
@@ -317,9 +441,13 @@ function createClineRemoteOps(options) {
|
|
|
317
441
|
return output || "No matches found";
|
|
318
442
|
},
|
|
319
443
|
async glob(pattern, inputPath = ".", limit = 1e3) {
|
|
320
|
-
const
|
|
321
|
-
const
|
|
322
|
-
|
|
444
|
+
const remotePath = resolveReadablePath(inputPath);
|
|
445
|
+
const target = await resolveReadableSandboxPath({
|
|
446
|
+
remotePath,
|
|
447
|
+
inputPath
|
|
448
|
+
});
|
|
449
|
+
const result = await runShell({
|
|
450
|
+
command: [
|
|
323
451
|
`if [ ! -e ${shellQuote(
|
|
324
452
|
target
|
|
325
453
|
)} ]; then echo "__CLINE_FIND_NOT_FOUND__"; exit 2; fi`,
|
|
@@ -327,7 +455,7 @@ function createClineRemoteOps(options) {
|
|
|
327
455
|
target
|
|
328
456
|
)} -type f -print; else printf '%s\\n' ${shellQuote(target)}; fi`
|
|
329
457
|
].join("; ")
|
|
330
|
-
);
|
|
458
|
+
});
|
|
331
459
|
const output = result.output.trim();
|
|
332
460
|
if (output.includes("__CLINE_FIND_NOT_FOUND__")) {
|
|
333
461
|
throw new Error(`Path not found: ${inputPath}`);
|
|
@@ -344,9 +472,13 @@ function createClineRemoteOps(options) {
|
|
|
344
472
|
).slice(0, limit);
|
|
345
473
|
},
|
|
346
474
|
async ls(inputPath = ".", limit = 500) {
|
|
347
|
-
const
|
|
348
|
-
const
|
|
349
|
-
|
|
475
|
+
const remotePath = resolveReadablePath(inputPath);
|
|
476
|
+
const target = await resolveReadableSandboxPath({
|
|
477
|
+
remotePath,
|
|
478
|
+
inputPath
|
|
479
|
+
});
|
|
480
|
+
const result = await runShell({
|
|
481
|
+
command: [
|
|
350
482
|
`if [ ! -e ${shellQuote(
|
|
351
483
|
target
|
|
352
484
|
)} ]; then echo "__CLINE_LS_NOT_FOUND__"; exit 2; fi`,
|
|
@@ -356,7 +488,7 @@ function createClineRemoteOps(options) {
|
|
|
356
488
|
`cd ${shellQuote(target)}`,
|
|
357
489
|
"ls -1Ap"
|
|
358
490
|
].join("; ")
|
|
359
|
-
);
|
|
491
|
+
});
|
|
360
492
|
const output = result.output.trim();
|
|
361
493
|
if (output.includes("__CLINE_LS_NOT_FOUND__")) {
|
|
362
494
|
throw new Error(`Path not found: ${inputPath}`);
|
|
@@ -1056,13 +1188,10 @@ async function createClineSession(input) {
|
|
|
1056
1188
|
const activeBuiltinNames = resolveActiveClineBuiltinNames(
|
|
1057
1189
|
input.builtinToolFiltering
|
|
1058
1190
|
);
|
|
1059
|
-
const ops = createClineRemoteOps({
|
|
1060
|
-
sandbox: toolSafeSandboxSession,
|
|
1061
|
-
workDir: input.sessionWorkDir
|
|
1062
|
-
});
|
|
1063
1191
|
let skillsSection;
|
|
1192
|
+
let skillRootDir;
|
|
1064
1193
|
if (input.skills.length > 0) {
|
|
1065
|
-
|
|
1194
|
+
skillRootDir = await writeClineSkills({
|
|
1066
1195
|
sandbox: toolSafeSandboxSession,
|
|
1067
1196
|
sandboxHomeDir,
|
|
1068
1197
|
skills: input.skills,
|
|
@@ -1072,6 +1201,11 @@ async function createClineSession(input) {
|
|
|
1072
1201
|
skillsSection = renderSkillsPromptSection(input.skills, skillRootDir);
|
|
1073
1202
|
}
|
|
1074
1203
|
}
|
|
1204
|
+
const ops = createClineRemoteOps({
|
|
1205
|
+
sandbox: toolSafeSandboxSession,
|
|
1206
|
+
workDir: input.sessionWorkDir,
|
|
1207
|
+
...skillRootDir ? { readableRoots: [{ sandboxDir: skillRootDir }] } : {}
|
|
1208
|
+
});
|
|
1075
1209
|
const baseSystemPrompt = buildSystemPrompt({
|
|
1076
1210
|
sessionWorkDir: input.sessionWorkDir,
|
|
1077
1211
|
sandboxDescription: toolSafeSandboxSession.description,
|
|
@@ -1565,7 +1699,7 @@ ${rebuildInput.instructions}` : baseSystemPrompt,
|
|
|
1565
1699
|
}
|
|
1566
1700
|
|
|
1567
1701
|
// src/version.ts
|
|
1568
|
-
var VERSION = true ? "1.0.
|
|
1702
|
+
var VERSION = true ? "1.0.9" : "0.0.0-test";
|
|
1569
1703
|
|
|
1570
1704
|
// src/cline-harness.ts
|
|
1571
1705
|
var CLINE_CLIENT_APP = `ai-sdk/harness-cline/${VERSION}`;
|