@lazyingart/agintiflow 0.20.304 → 0.20.306

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.304",
3
+ "version": "0.20.306",
4
4
  "type": "module",
5
5
  "description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
6
6
  "license": "Apache-2.0",
@@ -2370,6 +2370,35 @@ try {
2370
2370
  /outside the active task artifact scope/.test(String(outsideScopedRead.reason || "")),
2371
2371
  "task-scoped artifact tools could read an unrelated repository file"
2372
2372
  );
2373
+ const writeScopedRead = await executeWorkspaceTool(
2374
+ "read_file",
2375
+ { path: "unrelated.md" },
2376
+ {
2377
+ commandCwd: workspace,
2378
+ allowFileTools: true,
2379
+ workspaceWritePathScopeRoots: ["output/task-scope"],
2380
+ }
2381
+ );
2382
+ assert(
2383
+ writeScopedRead.content === "unrelated\n",
2384
+ "write-scoped artifact work could not read safe workspace evidence"
2385
+ );
2386
+ const outsideWriteScopedWrite = await executeWorkspaceTool(
2387
+ "write_file",
2388
+ { path: "outside-scoped-write.md", content: "blocked\n" },
2389
+ {
2390
+ commandCwd: workspace,
2391
+ allowFileTools: true,
2392
+ workspaceWritePathScopeRoots: ["output/task-scope"],
2393
+ }
2394
+ );
2395
+ assert(
2396
+ outsideWriteScopedWrite.blocked === true &&
2397
+ /outside the active task artifact scope/.test(
2398
+ String(outsideWriteScopedWrite.reason || "")
2399
+ ),
2400
+ "write-scoped artifact work could mutate an unrelated repository file"
2401
+ );
2373
2402
  const filenameSearch = await executeWorkspaceTool(
2374
2403
  "search_files",
2375
2404
  { path: ".", query: "routine_entry.py", maxResults: 5 },
@@ -5575,12 +5575,77 @@ const scopedRuntime = nextStepRuntimeConfig(
5575
5575
  }
5576
5576
  );
5577
5577
  assertStrict.equal(scopedRuntime.scopedArtifactTask, true);
5578
- assertStrict.deepEqual(scopedRuntime.workspacePathScopeRoots, ["output/wechat_worker/task"]);
5578
+ assertStrict.deepEqual(scopedRuntime.workspaceWritePathScopeRoots, ["output/wechat_worker/task"]);
5579
+ assertStrict.equal(
5580
+ scopedRuntime.workspacePathScopeRoots,
5581
+ undefined,
5582
+ "task artifact output incorrectly hid repository evidence from safe reads"
5583
+ );
5579
5584
  assertStrict.equal(
5580
5585
  scopedRuntime.repositoryGroundingRequired,
5581
5586
  undefined,
5582
5587
  "an explicit task artifact root still activated generic repository grounding"
5583
5588
  );
