@lazyingart/agintiflow 0.20.73 → 0.20.75
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 +77 -0
- package/src/agent-runner.js +88 -55
- package/src/behavior-contract.js +1 -0
- package/src/model-client.js +2 -1
- package/src/redaction.js +5 -0
- package/src/workspace-tools.js +45 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.75",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Low-cost, project-aware Web and CLI agents with DeepSeek/Venice/OpenAI routing, visible tool calls, durable sessions, scouts, AAPS, SCS, and guarded local execution.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -78,6 +78,31 @@ try {
|
|
|
78
78
|
};
|
|
79
79
|
const repair = repairModelMessageHistory(staleDeepSeekState, { provider: "deepseek" });
|
|
80
80
|
assert(repair.changed, "stale DeepSeek history was not repaired");
|
|
81
|
+
|
|
82
|
+
const interleavedToolState = {
|
|
83
|
+
messages: [
|
|
84
|
+
{ role: "system", content: "system" },
|
|
85
|
+
{ role: "user", content: "do guarded writes" },
|
|
86
|
+
{
|
|
87
|
+
role: "assistant",
|
|
88
|
+
content: "",
|
|
89
|
+
tool_calls: [
|
|
90
|
+
{ id: "call-a", type: "function", function: { name: "write_file", arguments: "{\"path\":\".env\",\"content\":\"TOKEN=blocked\"}" } },
|
|
91
|
+
{ id: "call-b", type: "function", function: { name: "write_file", arguments: "{\"path\":\"notes/ok.md\",\"content\":\"ok\"}" } },
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
{ role: "tool", tool_call_id: "call-a", content: "{\"ok\":false,\"blocked\":true}" },
|
|
95
|
+
{ role: "user", content: "Loop guard: do not repeat the blocked call." },
|
|
96
|
+
{ role: "tool", tool_call_id: "call-b", content: "{\"ok\":false,\"skipped\":true}" },
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
const interleavedRepair = repairModelMessageHistory(interleavedToolState, { provider: "openai" });
|
|
100
|
+
assert(interleavedRepair.changed, "interleaved tool-call history repair did not report a change");
|
|
101
|
+
const roles = interleavedToolState.messages.map((message) => `${message.role}:${message.tool_call_id || ""}`);
|
|
102
|
+
assert(
|
|
103
|
+
roles.join("|") === "system:|user:|assistant:|tool:call-a|tool:call-b|user:",
|
|
104
|
+
`interleaved tool-call history was not repaired into provider-valid order: ${roles.join("|")}`
|
|
105
|
+
);
|
|
81
106
|
assert(
|
|
82
107
|
staleDeepSeekState.messages.every(
|
|
83
108
|
(message) => message.role !== "assistant" || message.reasoning_content || message.reasoningContent
|
|
@@ -530,6 +555,54 @@ try {
|
|
|
530
555
|
});
|
|
531
556
|
assert(envRun.events.some((event) => event.type === "tool.blocked"), ".env guardrail did not emit tool.blocked");
|
|
532
557
|
|
|
558
|
+
const secretContentResult = await executeWorkspaceTool(
|
|
559
|
+
"write_file",
|
|
560
|
+
{
|
|
561
|
+
path: "notes/secret-leak-report.md",
|
|
562
|
+
content: "Content attempted: DEMO_SECRET_TOKEN=aginti_fake_do_not_use\n",
|
|
563
|
+
mode: "create",
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
commandCwd: workspace,
|
|
567
|
+
allowFileTools: true,
|
|
568
|
+
}
|
|
569
|
+
);
|
|
570
|
+
assert(secretContentResult.blocked && secretContentResult.category === "workspace-content", "write_file secret-like content was not blocked");
|
|
571
|
+
await fs
|
|
572
|
+
.access(path.join(workspace, "notes/secret-leak-report.md"))
|
|
573
|
+
.then(() => {
|
|
574
|
+
throw new Error("secret-like report content was written despite content guardrails");
|
|
575
|
+
})
|
|
576
|
+
.catch((error) => {
|
|
577
|
+
if (error.code !== "ENOENT") throw error;
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
const redactedContentResult = await executeWorkspaceTool(
|
|
581
|
+
"write_file",
|
|
582
|
+
{
|
|
583
|
+
path: "notes/redacted-report.md",
|
|
584
|
+
content: "Content attempted: DEMO_SECRET_TOKEN=[REDACTED]\n",
|
|
585
|
+
mode: "create",
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
commandCwd: workspace,
|
|
589
|
+
allowFileTools: true,
|
|
590
|
+
}
|
|
591
|
+
);
|
|
592
|
+
assert(redactedContentResult.ok, "write_file should allow already-redacted secret placeholders");
|
|
593
|
+
|
|
594
|
+
const secretPatchResult = await executeWorkspaceTool(
|
|
595
|
+
"apply_patch",
|
|
596
|
+
{
|
|
597
|
+
patch: ["*** Begin Patch", "*** Add File: notes/secret-patch.md", "+DEMO_SECRET_TOKEN=aginti_fake_do_not_use", "*** End Patch"].join("\n"),
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
commandCwd: workspace,
|
|
601
|
+
allowFileTools: true,
|
|
602
|
+
}
|
|
603
|
+
);
|
|
604
|
+
assert(secretPatchResult.blocked && secretPatchResult.category === "workspace-content", "apply_patch secret-like additions were not blocked");
|
|
605
|
+
|
|
533
606
|
const outsideRun = await runMock("Create file: ../outside-workspace.txt with blocked content.", "coding-block-outside");
|
|
534
607
|
await fs
|
|
535
608
|
.access(path.join(tempRoot, "outside-workspace.txt"))
|
|
@@ -548,6 +621,7 @@ try {
|
|
|
548
621
|
workspace,
|
|
549
622
|
checks: [
|
|
550
623
|
"deepseek_history_repair",
|
|
624
|
+
"interleaved_tool_history_repair",
|
|
551
625
|
"blocked_tool_batch_short_circuit",
|
|
552
626
|
"deepseek_pro_patch_route",
|
|
553
627
|
"runtime_time_context",
|
|
@@ -576,6 +650,9 @@ try {
|
|
|
576
650
|
"patch_guardrail",
|
|
577
651
|
"patch_move_no_overwrite",
|
|
578
652
|
"block_env",
|
|
653
|
+
"block_secret_write_content",
|
|
654
|
+
"allow_redacted_write_content",
|
|
655
|
+
"block_secret_patch_content",
|
|
579
656
|
"block_outside",
|
|
580
657
|
],
|
|
581
658
|
},
|
package/src/agent-runner.js
CHANGED
|
@@ -213,14 +213,23 @@ function normalizeUrlPath(relativePath) {
|
|
|
213
213
|
function preserveAssistantMessage(message) {
|
|
214
214
|
const preserved = {
|
|
215
215
|
role: "assistant",
|
|
216
|
-
content: message.content || "",
|
|
217
|
-
tool_calls: message.tool_calls
|
|
216
|
+
content: redactSensitiveText(message.content || ""),
|
|
217
|
+
tool_calls: Array.isArray(message.tool_calls)
|
|
218
|
+
? message.tool_calls.map((call) => ({
|
|
219
|
+
...call,
|
|
220
|
+
function: {
|
|
221
|
+
...(call.function || {}),
|
|
222
|
+
arguments:
|
|
223
|
+
typeof call.function?.arguments === "string"
|
|
224
|
+
? redactSensitiveText(call.function.arguments)
|
|
225
|
+
: call.function?.arguments,
|
|
226
|
+
},
|
|
227
|
+
}))
|
|
228
|
+
: message.tool_calls,
|
|
218
229
|
};
|
|
219
230
|
|
|
220
231
|
const reasoningContent = message.reasoning_content || message.reasoningContent;
|
|
221
|
-
if (reasoningContent)
|
|
222
|
-
preserved.reasoning_content = reasoningContent;
|
|
223
|
-
}
|
|
232
|
+
if (reasoningContent) preserved.reasoning_content = redactSensitiveText(reasoningContent);
|
|
224
233
|
|
|
225
234
|
return preserved;
|
|
226
235
|
}
|
|
@@ -233,6 +242,7 @@ export function repairModelMessageHistory(state, config = {}) {
|
|
|
233
242
|
convertedAssistantMessages: 0,
|
|
234
243
|
droppedToolMessages: 0,
|
|
235
244
|
incompleteToolCallMessages: 0,
|
|
245
|
+
reorderedInterleavedMessages: 0,
|
|
236
246
|
};
|
|
237
247
|
}
|
|
238
248
|
|
|
@@ -241,6 +251,7 @@ export function repairModelMessageHistory(state, config = {}) {
|
|
|
241
251
|
let convertedAssistantMessages = 0;
|
|
242
252
|
let droppedToolMessages = 0;
|
|
243
253
|
let incompleteToolCallMessages = 0;
|
|
254
|
+
let reorderedInterleavedMessages = 0;
|
|
244
255
|
|
|
245
256
|
for (let index = 0; index < state.messages.length; index += 1) {
|
|
246
257
|
const message = state.messages[index];
|
|
@@ -254,12 +265,21 @@ export function repairModelMessageHistory(state, config = {}) {
|
|
|
254
265
|
const expectedIds = toolCalls.map((call) => String(call?.id || "")).filter(Boolean);
|
|
255
266
|
const expected = new Set(expectedIds);
|
|
256
267
|
const followingToolMessages = [];
|
|
268
|
+
const deferredInterleavedMessages = [];
|
|
257
269
|
const duplicateOrUnexpectedToolMessages = [];
|
|
258
270
|
const seen = new Set();
|
|
259
271
|
let cursor = index + 1;
|
|
260
272
|
|
|
261
|
-
while (cursor < state.messages.length
|
|
262
|
-
const
|
|
273
|
+
while (cursor < state.messages.length) {
|
|
274
|
+
const nextMessage = state.messages[cursor];
|
|
275
|
+
if (nextMessage?.role !== "tool") {
|
|
276
|
+
const complete = expectedIds.length > 0 && expectedIds.every((id) => seen.has(id));
|
|
277
|
+
if (complete || nextMessage?.role === "assistant") break;
|
|
278
|
+
deferredInterleavedMessages.push(nextMessage);
|
|
279
|
+
cursor += 1;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
const toolMessage = nextMessage;
|
|
263
283
|
const toolCallId = String(toolMessage.tool_call_id || "");
|
|
264
284
|
if (expected.has(toolCallId) && !seen.has(toolCallId)) {
|
|
265
285
|
followingToolMessages.push(toolMessage);
|
|
@@ -275,12 +295,18 @@ export function repairModelMessageHistory(state, config = {}) {
|
|
|
275
295
|
droppedAssistantMessages += 1;
|
|
276
296
|
droppedToolMessages += followingToolMessages.length + duplicateOrUnexpectedToolMessages.length;
|
|
277
297
|
if (!completeToolResults) incompleteToolCallMessages += 1;
|
|
298
|
+
if (deferredInterleavedMessages.length) {
|
|
299
|
+
repaired.push(...deferredInterleavedMessages);
|
|
300
|
+
reorderedInterleavedMessages += deferredInterleavedMessages.length;
|
|
301
|
+
}
|
|
278
302
|
index = cursor - 1;
|
|
279
303
|
continue;
|
|
280
304
|
}
|
|
281
305
|
|
|
282
306
|
repaired.push(preserveAssistantMessage(message));
|
|
283
307
|
repaired.push(...followingToolMessages);
|
|
308
|
+
repaired.push(...deferredInterleavedMessages);
|
|
309
|
+
reorderedInterleavedMessages += deferredInterleavedMessages.length;
|
|
284
310
|
droppedToolMessages += duplicateOrUnexpectedToolMessages.length;
|
|
285
311
|
index = cursor - 1;
|
|
286
312
|
continue;
|
|
@@ -320,6 +346,7 @@ export function repairModelMessageHistory(state, config = {}) {
|
|
|
320
346
|
droppedAssistantMessages > 0 ||
|
|
321
347
|
convertedAssistantMessages > 0 ||
|
|
322
348
|
droppedToolMessages > 0 ||
|
|
349
|
+
reorderedInterleavedMessages > 0 ||
|
|
323
350
|
repaired.length !== state.messages.length;
|
|
324
351
|
|
|
325
352
|
if (changed) {
|
|
@@ -332,6 +359,7 @@ export function repairModelMessageHistory(state, config = {}) {
|
|
|
332
359
|
convertedAssistantMessages,
|
|
333
360
|
droppedToolMessages,
|
|
334
361
|
incompleteToolCallMessages,
|
|
362
|
+
reorderedInterleavedMessages,
|
|
335
363
|
};
|
|
336
364
|
}
|
|
337
365
|
|
|
@@ -1372,7 +1400,7 @@ async function executeTool(browserState, toolCall, snapshot, config, store, obse
|
|
|
1372
1400
|
return result;
|
|
1373
1401
|
}
|
|
1374
1402
|
case "finish":
|
|
1375
|
-
return { ok: true, done: true, result: String(args.result || ""), toolName: "finish" };
|
|
1403
|
+
return { ok: true, done: true, result: redactSensitiveText(String(args.result || "")), toolName: "finish" };
|
|
1376
1404
|
default:
|
|
1377
1405
|
throw new Error(`Unknown tool: ${toolCall.function.name}`);
|
|
1378
1406
|
}
|
|
@@ -1481,7 +1509,7 @@ export async function runAgent(config) {
|
|
|
1481
1509
|
taskProfile: config.taskProfile,
|
|
1482
1510
|
goal: config.goal,
|
|
1483
1511
|
});
|
|
1484
|
-
state.plan = scsPlan.plan;
|
|
1512
|
+
state.plan = redactSensitiveText(scsPlan.plan);
|
|
1485
1513
|
state.meta.scs = scsPlan.scs;
|
|
1486
1514
|
state.messages.push({
|
|
1487
1515
|
role: "user",
|
|
@@ -1496,7 +1524,7 @@ export async function runAgent(config) {
|
|
|
1496
1524
|
await store.appendEvent("scs.committee.plan_drafted", {
|
|
1497
1525
|
phase: scsPlan.scs.phase,
|
|
1498
1526
|
phaseGoal: scsPlan.scs.phaseGoal,
|
|
1499
|
-
plan:
|
|
1527
|
+
plan: state.plan,
|
|
1500
1528
|
acceptanceCriteria: scsPlan.scs.acceptanceCriteria,
|
|
1501
1529
|
});
|
|
1502
1530
|
await store.appendEvent(`scs.student.${scsPlan.scs.student.decision}`, scsPlan.scs.student);
|
|
@@ -1504,16 +1532,16 @@ export async function runAgent(config) {
|
|
|
1504
1532
|
phase: scsPlan.scs.phase,
|
|
1505
1533
|
phaseGoal: scsPlan.scs.phaseGoal,
|
|
1506
1534
|
});
|
|
1507
|
-
await store.savePlan(
|
|
1508
|
-
await store.appendEvent("plan.created", { plan:
|
|
1535
|
+
await store.savePlan(state.plan);
|
|
1536
|
+
await store.appendEvent("plan.created", { plan: state.plan, scs: true });
|
|
1509
1537
|
await store.saveState(state);
|
|
1510
|
-
observers.event("plan.created", { plan:
|
|
1538
|
+
observers.event("plan.created", { plan: state.plan, scs: true });
|
|
1511
1539
|
observers.event("scs.student.approve_plan", scsPlan.scs.student);
|
|
1512
1540
|
emitConsole(config, `SCS: student approved phase plan (${Math.round((scsPlan.scs.student.confidence || 0) * 100)}%).`, {
|
|
1513
1541
|
kind: "meta",
|
|
1514
1542
|
});
|
|
1515
1543
|
} else {
|
|
1516
|
-
const plan = await createPlan(client, config, state);
|
|
1544
|
+
const plan = redactSensitiveText(await createPlan(client, config, state));
|
|
1517
1545
|
state.plan = plan;
|
|
1518
1546
|
await store.savePlan(plan);
|
|
1519
1547
|
await store.appendEvent("plan.created", { plan });
|
|
@@ -1718,7 +1746,7 @@ export async function runAgent(config) {
|
|
|
1718
1746
|
|
|
1719
1747
|
await store.appendEvent("model.responded", {
|
|
1720
1748
|
step,
|
|
1721
|
-
content: assistantMessage.content || "",
|
|
1749
|
+
content: redactSensitiveText(assistantMessage.content || ""),
|
|
1722
1750
|
toolCalls: (assistantMessage.tool_calls || []).map((call) => ({
|
|
1723
1751
|
id: call.id,
|
|
1724
1752
|
name: call.function.name,
|
|
@@ -1727,7 +1755,7 @@ export async function runAgent(config) {
|
|
|
1727
1755
|
});
|
|
1728
1756
|
observers.event("model.responded", {
|
|
1729
1757
|
step,
|
|
1730
|
-
content: assistantMessage.content || "",
|
|
1758
|
+
content: redactSensitiveText(assistantMessage.content || ""),
|
|
1731
1759
|
});
|
|
1732
1760
|
|
|
1733
1761
|
const toolCalls = assistantMessage.tool_calls || [];
|
|
@@ -1740,7 +1768,7 @@ export async function runAgent(config) {
|
|
|
1740
1768
|
await store.saveState(state);
|
|
1741
1769
|
continue;
|
|
1742
1770
|
}
|
|
1743
|
-
const fallback = assistantMessage.content?.trim() || "No tool call returned.";
|
|
1771
|
+
const fallback = redactSensitiveText(assistantMessage.content?.trim() || "No tool call returned.");
|
|
1744
1772
|
if (config.scsActive) {
|
|
1745
1773
|
const decision = await reviewScsFinish(client, config, state, fallback, {
|
|
1746
1774
|
events: await store.loadEvents(),
|
|
@@ -1792,6 +1820,7 @@ export async function runAgent(config) {
|
|
|
1792
1820
|
}
|
|
1793
1821
|
|
|
1794
1822
|
let continueForQueuedInput = false;
|
|
1823
|
+
const postBatchToolResults = [];
|
|
1795
1824
|
for (let toolIndex = 0; toolIndex < toolCalls.length; toolIndex += 1) {
|
|
1796
1825
|
const toolCall = toolCalls[toolIndex];
|
|
1797
1826
|
throwIfAborted(config);
|
|
@@ -1801,44 +1830,7 @@ export async function runAgent(config) {
|
|
|
1801
1830
|
tool_call_id: toolCall.id,
|
|
1802
1831
|
content: JSON.stringify(toolResult),
|
|
1803
1832
|
});
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
if (config.scsActive && shouldReviewToolResult(toolResult, state)) {
|
|
1807
|
-
const decision = await reviewScsToolResult(client, config, state, toolResult, {
|
|
1808
|
-
events: await store.loadEvents(),
|
|
1809
|
-
taskProfile: config.taskProfile,
|
|
1810
|
-
goal: config.goal,
|
|
1811
|
-
});
|
|
1812
|
-
state.meta.scs = state.meta.scs || { enabled: true, mode: config.enableScs || "on", active: true };
|
|
1813
|
-
state.meta.scs.monitorReviews = (state.meta.scs.monitorReviews || 0) + 1;
|
|
1814
|
-
state.meta.scs.lastStudentDecision = decision;
|
|
1815
|
-
await store.appendEvent(`scs.student.${decision.decision}`, {
|
|
1816
|
-
...decision,
|
|
1817
|
-
toolName: toolResult.toolName,
|
|
1818
|
-
});
|
|
1819
|
-
observers.event(`scs.student.${decision.decision}`, {
|
|
1820
|
-
decision: decision.decision,
|
|
1821
|
-
reason: decision.reason,
|
|
1822
|
-
toolName: toolResult.toolName,
|
|
1823
|
-
});
|
|
1824
|
-
if (decision.decision === "rethink_plan" || decision.decision === "reject_phase") {
|
|
1825
|
-
state.messages.push({
|
|
1826
|
-
role: "user",
|
|
1827
|
-
content: [
|
|
1828
|
-
"SCS student monitor requested a rethink based on tool evidence.",
|
|
1829
|
-
`Decision: ${decision.decision}`,
|
|
1830
|
-
`Reason: ${decision.reason || "No reason provided."}`,
|
|
1831
|
-
decision.nextRequiredAction ? `Next required action: ${decision.nextRequiredAction}` : "",
|
|
1832
|
-
"Supervisor: do not repeat the same failed call. Adjust within the approved phase or finish with a concrete blocker if the phase is invalidated.",
|
|
1833
|
-
]
|
|
1834
|
-
.filter(Boolean)
|
|
1835
|
-
.join("\n"),
|
|
1836
|
-
});
|
|
1837
|
-
emitConsole(config, `SCS: ${decision.decision} after ${toolResult.toolName}: ${decision.reason || "reviewed"}`, {
|
|
1838
|
-
kind: "meta",
|
|
1839
|
-
});
|
|
1840
|
-
}
|
|
1841
|
-
}
|
|
1833
|
+
postBatchToolResults.push(toolResult);
|
|
1842
1834
|
|
|
1843
1835
|
if (toolResult.toolName === "run_command") {
|
|
1844
1836
|
observers.log("command.output", {
|
|
@@ -1956,6 +1948,47 @@ export async function runAgent(config) {
|
|
|
1956
1948
|
}
|
|
1957
1949
|
}
|
|
1958
1950
|
|
|
1951
|
+
for (const toolResult of postBatchToolResults) {
|
|
1952
|
+
await applyToolLoopGuard(state, toolResult, store, observers);
|
|
1953
|
+
|
|
1954
|
+
if (config.scsActive && shouldReviewToolResult(toolResult, state)) {
|
|
1955
|
+
const decision = await reviewScsToolResult(client, config, state, toolResult, {
|
|
1956
|
+
events: await store.loadEvents(),
|
|
1957
|
+
taskProfile: config.taskProfile,
|
|
1958
|
+
goal: config.goal,
|
|
1959
|
+
});
|
|
1960
|
+
state.meta.scs = state.meta.scs || { enabled: true, mode: config.enableScs || "on", active: true };
|
|
1961
|
+
state.meta.scs.monitorReviews = (state.meta.scs.monitorReviews || 0) + 1;
|
|
1962
|
+
state.meta.scs.lastStudentDecision = decision;
|
|
1963
|
+
await store.appendEvent(`scs.student.${decision.decision}`, {
|
|
1964
|
+
...decision,
|
|
1965
|
+
toolName: toolResult.toolName,
|
|
1966
|
+
});
|
|
1967
|
+
observers.event(`scs.student.${decision.decision}`, {
|
|
1968
|
+
decision: decision.decision,
|
|
1969
|
+
reason: decision.reason,
|
|
1970
|
+
toolName: toolResult.toolName,
|
|
1971
|
+
});
|
|
1972
|
+
if (decision.decision === "rethink_plan" || decision.decision === "reject_phase") {
|
|
1973
|
+
state.messages.push({
|
|
1974
|
+
role: "user",
|
|
1975
|
+
content: [
|
|
1976
|
+
"SCS student monitor requested a rethink based on tool evidence.",
|
|
1977
|
+
`Decision: ${decision.decision}`,
|
|
1978
|
+
`Reason: ${decision.reason || "No reason provided."}`,
|
|
1979
|
+
decision.nextRequiredAction ? `Next required action: ${decision.nextRequiredAction}` : "",
|
|
1980
|
+
"Supervisor: do not repeat the same failed call. Adjust within the approved phase or finish with a concrete blocker if the phase is invalidated.",
|
|
1981
|
+
]
|
|
1982
|
+
.filter(Boolean)
|
|
1983
|
+
.join("\n"),
|
|
1984
|
+
});
|
|
1985
|
+
emitConsole(config, `SCS: ${decision.decision} after ${toolResult.toolName}: ${decision.reason || "reviewed"}`, {
|
|
1986
|
+
kind: "meta",
|
|
1987
|
+
});
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1959
1992
|
if (continueForQueuedInput) continue;
|
|
1960
1993
|
|
|
1961
1994
|
if (config.scsActive && shouldReviewScsProgress(step, state)) {
|
package/src/behavior-contract.js
CHANGED
|
@@ -69,6 +69,7 @@ export function formatBehaviorContractForPrompt({ mode = "runtime" } = {}) {
|
|
|
69
69
|
"Make surgical edits: no drive-by refactors, unrelated formatting churn, or deletion of code you did not need to touch.",
|
|
70
70
|
"Define or infer concrete success criteria for non-trivial work, then run focused checks or state why checks are unavailable.",
|
|
71
71
|
"Respect the permission contract: if a tool is blocked or returns permissionAdvice, stop and present the exact suggestedCommand/approval path instead of retrying variants or inventing CLI flags.",
|
|
72
|
+
"Protect secrets aggressively: never repeat token/key/password/secret values from prompts, files, tool output, plans, final answers, reports, diffs, or artifacts. Redact the value as [REDACTED] and use dedicated key storage such as `aginti keys set` when credentials are needed.",
|
|
72
73
|
"Keep artifacts durable and discoverable with descriptive non-conflicting names; never overwrite unless the user clearly asked.",
|
|
73
74
|
"When reporting shell, language, runtime, build, or test results, name the actual environment used (host vs Docker, relevant interpreter/tool path/version when it matters). Do not claim compatibility across untested runtimes, hosts, containers, or language versions; state the caveat or run an explicit check.",
|
|
74
75
|
"Do not self-invoke AgInTiFlow with npx/npm exec or nested aginti commands from inside the agent shell; it can resolve stale project packages or create recursive sessions. Use current runtime evidence, project/session files, or ask for a host-side diagnostic instead.",
|
package/src/model-client.js
CHANGED
|
@@ -6,6 +6,7 @@ import { engineeringGuidanceForTask } from "./engineering-guidance.js";
|
|
|
6
6
|
import { formatSkillsForPrompt, selectSkillsForGoal } from "./skill-library.js";
|
|
7
7
|
import { platformInfo, platformLabel } from "./platform.js";
|
|
8
8
|
import { formatBehaviorContractForPrompt } from "./behavior-contract.js";
|
|
9
|
+
import { redactSensitiveText } from "./redaction.js";
|
|
9
10
|
|
|
10
11
|
export function createClient(config) {
|
|
11
12
|
if (config.provider === "mock") {
|
|
@@ -558,7 +559,7 @@ export async function createPlan(client, config, state) {
|
|
|
558
559
|
requestOptions(config)
|
|
559
560
|
);
|
|
560
561
|
|
|
561
|
-
return response.choices[0]?.message?.content?.trim() || "1. Inspect the page.\n2. Use the smallest safe action.\n3. Finish with a concise answer.";
|
|
562
|
+
return redactSensitiveText(response.choices[0]?.message?.content?.trim() || "1. Inspect the page.\n2. Use the smallest safe action.\n3. Finish with a concise answer.");
|
|
562
563
|
}
|
|
563
564
|
|
|
564
565
|
export async function requestNextStep(client, config, messages) {
|
package/src/redaction.js
CHANGED
|
@@ -19,6 +19,11 @@ export function redactSensitiveText(value) {
|
|
|
19
19
|
return text;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
export function hasSensitiveText(value) {
|
|
23
|
+
const text = String(value ?? "");
|
|
24
|
+
return redactSensitiveText(text) !== text;
|
|
25
|
+
}
|
|
26
|
+
|
|
22
27
|
export function redactValue(value) {
|
|
23
28
|
if (typeof value === "string") return redactSensitiveText(value);
|
|
24
29
|
if (Array.isArray(value)) return value.map((item) => redactValue(item));
|
package/src/workspace-tools.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { redactSensitiveText } from "./redaction.js";
|
|
4
|
+
import { hasSensitiveText, redactSensitiveText } from "./redaction.js";
|
|
5
5
|
|
|
6
6
|
export const WORKSPACE_TOOL_NAMES = ["inspect_project", "list_files", "read_file", "search_files", "write_file", "apply_patch"];
|
|
7
7
|
export const WORKSPACE_WRITE_TOOL_NAMES = ["write_file", "apply_patch"];
|
|
@@ -188,6 +188,37 @@ function pathPolicy(toolName, relativePath) {
|
|
|
188
188
|
return { allowed: true };
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
function secretContentPolicy(content) {
|
|
192
|
+
if (!hasSensitiveText(content)) return { allowed: true };
|
|
193
|
+
return {
|
|
194
|
+
allowed: false,
|
|
195
|
+
reason:
|
|
196
|
+
"Write content appears to contain token-like secret text. Redact secret values as [REDACTED] or use dedicated key storage such as `aginti keys set`; do not write credentials into workspace files.",
|
|
197
|
+
category: "workspace-content",
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function writeContentPolicy(toolName, args, operations = null) {
|
|
202
|
+
if (toolName === "write_file") return secretContentPolicy(args.content || "");
|
|
203
|
+
if (toolName !== "apply_patch") return { allowed: true };
|
|
204
|
+
|
|
205
|
+
if (typeof args.patch === "string" && args.patch.trim()) {
|
|
206
|
+
for (const operation of operations || []) {
|
|
207
|
+
if (operation.type === "add") {
|
|
208
|
+
const policy = secretContentPolicy(operation.content || "");
|
|
209
|
+
if (!policy.allowed) return policy;
|
|
210
|
+
}
|
|
211
|
+
for (const hunk of operation.hunks || []) {
|
|
212
|
+
const policy = secretContentPolicy(hunk.replace || "");
|
|
213
|
+
if (!policy.allowed) return policy;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return { allowed: true };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return secretContentPolicy(args.replace ?? "");
|
|
220
|
+
}
|
|
221
|
+
|
|
191
222
|
export function checkWorkspaceToolUse(toolName, args, config) {
|
|
192
223
|
if (!WORKSPACE_TOOL_NAMES.includes(toolName)) return { allowed: true };
|
|
193
224
|
if (!config.allowFileTools) {
|
|
@@ -196,17 +227,26 @@ export function checkWorkspaceToolUse(toolName, args, config) {
|
|
|
196
227
|
|
|
197
228
|
try {
|
|
198
229
|
if (toolName === "apply_patch" && typeof args.patch === "string" && args.patch.trim()) {
|
|
199
|
-
|
|
230
|
+
const operations = parsePatchDocument(args.patch);
|
|
231
|
+
for (const operation of operations) {
|
|
200
232
|
for (const candidate of [operation.path, operation.newPath].filter(Boolean)) {
|
|
201
233
|
const target = resolveWorkspacePath(config, candidate);
|
|
202
234
|
const policy = pathPolicy(toolName, target.relativePath);
|
|
203
235
|
if (!policy.allowed) return policy;
|
|
204
236
|
}
|
|
205
237
|
}
|
|
238
|
+
const contentPolicy = writeContentPolicy(toolName, args, operations);
|
|
239
|
+
if (!contentPolicy.allowed) return contentPolicy;
|
|
206
240
|
return { allowed: true };
|
|
207
241
|
}
|
|
208
242
|
const target = resolveWorkspacePath(config, args.path || ".");
|
|
209
|
-
|
|
243
|
+
const policy = pathPolicy(toolName, target.relativePath);
|
|
244
|
+
if (!policy.allowed) return policy;
|
|
245
|
+
if (WORKSPACE_WRITE_TOOL_NAMES.includes(toolName)) {
|
|
246
|
+
const contentPolicy = writeContentPolicy(toolName, args);
|
|
247
|
+
if (!contentPolicy.allowed) return contentPolicy;
|
|
248
|
+
}
|
|
249
|
+
return { allowed: true };
|
|
210
250
|
} catch (error) {
|
|
211
251
|
return {
|
|
212
252
|
allowed: false,
|
|
@@ -591,6 +631,8 @@ async function writeChange(target, nextContent, action, details = {}) {
|
|
|
591
631
|
if (Buffer.byteLength(content, "utf8") > MAX_WRITE_BYTES) {
|
|
592
632
|
throw new Error(`Write is too large for safe workspace tools: ${target.relativePath}`);
|
|
593
633
|
}
|
|
634
|
+
const contentPolicy = secretContentPolicy(content);
|
|
635
|
+
if (!contentPolicy.allowed) throw new Error(contentPolicy.reason);
|
|
594
636
|
|
|
595
637
|
await fs.mkdir(path.dirname(target.absolutePath), { recursive: true });
|
|
596
638
|
let beforeText = "";
|