@ai-sdk/harness-claude-code 1.0.174 → 1.0.176
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 +18 -0
- package/dist/bridge/index.mjs +106 -6
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.js +10 -3
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/bridge/index.ts +88 -8
- package/src/bridge/permission-paths.ts +108 -0
- package/src/claude-code-bridge-protocol.ts +1 -0
- package/src/claude-code-harness.ts +12 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @ai-sdk/harness-claude-code
|
|
2
2
|
|
|
3
|
+
## 1.0.176
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- e3f24b6: fix(harness-claude-code): require approval when accessing files outside of workDir with permission modes `allow-reads` and `allow-edits`
|
|
8
|
+
- Updated dependencies [51ca5b7]
|
|
9
|
+
- Updated dependencies [8a20b57]
|
|
10
|
+
- Updated dependencies [dc6f1c3]
|
|
11
|
+
- Updated dependencies [4ae6986]
|
|
12
|
+
- @ai-sdk/provider-utils@5.0.73
|
|
13
|
+
- @ai-sdk/harness@1.0.172
|
|
14
|
+
|
|
15
|
+
## 1.0.175
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- @ai-sdk/harness@1.0.171
|
|
20
|
+
|
|
3
21
|
## 1.0.174
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/dist/bridge/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { appendFile, mkdir, writeFile } from "node:fs/promises";
|
|
1
|
+
import { appendFile, lstat, mkdir, realpath, writeFile } from "node:fs/promises";
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { argv, env, pid, stdout } from "node:process";
|
|
@@ -7,6 +7,7 @@ import * as claudeAgentSdk from "@anthropic-ai/claude-agent-sdk";
|
|
|
7
7
|
import * as mcpServerModule from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
8
|
import { isDeepStrictEqual } from "node:util";
|
|
9
9
|
import { z } from "zod/v4";
|
|
10
|
+
import path from "node:path";
|
|
10
11
|
//#region ../harness/dist/bridge/index.js
|
|
11
12
|
const name = "AI_HarnessBridgeCapabilityUnsupportedError";
|
|
12
13
|
/**
|
|
@@ -1315,6 +1316,53 @@ function isJsonLiteral(value) {
|
|
|
1315
1316
|
return value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
|
|
1316
1317
|
}
|
|
1317
1318
|
//#endregion
|
|
1319
|
+
//#region src/bridge/permission-paths.ts
|
|
1320
|
+
async function areFileToolPathsTrusted(options) {
|
|
1321
|
+
if (options.paths.length === 0) return false;
|
|
1322
|
+
try {
|
|
1323
|
+
const roots = [await realpath(options.workDir)];
|
|
1324
|
+
if (options.access === "read") for (const root of options.readableSkillRoots) try {
|
|
1325
|
+
roots.push(await realpath(root));
|
|
1326
|
+
} catch {
|
|
1327
|
+
continue;
|
|
1328
|
+
}
|
|
1329
|
+
for (const filePath of options.paths) {
|
|
1330
|
+
if (typeof filePath !== "string" || filePath.length === 0 || filePath !== filePath.trim() || filePath === "~" || filePath.startsWith("~/")) return false;
|
|
1331
|
+
if (!path.posix.isAbsolute(filePath) && !path.posix.isAbsolute(options.cwd)) return false;
|
|
1332
|
+
const absolutePath = path.posix.isAbsolute(filePath) ? filePath : `${options.cwd}/${filePath}`;
|
|
1333
|
+
for (const candidatePath of [absolutePath, path.posix.normalize(absolutePath)]) {
|
|
1334
|
+
const candidate = await resolvePermissionPath({ filePath: candidatePath });
|
|
1335
|
+
if (!roots.some((root) => {
|
|
1336
|
+
const relative = path.posix.relative(root, candidate);
|
|
1337
|
+
return relative === "" || relative !== ".." && !relative.startsWith("../") && !path.posix.isAbsolute(relative);
|
|
1338
|
+
})) return false;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
return true;
|
|
1342
|
+
} catch {
|
|
1343
|
+
return false;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
async function resolvePermissionPath(options) {
|
|
1347
|
+
let existingPath = options.filePath;
|
|
1348
|
+
const missing = [];
|
|
1349
|
+
for (;;) {
|
|
1350
|
+
let exists = true;
|
|
1351
|
+
try {
|
|
1352
|
+
await lstat(existingPath);
|
|
1353
|
+
} catch (error) {
|
|
1354
|
+
if (error == null || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT") throw error;
|
|
1355
|
+
exists = false;
|
|
1356
|
+
}
|
|
1357
|
+
if (exists) return path.posix.join(await realpath(existingPath), ...missing);
|
|
1358
|
+
const parent = path.posix.dirname(existingPath);
|
|
1359
|
+
const segment = path.posix.basename(existingPath);
|
|
1360
|
+
if (parent === existingPath || segment === "." || segment === "..") throw new Error("Unable to resolve permission path.");
|
|
1361
|
+
missing.unshift(segment);
|
|
1362
|
+
existingPath = parent;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
//#endregion
|
|
1318
1366
|
//#region src/bridge/tool-filtering.ts
|
|
1319
1367
|
const PUBLIC_TO_NATIVE = {
|
|
1320
1368
|
read: "Read",
|
|
@@ -1503,10 +1551,14 @@ function createPermissionOptions(input) {
|
|
|
1503
1551
|
return {
|
|
1504
1552
|
...baseOptions,
|
|
1505
1553
|
canUseTool: async (toolName, toolInput, options) => {
|
|
1506
|
-
if (!needsHostApproval({
|
|
1554
|
+
if (!await needsHostApproval({
|
|
1507
1555
|
toolName,
|
|
1556
|
+
toolInput,
|
|
1508
1557
|
permissionMode,
|
|
1509
|
-
inactiveNativeTools
|
|
1558
|
+
inactiveNativeTools,
|
|
1559
|
+
cwd: input.toolCwds.get(options.toolUseID) ?? "",
|
|
1560
|
+
readableSkillRoots: input.start.readableSkillRoots ?? [],
|
|
1561
|
+
blockedPath: options.blockedPath
|
|
1510
1562
|
})) return {
|
|
1511
1563
|
behavior: "allow",
|
|
1512
1564
|
updatedInput: toolInput
|
|
@@ -1615,17 +1667,56 @@ function createGatewayModelOverrides(model) {
|
|
|
1615
1667
|
const id = model.slice(10).replace(/\[[^\]]*\]$/, "");
|
|
1616
1668
|
return id.includes(".") ? { [id.replace(/\./g, "-")]: model } : void 0;
|
|
1617
1669
|
}
|
|
1618
|
-
function needsHostApproval(input) {
|
|
1670
|
+
async function needsHostApproval(input) {
|
|
1619
1671
|
if (input.toolName.startsWith("mcp__harness-tools__")) return false;
|
|
1620
|
-
|
|
1672
|
+
if (input.inactiveNativeTools.has(input.toolName) || nativeToolRequiresApproval({
|
|
1621
1673
|
nativeName: input.toolName,
|
|
1622
1674
|
permissionMode: input.permissionMode
|
|
1675
|
+
})) return true;
|
|
1676
|
+
const args = input.toolInput != null && typeof input.toolInput === "object" ? input.toolInput : {};
|
|
1677
|
+
let paths;
|
|
1678
|
+
let access;
|
|
1679
|
+
if (input.toolName === "Glob" && (typeof args.pattern !== "string" || /\.\.|[{}()[\]]/.test(args.pattern) || args.pattern.includes("\\") || args.pattern.startsWith("/"))) return true;
|
|
1680
|
+
switch (input.toolName) {
|
|
1681
|
+
case "Read":
|
|
1682
|
+
paths = [args.file_path];
|
|
1683
|
+
access = "read";
|
|
1684
|
+
break;
|
|
1685
|
+
case "Write":
|
|
1686
|
+
case "Edit":
|
|
1687
|
+
paths = [args.file_path];
|
|
1688
|
+
access = "write";
|
|
1689
|
+
break;
|
|
1690
|
+
case "NotebookEdit":
|
|
1691
|
+
paths = [args.notebook_path];
|
|
1692
|
+
access = "write";
|
|
1693
|
+
break;
|
|
1694
|
+
case "Glob":
|
|
1695
|
+
case "Grep":
|
|
1696
|
+
paths = [args.path ?? input.cwd];
|
|
1697
|
+
access = "read";
|
|
1698
|
+
break;
|
|
1699
|
+
default:
|
|
1700
|
+
if (input.blockedPath == null) return false;
|
|
1701
|
+
paths = [];
|
|
1702
|
+
access = "read";
|
|
1703
|
+
}
|
|
1704
|
+
if (input.blockedPath != null) paths.push(input.blockedPath);
|
|
1705
|
+
if (!paths.every((value) => typeof value === "string")) return true;
|
|
1706
|
+
return !await areFileToolPathsTrusted({
|
|
1707
|
+
workDir: workdir,
|
|
1708
|
+
cwd: input.cwd,
|
|
1709
|
+
paths,
|
|
1710
|
+
access,
|
|
1711
|
+
readableSkillRoots: input.readableSkillRoots
|
|
1623
1712
|
});
|
|
1624
1713
|
}
|
|
1625
1714
|
function createApprovalPreToolUseHook(input) {
|
|
1626
1715
|
return async (hookInput, toolUseID) => {
|
|
1716
|
+
if (hookInput.hook_event_name !== "PreToolUse") return {};
|
|
1627
1717
|
const toolName = hookInput.tool_name;
|
|
1628
1718
|
if (toolUseID == null || typeof toolName !== "string" || toolName === "AskUserQuestion") return {};
|
|
1719
|
+
input.toolCwds.set(toolUseID, hookInput.cwd);
|
|
1629
1720
|
const decision = input.decisions.get(toolUseID);
|
|
1630
1721
|
if (decision != null) {
|
|
1631
1722
|
input.nativeToolCallNames.set(toolUseID, toolName);
|
|
@@ -1638,8 +1729,11 @@ function createApprovalPreToolUseHook(input) {
|
|
|
1638
1729
|
}
|
|
1639
1730
|
} };
|
|
1640
1731
|
}
|
|
1641
|
-
if (!needsHostApproval({
|
|
1732
|
+
if (!await needsHostApproval({
|
|
1642
1733
|
toolName,
|
|
1734
|
+
toolInput: hookInput.tool_input,
|
|
1735
|
+
cwd: hookInput.cwd,
|
|
1736
|
+
readableSkillRoots: input.readableSkillRoots,
|
|
1643
1737
|
permissionMode: input.permissionMode,
|
|
1644
1738
|
inactiveNativeTools: input.inactiveNativeTools
|
|
1645
1739
|
})) return {};
|
|
@@ -1652,6 +1746,8 @@ function createApprovalPreToolUseHook(input) {
|
|
|
1652
1746
|
}
|
|
1653
1747
|
function nativeToolRequiresApproval(input) {
|
|
1654
1748
|
if (input.permissionMode === "allow-all") return false;
|
|
1749
|
+
if (input.nativeName === "EnterWorktree" || input.nativeName === "ExitWorktree") return true;
|
|
1750
|
+
if (!(input.nativeName in NATIVE_TOOL_KINDS)) return true;
|
|
1655
1751
|
const kind = NATIVE_TOOL_KINDS[input.nativeName] ?? "edit";
|
|
1656
1752
|
if (input.permissionMode === "allow-edits") return kind === "bash";
|
|
1657
1753
|
return kind === "edit" || kind === "bash";
|
|
@@ -1768,7 +1864,9 @@ async function runTurn(start, turn) {
|
|
|
1768
1864
|
const inactiveNativeTools = resolveInactiveNativeTools(start.builtinToolFiltering);
|
|
1769
1865
|
const permissionMode = start.permissionMode ?? "allow-all";
|
|
1770
1866
|
const approvalDecisions = /* @__PURE__ */ new Map();
|
|
1867
|
+
const toolCwds = /* @__PURE__ */ new Map();
|
|
1771
1868
|
const permissionOptions = createPermissionOptions({
|
|
1869
|
+
toolCwds,
|
|
1772
1870
|
approvalDecisions,
|
|
1773
1871
|
start,
|
|
1774
1872
|
inactiveNativeTools,
|
|
@@ -1790,6 +1888,8 @@ async function runTurn(start, turn) {
|
|
|
1790
1888
|
const modelOverrides = createGatewayModelOverrides(start.model);
|
|
1791
1889
|
const deferredToolUseIds = /* @__PURE__ */ new Set();
|
|
1792
1890
|
const approvalPreToolUseHook = createApprovalPreToolUseHook({
|
|
1891
|
+
toolCwds,
|
|
1892
|
+
readableSkillRoots: start.readableSkillRoots ?? [],
|
|
1793
1893
|
permissionMode,
|
|
1794
1894
|
inactiveNativeTools: new Set(inactiveNativeTools),
|
|
1795
1895
|
decisions: approvalDecisions,
|