5589
+ const readOnlyAuditRequest =
5590
+ "Check readiness, read configs/model-policy.json, and report git status without changing source files or sending messages.";
5591
+ const readOnlyAuditPrompt = `AGINTI_EVIDENCE_SCOPE_JSON: ${JSON.stringify({
5592
+ mode: "task",
5593
+ request: readOnlyAuditRequest,
5594
+ artifact_root: "/workspace/output/webapp/agent/tasks/read-only-audit",
5595
+ })}`;
5596
+ const readOnlyAuditRuntime = nextStepRuntimeConfig(
5597
+ {
5598
+ provider: "localllm",
5599
+ goal: readOnlyAuditPrompt,
5600
+ commandCwd: "/workspace",
5601
+ },
5602
+ {
5603
+ goal: readOnlyAuditPrompt,
5604
+ commandCwd: "/workspace",
5605
+ meta: {
5606
+ goalContract: {
5607
+ revision: 1,
5608
+ currentRequest: readOnlyAuditRequest,
5609
+ },
5610
+ activeExecutionContract: {
5611
+ revision: 1,
5612
+ startedMutationRevision: 0,
5613
+ requiresWorkspaceMutation: false,
5614
+ requiresFileMutation: false,
5615
+ requiresSourceGrounding: false,
5616
+ },
5617
+ projectVerification: { mutationRevision: 0 },
5618
+ },
5619
+ }
5620
+ );
5621
+ assertStrict.equal(
5622
+ readOnlyAuditRuntime.scopedArtifactTask,
5623
+ undefined,
5624
+ "an optional host result directory incorrectly sandboxed a read-only workspace audit"
5625
+ );
5626
+ assertStrict.equal(
5627
+ readOnlyAuditRuntime.workspaceWritePathScopeRoots,
5628
+ undefined,
5629
+ "a read-only workspace audit inherited an unrelated artifact write scope"
5630
+ );
5631
+ const readOnlyAuditTools = selectProgressiveTools(allTools, {
5632
+ config: {
5633
+ provider: "localllm",
5634
+ progressiveTools: true,
5635
+ ...readOnlyAuditRuntime,
5636
+ },
5637
+ goal: readOnlyAuditPrompt,
5638
+ profile: "auto",
5639
+ messages: [{ role: "user", content: readOnlyAuditPrompt }],
5640
+ });
5641
+ assert(
5642
+ names(readOnlyAuditTools).includes("run_command"),
5643
+ "a command-evidence audit omitted run_command from the compact local surface"
5644
+ );
5645
+ assert(
5646
+ names(readOnlyAuditTools).includes("read_file"),
5647
+ "an audit with an exact workspace input omitted read_file from the compact local surface"
5648
+ );
5584
5649
  const scopedPathWriteDescriptor = {
5585
5650
  type: "function",
5586
5651
  function: {
@@ -5683,9 +5748,9 @@ assertStrict.equal(
5683
5748
  "long-context compaction discarded the host-owned artifact scope"
5684
5749
  );
5685
5750
  assertStrict.deepEqual(
5686
- longScopedRuntime.workspacePathScopeRoots,
5751
+ longScopedRuntime.workspaceWritePathScopeRoots,
5687
5752
  ["output/wechat_worker/long-scoped-task"],
5688
- "long-context artifact repair escaped its exact task root"
5753
+ "long-context artifact writes escaped their exact task root"
5689
5754
  );
5690
5755
  assertStrict.equal(
5691
5756
  longScopedRuntime.completionFreshMutationRequired,
@@ -33,6 +33,51 @@ const groupBriefingContract = deriveScsTaskContract({
33
33
  ].join(" "),
34
34
  taskProfile: "research",
35
35
  });
36
+ const scopedReadThenWriteRoot = fs.mkdtempSync(
37
+ path.join(os.tmpdir(), "aginti-scoped-read-then-write-")
38
+ );
39
+ const scopedReadThenWriteGoal = [
40
+ `AGINTI_EVIDENCE_SCOPE_JSON: ${JSON.stringify({
41
+ mode: "task",
42
+ request:
43
+ "Read AGENTS.md and configs/model-policy.json, then create provider-fallback-readiness.md in the task artifact directory.",
44
+ artifact_root: scopedReadThenWriteRoot,
45
+ })}`,
46
+ "Use the task artifact directory for the requested output.",
47
+ ].join("\n");
48
+ const scopedReadThenWriteContract = deriveScsTaskContract({
49
+ goal: scopedReadThenWriteGoal,
50
+ taskProfile: "auto",
51
+ });
52
+ assert.deepEqual(
53
+ scopedReadThenWriteContract.exactInputPaths,
54
+ ["AGENTS.md", "configs/model-policy.json"],
55
+ "explicit read-then-write artifact contract lost its exact source inputs"
56
+ );
57
+ assert.equal(
58
+ scopedReadThenWriteContract.requiresSourceGrounding,
59
+ true,
60
+ "explicit exact-file reads were not required before artifact completion"
61
+ );
62
+ fs.writeFileSync(
63
+ path.join(scopedReadThenWriteRoot, "provider-fallback-readiness.md"),
64
+ "# Provider fallback readiness\n",
65
+ "utf8"
66
+ );
67
+ const ungroundedScopedArtifact = evaluateScsSemanticContract(
68
+ scopedReadThenWriteContract,
69
+ { commandCwd: scopedReadThenWriteRoot, events: [], state: {} }
70
+ );
71
+ assert.equal(
72
+ ungroundedScopedArtifact.ok,
73
+ false,
74
+ "artifact completion passed without the explicitly requested source reads"
75
+ );
76
+ assert.deepEqual(
77
+ ungroundedScopedArtifact.missingSourceReads,
78
+ ["AGENTS.md", "configs/model-policy.json"],
79
+ "artifact completion did not report every unread exact input"
80
+ );
36
81
  assert.deepEqual(
37
82
  groupBriefingContract.requiredArtifactKinds.map((item) => item.id),
38
83
  ["format:.pdf"],
@@ -15034,8 +15034,30 @@ export function nextStepRuntimeConfig(config = {}, state = {}) {
15034
15034
  const scopedRootRelative = resolvedScopedRoot
15035
15035
  ? path.relative(commandCwd, resolvedScopedRoot)
15036
15036
  : "";
15037
- const scopedArtifactTask = Boolean(
15037
+ const exactScopedOutputs = Array.isArray(groundingTaskContract.exactOutputPaths)
15038
+ ? groundingTaskContract.exactOutputPaths
15039
+ .map((item) => String(item || "").trim())
15040
+ .filter(Boolean)
15041
+ : [];
15042
+ const exactOutputsStayInsideScopedRoot = Boolean(
15038
15043
  resolvedScopedRoot &&
15044
+ exactScopedOutputs.length > 0 &&
15045
+ exactScopedOutputs.every((item) => {
15046
+ const absoluteOutput = path.resolve(commandCwd, item);
15047
+ const relativeOutput = path.relative(resolvedScopedRoot, absoluteOutput);
15048
+ return (
15049
+ relativeOutput === "" ||
15050
+ (!relativeOutput.startsWith("..") && !path.isAbsolute(relativeOutput))
15051
+ );
15052
+ })
15053
+ );
15054
+ const scopedArtifactTask = Boolean(
15055
+ (
15056
+ groundingTaskContract.scopedArtifactDeliverable === true ||
15057
+ groundingTaskContract.scopedArtifactOperation === true ||
15058
+ exactOutputsStayInsideScopedRoot
15059
+ ) &&
15060
+ resolvedScopedRoot &&
15039
15061
  scopedRootRelative &&
15040
15062
  !scopedRootRelative.startsWith("..") &&
15041
15063
  !path.isAbsolute(scopedRootRelative)
@@ -15043,7 +15065,7 @@ export function nextStepRuntimeConfig(config = {}, state = {}) {
15043
15065
  if (scopedArtifactTask) {
15044
15066
  runtimeConfig.scopedArtifactTask = true;
15045
15067
  runtimeConfig.scopedArtifactRoot = scopedRootRelative.replace(/\\/g, "/");
15046
- runtimeConfig.workspacePathScopeRoots = [runtimeConfig.scopedArtifactRoot];
15068
+ runtimeConfig.workspaceWritePathScopeRoots = [runtimeConfig.scopedArtifactRoot];
15047
15069
  }
15048
15070
  if (
15049
15071
  (
@@ -4,7 +4,11 @@ import {
4
4
  hasLocalResearchWorkspaceIntent,
5
5
  shouldStartWithDeepResearch,
6
6
  } from "./research-routing.js";
7
- import { hasAgintiEvidenceScope, scopedChatopsEvidenceGoal } from "./scs-evidence.js";
7
+ import {
8
+ deriveScsTaskContract,
9
+ hasAgintiEvidenceScope,
10
+ scopedChatopsEvidenceGoal,
11
+ } from "./scs-evidence.js";
8
12
  import {
9
13
  INTEGRATION_TEXT_WORKSPACE_PROFILE_ID,
10
14
  isIntegrationTextWorkspaceToolAllowed,
@@ -2204,6 +2208,32 @@ function compactToolNames({ config, goal, profile, messages }) {
2204
2208
  const explicitBundles = bundlesForProfile(profileId(config, profile));
2205
2209
  const inferred = inferredBundles(taskText(goal, config, messages));
2206
2210
  const explicitProfileTools = profileToolNames(profile);
2211
+ const taskContract = deriveScsTaskContract({
2212
+ goal: goal || config.goal || "",
2213
+ taskProfile: profileId(config, profile),
2214
+ });
2215
+ const contractRequiredTools = [];
2216
+ if (
2217
+ (Array.isArray(taskContract.requiredEvidence) &&
2218
+ taskContract.requiredEvidence.some(
2219
+ (item) => String(item?.category || "") === "command"
2220
+ )) ||
2221
+ (Array.isArray(taskContract.requiredProjectCommands) &&
2222
+ taskContract.requiredProjectCommands.length > 0)
2223
+ ) {
2224
+ contractRequiredTools.push("run_command");
2225
+ }
2226
+ if (
2227
+ Array.isArray(taskContract.exactInputPaths) &&
2228
+ taskContract.exactInputPaths.length > 0
2229
+ ) {
2230
+ contractRequiredTools.push("read_file");
2231
+ }
2232
+ for (const name of taskContract.requiredToolCalls || []) {
2233
+ if (COMPACT_ELIGIBLE_TOOL_NAMES.has(String(name || "").trim())) {
2234
+ contractRequiredTools.push(String(name).trim());
2235
+ }
2236
+ }
2207
2237
  const bundleOrder = [];
2208
2238
  for (const bundle of explicitBundles) bundleOrder.push(bundle);
2209
2239
  for (const bundle of inferred) {
@@ -2232,7 +2262,10 @@ function compactToolNames({ config, goal, profile, messages }) {
2232
2262
  for (const name of COMPACT_TOOL_BUNDLES[bundle] || []) append(name);
2233
2263
  }
2234
2264
  for (const name of explicitProfileTools) append(name);
2235
- return names;
2265
+ const missingContractTools = [
2266
+ ...new Set(contractRequiredTools.filter((name) => !seen.has(name))),
2267
+ ];
2268
+ return [...missingContractTools, ...names];
2236
2269
  }
2237
2270
 
2238
2271
  /**
@@ -2739,21 +2772,9 @@ export function selectProgressiveTools(
2739
2772
  if (config.scopedArtifactTask === true && config.scopedArtifactRoot) {
2740
2773
  const available = new Map(enabled.map(({ name, tool }) => [name, tool]));
2741
2774
  const scopedWorkspaceTools = [
2742
- constrainScopedArtifactPath(
2743
- available.get("list_files"),
2744
- config.scopedArtifactRoot,
2745
- { commandCwd: config.commandCwd }
2746
- ),
2747
- constrainScopedArtifactPath(
2748
- available.get("read_file"),
2749
- config.scopedArtifactRoot,
2750
- { commandCwd: config.commandCwd }
2751
- ),
2752
- constrainScopedArtifactPath(
2753
- available.get("search_files"),
2754
- config.scopedArtifactRoot,
2755
- { commandCwd: config.commandCwd }
2756
- ),
2775
+ available.get("list_files"),
2776
+ available.get("read_file"),
2777
+ available.get("search_files"),
2757
2778
  constrainScopedArtifactPath(
2758
2779
  available.get("write_file"),
2759
2780
  config.scopedArtifactRoot,
@@ -2767,9 +2788,9 @@ export function selectProgressiveTools(
2767
2788
  available.get("run_command"),
2768
2789
  ].filter(Boolean);
2769
2790
 
2770
- // Artifact isolation limits where the task may read or write; it must not
2771
- // erase the task's research, browser, or specialist capabilities. Keep
2772
- // inferred non-file tools while the workspace tools remain root-scoped.
2791
+ // A host-owned artifact root confines artifact writes, but it must not hide
2792
+ // the workspace evidence needed to produce those artifacts. Keep safe reads
2793
+ // and inferred non-file tools available while write tools remain root-scoped.
2773
2794
  const scopedWorkspaceToolNames = new Set(
2774
2795
  scopedWorkspaceTools.map((tool) => openAiFunctionName(tool)).filter(Boolean)
2775
2796
  );
@@ -1341,6 +1341,20 @@ function goalRequestsScopedArtifactDeliverable(goal = "") {
1341
1341
  return !projectSourceMutation;
1342
1342
  }
1343
1343
 
1344
+ function goalRequestsScopedArtifactOperation(goal = "") {
1345
+ const text = normalizedText(stripCompletedWorkNarration(stripForbiddenLanguage(goal)));
1346
+ const artifactOperation =
1347
+ /\b(?:build|compile|create|draft|edit|export|generate|make|prepare|produce|render|repair|revise|save|update|write)\b[^.\n;]{0,180}\b(?:artifact|brief|cad|diagram|document|figure|image|markdown|memo|model|note|paper|pcb|pdf|presentation|prompt|report|research note|slide deck|slides?|spreadsheet|story|summary|transcript|video)\b/.test(text) ||
1348
+ /\b(?:artifact|brief|cad|diagram|document|figure|image|markdown|memo|model|note|paper|pcb|pdf|presentation|prompt|report|research note|slide deck|slides?|spreadsheet|story|summary|transcript|video)\b[^.\n;]{0,120}\b(?:build|compile|create|draft|edit|export|generate|make|prepare|produce|render|repair|revise|save|update|write)\b/.test(text) ||
1349
+ /(?:创建|生成|保存|编写|撰写|制作|导出|准备|修改|修订|修复|更新)[^。;\n]{0,100}(?:报告|笔记|摘要|文档|论文|图表|图片|模型|演示文稿|幻灯片|表格|故事|提示词|转录|视频)/.test(text);
1350
+ if (!artifactOperation) return false;
1351
+ const projectSourceMutation =
1352
+ /\b(?:change|debug|edit|fix|implement|improve|modify|patch|refactor|repair|replace|rewrite|update)\b[^.\n;]{0,140}\b(?:app|application|code|codebase|implementation|library|package|repo|repository|runtime|source\s+code|src\/)\b/.test(text) ||
1353
+ /\b(?:app|application|code|codebase|implementation|library|package|repo|repository|runtime|source\s+code|src\/)\b[^.\n;]{0,140}\b(?:change|debug|edit|fix|implement|improve|modify|patch|refactor|repair|replace|rewrite|update)\b/.test(text) ||
1354
+ /(?:修改|修复|实现|改进|重构|调试|替换|更新)[^。;\n]{0,100}(?:代码|代码库|实现|运行时|应用|仓库|源码)/.test(text);
1355
+ return !projectSourceMutation;
1356
+ }
1357
+
1344
1358
  function goalRequestsTestExecution(goal = "") {
1345
1359
  const text = normalizedText(stripCompletedWorkNarration(stripForbiddenLanguage(goal)));
1346
1360
  return (
@@ -1400,7 +1414,17 @@ function isReadOnlyReadinessTask(goal = "") {
1400
1414
 
1401
1415
  function requiresSourceGrounding(goal = "") {
1402
1416
  const text = String(goal || "");
1417
+ const positiveText = stripForbiddenLanguage(text);
1418
+ const inferredOutputs = new Set(inferExactOutputPaths(positiveText));
1419
+ const explicitSourceInputs = inferExactInputPaths(positiveText).filter(
1420
+ (item) => !inferredOutputs.has(item)
1421
+ );
1422
+ const explicitlyRequestedInputRead = Boolean(
1423
+ /\b(?:read|inspect|review|audit|consult|examine)\b/iu.test(positiveText) &&
1424
+ explicitSourceInputs.length > 0
1425
+ );
1403
1426
  return (
1427
+ explicitlyRequestedInputRead ||
1404
1428
  isReadOnlyReadinessTask(text) ||
1405
1429
  /\b(?:re-?read|read|inspect|review|audit)\b[^.\n;]{0,120}\b(?:repository|project|workspace)\b[^.\n;]{0,120}\b(?:requirements?|instructions?|implementation|source|tests?)\b/i.test(
1406
1430
  text
@@ -2062,6 +2086,9 @@ export function deriveScsTaskContract({ goal = "", taskProfile = "", acceptanceC
2062
2086
  const scopedArtifactDeliverable = Boolean(
2063
2087
  artifactRoot && goalRequestsScopedArtifactDeliverable(evidenceGoal)
2064
2088
  );
2089
+ const scopedArtifactOperation = Boolean(
2090
+ artifactRoot && goalRequestsScopedArtifactOperation(evidenceGoal)
2091
+ );
2065
2092
  return {
2066
2093
  version: 1,
2067
2094
  outcome: compact(evidenceGoal || "Complete the requested task.", 500),
@@ -2072,6 +2099,7 @@ export function deriveScsTaskContract({ goal = "", taskProfile = "", acceptanceC
2072
2099
  exactOutputPaths,
2073
2100
  requiredArtifactKinds,
2074
2101
  scopedArtifactDeliverable,
2102
+ scopedArtifactOperation,
2075
2103
  excludedOutputPaths,
2076
2104
  artifactRoot,
2077
2105
  exactInputPaths,
@@ -3562,7 +3590,10 @@ function sourceEvidencePaths({ events = [], state = {}, contract = {}, commandCw
3562
3590
  }
3563
3591
 
3564
3592
  function sourceScopeCoverage(contract = {}, { events = [], state = {}, commandCwd = process.cwd() } = {}) {
3565
- const roots = (contract.declaredSourceRoots || []).map((rawPath) => ({
3593
+ const roots = unique([
3594
+ ...(contract.declaredSourceRoots || []),
3595
+ ...(contract.exactInputPaths || []),
3596
+ ]).map((rawPath) => ({
3566
3597
  rawPath,
3567
3598
  absolutePath: resolveContractPath(commandCwd, rawPath).replace(/\/+$/, ""),
3568
3599
  }));
@@ -117,10 +117,15 @@ function matchingReadOnlyRoot(config, target) {
117
117
  .find((root) => isInside(root, target));
118
118
  }
119
119
 
120
- function configuredWorkspaceScopeRoots(config, root) {
121
- const values = Array.isArray(config.workspacePathScopeRoots)
122
- ? config.workspacePathScopeRoots
123
- : [];
120
+ function configuredWorkspaceScopeRoots(config, root, { writeAccess = false } = {}) {
121
+ const values = [
122
+ ...(Array.isArray(config.workspacePathScopeRoots)
123
+ ? config.workspacePathScopeRoots
124
+ : []),
125
+ ...(writeAccess && Array.isArray(config.workspaceWritePathScopeRoots)
126
+ ? config.workspaceWritePathScopeRoots
127
+ : []),
128
+ ];
124
129
  return values
125
130
  .map((item) => String(item || "").trim())
126
131
  .filter(Boolean)
@@ -267,12 +272,16 @@ export function normalizeWorkspaceInputPath(rawPath) {
267
272
  return rawPath;
268
273
  }
269
274
 
270
- export function resolveWorkspacePath(config, inputPath = ".", { allowReadOnlyRoots = false } = {}) {
275
+ export function resolveWorkspacePath(
276
+ config,
277
+ inputPath = ".",
278
+ { allowReadOnlyRoots = false, writeAccess = false } = {}
279
+ ) {
271
280
  const root = workspaceRoot(config);
272
281
  const rawPath = sanitizePathInput(inputPath);
273
282
  const workspacePath = normalizeWorkspaceInputPath(rawPath);
274
283
  const absolutePath = path.resolve(root, workspacePath);
275
- const scopedRoots = configuredWorkspaceScopeRoots(config, root);
284
+ const scopedRoots = configuredWorkspaceScopeRoots(config, root, { writeAccess });
276
285
 
277
286
  if (scopedRoots.length && !scopedRoots.some((scopeRoot) => isInside(scopeRoot, absolutePath))) {
278
287
  throw new Error(`Path is outside the active task artifact scope: ${rawPath}`);
@@ -451,7 +460,7 @@ export function checkWorkspaceToolUse(toolName, args, config) {
451
460
  const operations = parsePatchDocument(args.patch);
452
461
  for (const operation of operations) {
453
462
  for (const candidate of [operation.path, operation.newPath].filter(Boolean)) {
454
- const target = resolveWorkspacePath(config, candidate);
463
+ const target = resolveWorkspacePath(config, candidate, { writeAccess: true });
455
464
  const policy = pathPolicy(toolName, target.relativePath);
456
465
  if (!policy.allowed) return policy;
457
466
  }
@@ -462,6 +471,7 @@ export function checkWorkspaceToolUse(toolName, args, config) {
462
471
  }
463
472
  const target = resolveWorkspacePath(config, args.path || ".", {
464
473
  allowReadOnlyRoots: !WORKSPACE_WRITE_TOOL_NAMES.includes(toolName),
474
+ writeAccess: WORKSPACE_WRITE_TOOL_NAMES.includes(toolName),
465
475
  });
466
476
  const policy = pathPolicy(toolName, target.relativePath);
467
477
  if (!policy.allowed) return policy;
@@ -1006,7 +1016,7 @@ async function deleteChange(target, action = "delete_file", details = {}) {
1006
1016
  }
1007
1017
 
1008
1018
  async function writeFile(config, args) {
1009
- const target = resolveWorkspacePath(config, args.path);
1019
+ const target = resolveWorkspacePath(config, args.path, { writeAccess: true });
1010
1020
  const mode = args.mode === "overwrite" ? "overwrite" : "create";
1011
1021
  const exists = await fs
1012
1022
  .stat(target.absolutePath)
@@ -1033,7 +1043,7 @@ async function applyPatch(config, args) {
1033
1043
  return applyPatchDocument(config, args);
1034
1044
  }
1035
1045
 
1036
- const target = resolveWorkspacePath(config, args.path);
1046
+ const target = resolveWorkspacePath(config, args.path, { writeAccess: true });
1037
1047
  const { content: beforeText, hash } = await readTextFile(target);
1038
1048
  if (args.baseHash && args.baseHash !== hash) {
1039
1049
  throw new Error(`Base hash mismatch for ${target.relativePath}; read the file again before patching.`);
@@ -1234,10 +1244,12 @@ async function applyPatchDocument(config, args) {
1234
1244
  if (seenPaths.has(operation.path)) throw new Error(`Patch contains duplicate file operations for ${operation.path}.`);
1235
1245
  seenPaths.add(operation.path);
1236
1246
 
1237
- const target = resolveWorkspacePath(config, operation.path);
1247
+ const target = resolveWorkspacePath(config, operation.path, { writeAccess: true });
1238
1248
  const policy = pathPolicy("apply_patch", target.relativePath);
1239
1249
  if (!policy.allowed) throw new Error(policy.reason);
1240
- const newTarget = operation.newPath ? resolveWorkspacePath(config, operation.newPath) : null;
1250
+ const newTarget = operation.newPath
1251
+ ? resolveWorkspacePath(config, operation.newPath, { writeAccess: true })
1252
+ : null;
1241
1253
  if (newTarget) {
1242
1254
  const newPolicy = pathPolicy("apply_patch", newTarget.relativePath);
1243
1255
  if (!newPolicy.allowed) throw new Error(newPolicy.reason);
@@ -1300,7 +1312,7 @@ async function applyPatchDocument(config, args) {
1300
1312
  continue;
1301
1313
  }
1302
1314
  if (operation.newPath) {
1303
- const newTarget = resolveWorkspacePath(config, operation.newPath);
1315
+ const newTarget = resolveWorkspacePath(config, operation.newPath, { writeAccess: true });
1304
1316
  const policy = pathPolicy("apply_patch", newTarget.relativePath);
1305
1317
  if (!policy.allowed) throw new Error(policy.reason);
1306
1318
  changes.push(await writeChange(newTarget, item.afterText, "apply_patch_move", { fromPath: target.relativePath, patchFormat: "multi-file" }));