@lazyingart/agintiflow 0.20.303 → 0.20.305
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 +1 -1
- package/scripts/smoke-coding-tools.js +29 -0
- package/scripts/smoke-progressive-tool-selection.js +68 -3
- package/scripts/smoke-scs-evidence-visibility.js +42 -0
- package/src/agent-runner.js +24 -2
- package/src/progressive-tool-selection.js +41 -20
- package/src/scs-evidence.js +38 -4
- package/src/workspace-tools.js +24 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.305",
|
|
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.
|
|
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.
|
|
5751
|
+
longScopedRuntime.workspaceWritePathScopeRoots,
|
|
5687
5752
|
["output/wechat_worker/long-scoped-task"],
|
|
5688
|
-
"long-context artifact
|
|
5753
|
+
"long-context artifact writes escaped their exact task root"
|
|
5689
5754
|
);
|
|
5690
5755
|
assertStrict.equal(
|
|
5691
5756
|
longScopedRuntime.completionFreshMutationRequired,
|
|
@@ -205,6 +205,20 @@ const readOnlyReviewContract = deriveScsTaskContract({
|
|
|
205
205
|
taskProfile: "review",
|
|
206
206
|
});
|
|
207
207
|
|
|
208
|
+
const readOnlyOperatorAuditRequest =
|
|
209
|
+
"This is a short imperfect operator audit. Check these three things without changing source files or sending messages: 1) run curl -fsS http://127.0.0.1:8008/readyz and state whether LocalLLM is ready; 2) read configs/model-policy.json and name the configured AgInTi provider order; 3) run git status --short and say only whether tracked source changes exist, without treating ignored runtime output as source. Cover all three numbered items in one concise answer and write the normal task result.";
|
|
210
|
+
const readOnlyOperatorAuditContract = deriveScsTaskContract({
|
|
211
|
+
goal: [
|
|
212
|
+
readOnlyOperatorAuditRequest,
|
|
213
|
+
`AGINTI_EVIDENCE_SCOPE_JSON: ${JSON.stringify({
|
|
214
|
+
mode: "task",
|
|
215
|
+
request: readOnlyOperatorAuditRequest,
|
|
216
|
+
artifact_root: "/tmp/aginti-read-only-audit",
|
|
217
|
+
})}`,
|
|
218
|
+
].join("\n\n"),
|
|
219
|
+
taskProfile: "auto",
|
|
220
|
+
});
|
|
221
|
+
|
|
208
222
|
const explicitReviewRepairContract = deriveScsTaskContract({
|
|
209
223
|
goal: [
|
|
210
224
|
"Review focus: inspect changed files and fix confirmed defects.",
|
|
@@ -374,6 +388,34 @@ assert.equal(
|
|
|
374
388
|
true,
|
|
375
389
|
"an explicit review-and-fix focus lost its positive mutation request"
|
|
376
390
|
);
|
|
391
|
+
assert.equal(
|
|
392
|
+
readOnlyOperatorAuditContract.requiresWorkspaceMutation,
|
|
393
|
+
false,
|
|
394
|
+
"a host-managed task response was mistaken for a workspace mutation"
|
|
395
|
+
);
|
|
396
|
+
assert.equal(
|
|
397
|
+
readOnlyOperatorAuditContract.requiresFileMutation,
|
|
398
|
+
false,
|
|
399
|
+
"an observational source-status mention was mistaken for a source mutation"
|
|
400
|
+
);
|
|
401
|
+
assert(
|
|
402
|
+
readOnlyOperatorAuditContract.requiredEvidence.some(
|
|
403
|
+
(item) => item.category === "command"
|
|
404
|
+
) &&
|
|
405
|
+
!readOnlyOperatorAuditContract.requiredEvidence.some(
|
|
406
|
+
(item) => ["artifact", "file"].includes(item.category)
|
|
407
|
+
),
|
|
408
|
+
"the phrase 'cover all items' invented cover-art or file evidence"
|
|
409
|
+
);
|
|
410
|
+
assert(
|
|
411
|
+
readOnlyOperatorAuditContract.forbiddenActions.some((item) =>
|
|
412
|
+
/changing source files or sending messages/i.test(item)
|
|
413
|
+
) &&
|
|
414
|
+
!readOnlyOperatorAuditContract.forbiddenActions.some((item) =>
|
|
415
|
+
/run curl/i.test(item)
|
|
416
|
+
),
|
|
417
|
+
"a colon after a without-clause swallowed the following command"
|
|
418
|
+
);
|
|
377
419
|
assert(
|
|
378
420
|
pageSafeReportContract.requiredEvidence.some((item) => item.category === "file"),
|
|
379
421
|
"a scoped existing-report edit did not require file evidence"
|
package/src/agent-runner.js
CHANGED
|
@@ -15034,8 +15034,30 @@ export function nextStepRuntimeConfig(config = {}, state = {}) {
|
|
|
15034
15034
|
const scopedRootRelative = resolvedScopedRoot
|
|
15035
15035
|
? path.relative(commandCwd, resolvedScopedRoot)
|
|
15036
15036
|
: "";
|
|
15037
|
-
const
|
|
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.
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
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
|
-
//
|
|
2771
|
-
//
|
|
2772
|
-
// inferred non-file tools while
|
|
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
|
);
|
package/src/scs-evidence.js
CHANGED
|
@@ -1275,8 +1275,19 @@ function goalRequestsExplicitTestMutation(text = "") {
|
|
|
1275
1275
|
);
|
|
1276
1276
|
}
|
|
1277
1277
|
|
|
1278
|
+
function stripHostManagedResponseNarration(goal = "") {
|
|
1279
|
+
return String(goal || "").replace(
|
|
1280
|
+
/\b(?:and\s+)?(?:write|return|provide)\s+(?:(?:a|the)\s+)?(?:(?:concise|final|normal|structured)\s+)?(?:(?:agent|task)\s+)?(?:answer|response|result)\b(?!\s+(?:as|at|file|in|into|json|markdown|pdf|text|to|under)\b)/gi,
|
|
1281
|
+
""
|
|
1282
|
+
);
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1278
1285
|
function goalRequestsWorkspaceMutation(goal = "", taskProfile = "") {
|
|
1279
|
-
const text = normalizedText(
|
|
1286
|
+
const text = normalizedText(
|
|
1287
|
+
stripHostManagedResponseNarration(
|
|
1288
|
+
stripCompletedWorkNarration(stripForbiddenLanguage(goal))
|
|
1289
|
+
)
|
|
1290
|
+
);
|
|
1280
1291
|
const explicitAddMutation =
|
|
1281
1292
|
goalRequestsExplicitTestMutation(text) ||
|
|
1282
1293
|
/\badd\b(?:\s+(?:a|an|the|new|additional|specific))*\s+(?:code|documents?|files?|notes?|readme|scripts?|source|workspace)\b/.test(
|
|
@@ -1330,6 +1341,20 @@ function goalRequestsScopedArtifactDeliverable(goal = "") {
|
|
|
1330
1341
|
return !projectSourceMutation;
|
|
1331
1342
|
}
|
|
1332
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
|
+
|
|
1333
1358
|
function goalRequestsTestExecution(goal = "") {
|
|
1334
1359
|
const text = normalizedText(stripCompletedWorkNarration(stripForbiddenLanguage(goal)));
|
|
1335
1360
|
return (
|
|
@@ -1448,7 +1473,12 @@ function inferRequirementCategories(goal = "", taskProfile = "", acceptanceCrite
|
|
|
1448
1473
|
if (goalRequestsTestExecution(positiveGoal)) {
|
|
1449
1474
|
categories.add("test");
|
|
1450
1475
|
}
|
|
1451
|
-
if (
|
|
1476
|
+
if (
|
|
1477
|
+
textHas(
|
|
1478
|
+
mandatoryEvidenceText,
|
|
1479
|
+
/\b(artifact|canvas|pdf|image|video|screenshot|plot|chart|figure|docx|archive|copy to|export|generated|generate|draft)\b|\b(?:album|book|paper|report|video)\s+cover\b|\bcover\s+(?:art|design|image)\b/
|
|
1480
|
+
) || /输出|产物|图片|视频|截图|封面|生成/.test(mandatoryEvidenceText)
|
|
1481
|
+
) {
|
|
1452
1482
|
categories.add("artifact");
|
|
1453
1483
|
}
|
|
1454
1484
|
if (
|
|
@@ -1513,14 +1543,14 @@ function inferForbiddenActions(goal = "") {
|
|
|
1513
1543
|
const text = String(goal || "");
|
|
1514
1544
|
const forbidden = [];
|
|
1515
1545
|
const isAction = (value = "") =>
|
|
1516
|
-
/\b(use|open|click|browse|browser|upload|attach|submit|publish|deploy|run|execute|install|delete|remove|commit|push|call|api|alter|
|
|
1546
|
+
/\b(use|open|click|browse|browser|upload|attach|submit|publish|deploy|run|execute|install|delete|remove|commit|push|call|api|alter|chang(?:e|ing)|edit(?:ing)?|fix(?:ing)?|modif(?:y|ying)|patch(?:ing)?|repair(?:ing)?|rewrit(?:e|ing)|send(?:ing)?|touch(?:ing)?|writ(?:e|ing))\b/i.test(
|
|
1517
1547
|
value
|
|
1518
1548
|
) || /浏览器|网页|打开|点击|上传|提交|发布|部署|运行|执行|安装|删除|复制|移动|修改|编辑|修复|改写|写入|提交代码|推送|调用|API/.test(value);
|
|
1519
1549
|
const patterns = [
|
|
1520
1550
|
{ re: /\b(do not|don't|dont|never|no need to)\s+([^.\n;]+)/gi, prefix: "User forbids" },
|
|
1521
1551
|
// A sentence such as "without changing X, run tests and commit" starts a
|
|
1522
1552
|
// positive instruction after the comma. Keep only the local without-clause.
|
|
1523
|
-
{ re: /\bwithout\s+([
|
|
1553
|
+
{ re: /\bwithout\s+([^.,:\uFF1A\n;]+)/gi, prefix: "User forbids" },
|
|
1524
1554
|
{ re: /不要([^。\n;]+)/g, prefix: "User forbids" },
|
|
1525
1555
|
{ re: /禁止([^。\n;]+)/g, prefix: "User forbids" },
|
|
1526
1556
|
];
|
|
@@ -2046,6 +2076,9 @@ export function deriveScsTaskContract({ goal = "", taskProfile = "", acceptanceC
|
|
|
2046
2076
|
const scopedArtifactDeliverable = Boolean(
|
|
2047
2077
|
artifactRoot && goalRequestsScopedArtifactDeliverable(evidenceGoal)
|
|
2048
2078
|
);
|
|
2079
|
+
const scopedArtifactOperation = Boolean(
|
|
2080
|
+
artifactRoot && goalRequestsScopedArtifactOperation(evidenceGoal)
|
|
2081
|
+
);
|
|
2049
2082
|
return {
|
|
2050
2083
|
version: 1,
|
|
2051
2084
|
outcome: compact(evidenceGoal || "Complete the requested task.", 500),
|
|
@@ -2056,6 +2089,7 @@ export function deriveScsTaskContract({ goal = "", taskProfile = "", acceptanceC
|
|
|
2056
2089
|
exactOutputPaths,
|
|
2057
2090
|
requiredArtifactKinds,
|
|
2058
2091
|
scopedArtifactDeliverable,
|
|
2092
|
+
scopedArtifactOperation,
|
|
2059
2093
|
excludedOutputPaths,
|
|
2060
2094
|
artifactRoot,
|
|
2061
2095
|
exactInputPaths,
|
package/src/workspace-tools.js
CHANGED
|
@@ -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 =
|
|
122
|
-
|
|
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(
|
|
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
|
|
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" }));
|