@lazyingart/agintiflow 0.20.11 → 0.20.13
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/public/app.js +8 -1
- package/scripts/smoke-cli-chat.js +20 -0
- package/scripts/smoke-model-roles.js +29 -1
- package/src/agent-runner.js +51 -4
- package/src/interactive-cli.js +9 -1
- package/src/model-client.js +40 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgInTiFlow is a web-first coding agent and CLI with DeepSeek routing, sandboxed tools, model providers, canvas artifacts, and optional wrappers.",
|
|
6
6
|
"license": "Apache-2.0",
|
package/public/app.js
CHANGED
|
@@ -1441,8 +1441,15 @@ function isMarkdownBlockStart(line, nextLine = "") {
|
|
|
1441
1441
|
);
|
|
1442
1442
|
}
|
|
1443
1443
|
|
|
1444
|
+
function unwrapRenderableMarkdownFence(value = "") {
|
|
1445
|
+
const raw = String(value || "");
|
|
1446
|
+
const trimmed = raw.trim();
|
|
1447
|
+
const match = trimmed.match(/^```(?:markdown|md)\s*\n([\s\S]*?)\n```\s*$/i);
|
|
1448
|
+
return match ? match[1] : raw;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1444
1451
|
function renderMarkdown(value) {
|
|
1445
|
-
const lines =
|
|
1452
|
+
const lines = unwrapRenderableMarkdownFence(value).replace(/\r\n?/g, "\n").split("\n");
|
|
1446
1453
|
const html = [];
|
|
1447
1454
|
let paragraph = [];
|
|
1448
1455
|
let listType = "";
|
|
@@ -81,6 +81,25 @@ try {
|
|
|
81
81
|
if ((renderedMarkdown.match(/Present/g) || []).length !== 1) {
|
|
82
82
|
throw new Error("terminal markdown renderer duplicated table rows");
|
|
83
83
|
}
|
|
84
|
+
const renderedMarkdownFence = stripMarkdown(
|
|
85
|
+
[
|
|
86
|
+
"```markdown",
|
|
87
|
+
"# Saved Story",
|
|
88
|
+
"",
|
|
89
|
+
"| File | Status |",
|
|
90
|
+
"| --- | --- |",
|
|
91
|
+
"| `story-ja.md` | **saved** |",
|
|
92
|
+
"```",
|
|
93
|
+
].join("\n")
|
|
94
|
+
);
|
|
95
|
+
if (
|
|
96
|
+
renderedMarkdownFence.includes("code markdown") ||
|
|
97
|
+
renderedMarkdownFence.includes("| --- |") ||
|
|
98
|
+
!renderedMarkdownFence.includes("Saved Story") ||
|
|
99
|
+
!renderedMarkdownFence.includes("story-ja.md")
|
|
100
|
+
) {
|
|
101
|
+
throw new Error("terminal markdown renderer did not unwrap full markdown fences");
|
|
102
|
+
}
|
|
84
103
|
const renderedDiff = stripMarkdown(
|
|
85
104
|
[
|
|
86
105
|
"Diff:",
|
|
@@ -291,6 +310,7 @@ try {
|
|
|
291
310
|
projectRoot: tempRoot,
|
|
292
311
|
checks: [
|
|
293
312
|
"markdown-render",
|
|
313
|
+
"markdown-fence-render",
|
|
294
314
|
"markdown-table-no-duplicate",
|
|
295
315
|
"patch-diff-render",
|
|
296
316
|
"workspace-patch-event-render",
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
modelsForProviderGroup,
|
|
10
10
|
selectModelRoute,
|
|
11
11
|
} from "../src/model-routing.js";
|
|
12
|
-
import { parseTextToolCalls, usesTextToolProtocol } from "../src/model-client.js";
|
|
12
|
+
import { normalizeTextToolCallResponse, parseTextToolCalls, usesTextToolProtocol } from "../src/model-client.js";
|
|
13
13
|
import { modelRoleChoices } from "../src/interactive-cli.js";
|
|
14
14
|
|
|
15
15
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -161,6 +161,33 @@ const multipleRequestedToolCalls = parseTextToolCalls(
|
|
|
161
161
|
);
|
|
162
162
|
assert(multipleRequestedToolCalls.length === 2, "Requested tools parser did not detect multiple function-call texts");
|
|
163
163
|
assert(multipleRequestedToolCalls[1].function.name === "inspect_project", "Requested tools parser returned wrong second requested tool");
|
|
164
|
+
const malformedRequestedToolResponse = normalizeTextToolCallResponse({
|
|
165
|
+
choices: [
|
|
166
|
+
{
|
|
167
|
+
message: {
|
|
168
|
+
role: "assistant",
|
|
169
|
+
content: 'Requested tools: write_file({"path":"story.md","content":"unfinished',
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
});
|
|
174
|
+
const malformedMessage = malformedRequestedToolResponse.choices[0].message;
|
|
175
|
+
assert(malformedMessage.tool_calls?.[0]?.function?.name === "wait", "malformed requested tool text should trigger a safe retry tool");
|
|
176
|
+
assert(!malformedMessage.content.includes("write_file("), "malformed requested tool text should not be surfaced as assistant content");
|
|
177
|
+
const cleanedMalformedSuffix = normalizeTextToolCallResponse({
|
|
178
|
+
choices: [
|
|
179
|
+
{
|
|
180
|
+
message: {
|
|
181
|
+
role: "assistant",
|
|
182
|
+
content: 'Here is a normal answer. Requested tools: write_file({"path":"story.md","content":"unfinished',
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
});
|
|
187
|
+
assert(
|
|
188
|
+
cleanedMalformedSuffix.choices[0].message.content === "Here is a normal answer.",
|
|
189
|
+
"malformed tool suffix should be stripped from otherwise usable assistant content"
|
|
190
|
+
);
|
|
164
191
|
assert(usesTextToolProtocol({ provider: "venice", model: "gemma-4-uncensored" }), "Venice Gemma should use text tool protocol");
|
|
165
192
|
assert(usesTextToolProtocol({ provider: "venice", model: "e2ee-venice-uncensored-24b-p" }), "Venice 1.1 should use text tool protocol");
|
|
166
193
|
assert(usesTextToolProtocol({ provider: "venice", model: "venice-uncensored" }), "Venice legacy 1.1 should use text tool protocol");
|
|
@@ -192,6 +219,7 @@ console.log(
|
|
|
192
219
|
"shared-model-selectors",
|
|
193
220
|
"venice-text-tool-parser",
|
|
194
221
|
"requested-tools-parser",
|
|
222
|
+
"malformed-text-tool-retry",
|
|
195
223
|
"cli-models-command",
|
|
196
224
|
"venice-shortcut",
|
|
197
225
|
],
|
package/src/agent-runner.js
CHANGED
|
@@ -313,7 +313,7 @@ async function createInitialState(config, sessionId) {
|
|
|
313
313
|
? "Host tmux tools are available for long-running terminals: list sessions, capture panes, send safe keys/text, and start detached sessions. Prefer these tools for monitoring long installs/tests/dev servers without blocking; capture before sending input and never send secrets or sudo passwords. Do not start or install tmux inside Docker run_command containers because those containers are short-lived."
|
|
314
314
|
: "",
|
|
315
315
|
config.allowFileTools
|
|
316
|
-
? `Workspace file tools are available in ${config.commandCwd}: inspect_project, list_files, read_file, search_files, write_file, apply_patch, open_workspace_file, and preview_workspace. For large or unfamiliar repositories, call inspect_project first, then search/read AGINTI.md/AGENTS.md/README/manifests as relevant before editing. apply_patch supports exact single-file replacements plus Codex-style/unified multi-file patches; prefer it for source edits after reading/searching the relevant context. Always use workspace-relative paths such as plot_fx.svg or docs/report.tex, never absolute host paths. Secret paths, .git internals, node_modules writes, and huge files are blocked. For generated local websites/pages, use open_workspace_file or preview_workspace instead of starting a localhost server inside Docker.`
|
|
316
|
+
? `Workspace file tools are available in ${config.commandCwd}: inspect_project, list_files, read_file, search_files, write_file, apply_patch, open_workspace_file, and preview_workspace. For large or unfamiliar repositories, call inspect_project first, then search/read AGINTI.md/AGENTS.md/README/manifests as relevant before editing. apply_patch supports exact single-file replacements plus Codex-style/unified multi-file patches; prefer it for source edits after reading/searching the relevant context. Always use workspace-relative paths such as plot_fx.svg or docs/report.tex, never absolute host paths. For newly generated standalone prose/docs/stories/assets, choose a descriptive non-conflicting filename from the topic/language and use mode=create; do not overwrite existing files unless the user explicitly asked to update/replace/overwrite that file. Secret paths, .git internals, node_modules writes, and huge files are blocked. For generated local websites/pages, use open_workspace_file or preview_workspace instead of starting a localhost server inside Docker.`
|
|
317
317
|
: "No workspace file tools are available.",
|
|
318
318
|
config.allowWrapperTools
|
|
319
319
|
? `External coding-agent wrappers are available as advisory tools only. Use the selected wrapper only: ${normalizeWrapperName(config.preferredWrapper)}. Wrapper status: ${wrapperStatusText()}.`
|
|
@@ -362,7 +362,7 @@ async function createInitialState(config, sessionId) {
|
|
|
362
362
|
: `Shell working directory: ${config.commandCwd}`
|
|
363
363
|
: "",
|
|
364
364
|
config.allowFileTools
|
|
365
|
-
? `Workspace file tools enabled in: ${config.commandCwd}. Use inspect_project first for large/unfamiliar codebases. Read AGINTI.md/AGENTS.md/README/manifests when relevant. Use workspace-relative paths. Use apply_patch for code edits; it accepts exact replacements or Codex-style/unified multi-file patches. Local preview tools available: open_workspace_file and preview_workspace.`
|
|
365
|
+
? `Workspace file tools enabled in: ${config.commandCwd}. Use inspect_project first for large/unfamiliar codebases. Read AGINTI.md/AGENTS.md/README/manifests when relevant. Use workspace-relative paths. Use apply_patch for code edits; it accepts exact replacements or Codex-style/unified multi-file patches. For newly generated standalone content, choose descriptive non-conflicting filenames and use mode=create unless the user explicitly asked to overwrite/update. Local preview tools available: open_workspace_file and preview_workspace.`
|
|
366
366
|
: "",
|
|
367
367
|
projectInstructions.exists ? "AGINTI.md project instructions are loaded into system context for this run." : "AGINTI.md is not present unless you create it.",
|
|
368
368
|
config.allowWrapperTools
|
|
@@ -500,7 +500,7 @@ async function applyContinuationPrompt(state, config, observers) {
|
|
|
500
500
|
: `Shell working directory: ${config.commandCwd}. Host platform: ${platformLabel(platform)}. Use OS-compatible commands; prefer WSL/Docker for bash-heavy workflows on Windows.`
|
|
501
501
|
: "",
|
|
502
502
|
config.allowFileTools
|
|
503
|
-
? `Workspace file tools enabled in: ${config.commandCwd}. Use inspect_project first for large or unfamiliar codebases, then search/read exact files before editing. Read AGINTI.md/AGENTS.md/README/manifests when relevant. Use workspace-relative paths. Use apply_patch for code edits; it accepts exact replacements or Codex-style/unified multi-file patches. For generated local files/sites, use open_workspace_file or preview_workspace.`
|
|
503
|
+
? `Workspace file tools enabled in: ${config.commandCwd}. Use inspect_project first for large or unfamiliar codebases, then search/read exact files before editing. Read AGINTI.md/AGENTS.md/README/manifests when relevant. Use workspace-relative paths. Use apply_patch for code edits; it accepts exact replacements or Codex-style/unified multi-file patches. For generated local files/sites, choose descriptive non-conflicting filenames, use mode=create unless the user explicitly asked to overwrite/update, and use open_workspace_file or preview_workspace.`
|
|
504
504
|
: "",
|
|
505
505
|
config.allowWrapperTools
|
|
506
506
|
? `Agent wrappers: selected=${normalizeWrapperName(config.preferredWrapper)}; ${wrapperStatusText()}`
|
|
@@ -601,6 +601,29 @@ function sanitizeToolArgs(toolName, args) {
|
|
|
601
601
|
return safeArgs;
|
|
602
602
|
}
|
|
603
603
|
|
|
604
|
+
function goalClearlyAllowsOverwrite(goal = "") {
|
|
605
|
+
const text = String(goal || "").toLowerCase();
|
|
606
|
+
return (
|
|
607
|
+
/\b(overwrite|replace|update|modify|edit|revise|rewrite|fix|patch|change|append|refresh|regenerate|remember|instruction|instructions|memory|preference|preferences|prefer)\b/i.test(text) ||
|
|
608
|
+
/覆盖|覆寫|替换|替換|更新|修改|修复|修復|编辑|編輯|改写|改寫|追加|记住|記住|指令|说明|說明|偏好|上書き|置換|修正|編集/.test(text)
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
async function implicitOverwriteBlock(toolName, args, config, state) {
|
|
613
|
+
if (toolName !== "write_file" || args.mode !== "overwrite") return null;
|
|
614
|
+
if (goalClearlyAllowsOverwrite(state?.goal || config.goal || "")) return null;
|
|
615
|
+
const target = resolveWorkspacePath(config, args.path || args.file || "");
|
|
616
|
+
const exists = await fs
|
|
617
|
+
.stat(target.absolutePath)
|
|
618
|
+
.then((stat) => stat.isFile())
|
|
619
|
+
.catch(() => false);
|
|
620
|
+
if (!exists) return null;
|
|
621
|
+
return {
|
|
622
|
+
reason: `Refusing to overwrite existing ${target.relativePath} without an explicit update/replace request. Choose a descriptive new filename or ask the user before replacing it.`,
|
|
623
|
+
category: "workspace-overwrite",
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
|
|
604
627
|
function sanitizeToolResult(result) {
|
|
605
628
|
const safeResult = redactValue(result);
|
|
606
629
|
if (typeof safeResult.content === "string") {
|
|
@@ -709,7 +732,7 @@ async function captureSyntheticSnapshot(store, step, config) {
|
|
|
709
732
|
? "Host tmux tools available: tmux_list_sessions, tmux_capture_pane, tmux_send_keys, tmux_start_session. Use them for long-running jobs and agent terminals; capture before sending input. Docker run_command containers are ephemeral, so tmux there will not persist."
|
|
710
733
|
: "",
|
|
711
734
|
config.allowFileTools
|
|
712
|
-
? `Workspace file tools available in: ${config.commandCwd}. Use inspect_project first for large or unfamiliar codebases, then search/read exact files before editing. Use workspace-relative paths. Use apply_patch for code edits; it supports exact single-file replacement and multi-file Codex-style/unified patches.`
|
|
735
|
+
? `Workspace file tools available in: ${config.commandCwd}. Use inspect_project first for large or unfamiliar codebases, then search/read exact files before editing. Use workspace-relative paths. Use apply_patch for code edits; it supports exact single-file replacement and multi-file Codex-style/unified patches. For new standalone generated content, pick a descriptive non-conflicting filename and avoid overwriting unless explicitly requested.`
|
|
713
736
|
: "Workspace file tools disabled.",
|
|
714
737
|
config.allowWrapperTools
|
|
715
738
|
? `Agent wrappers available: selected=${normalizeWrapperName(config.preferredWrapper)}; ${wrapperStatusText()}`
|
|
@@ -805,6 +828,30 @@ async function executeTool(browserState, toolCall, snapshot, config, store, obse
|
|
|
805
828
|
};
|
|
806
829
|
}
|
|
807
830
|
|
|
831
|
+
const overwriteBlock = await implicitOverwriteBlock(toolCall.function.name, args, config, state);
|
|
832
|
+
if (overwriteBlock) {
|
|
833
|
+
await store.appendEvent("tool.blocked", {
|
|
834
|
+
toolName: toolCall.function.name,
|
|
835
|
+
args: safeArgs,
|
|
836
|
+
reason: overwriteBlock.reason,
|
|
837
|
+
category: overwriteBlock.category,
|
|
838
|
+
});
|
|
839
|
+
observers.event("tool.blocked", {
|
|
840
|
+
toolName: toolCall.function.name,
|
|
841
|
+
args: safeArgs,
|
|
842
|
+
reason: overwriteBlock.reason,
|
|
843
|
+
category: overwriteBlock.category,
|
|
844
|
+
});
|
|
845
|
+
return {
|
|
846
|
+
ok: false,
|
|
847
|
+
blocked: true,
|
|
848
|
+
reason: overwriteBlock.reason,
|
|
849
|
+
category: overwriteBlock.category,
|
|
850
|
+
toolName: toolCall.function.name,
|
|
851
|
+
args: safeArgs,
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
|
|
808
855
|
await store.appendEvent("tool.started", {
|
|
809
856
|
toolName: toolCall.function.name,
|
|
810
857
|
args: safeArgs,
|
package/src/interactive-cli.js
CHANGED
|
@@ -241,7 +241,8 @@ function wrapTextLine(value = "", width = 72) {
|
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
export function stripMarkdown(text) {
|
|
244
|
-
const
|
|
244
|
+
const unwrapped = unwrapRenderableMarkdownFence(text);
|
|
245
|
+
const lines = String(unwrapped || "").split(/\r?\n/);
|
|
245
246
|
let inFence = false;
|
|
246
247
|
let diffContext = 0;
|
|
247
248
|
const rendered = [];
|
|
@@ -304,6 +305,13 @@ export function stripMarkdown(text) {
|
|
|
304
305
|
return rendered.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
305
306
|
}
|
|
306
307
|
|
|
308
|
+
function unwrapRenderableMarkdownFence(text = "") {
|
|
309
|
+
const raw = String(text || "");
|
|
310
|
+
const trimmed = raw.trim();
|
|
311
|
+
const match = trimmed.match(/^```(?:markdown|md)\s*\n([\s\S]*?)\n```\s*$/i);
|
|
312
|
+
return match ? match[1] : raw;
|
|
313
|
+
}
|
|
314
|
+
|
|
307
315
|
function renderPatchLine(line = "", { active = false } = {}) {
|
|
308
316
|
const value = String(line || "");
|
|
309
317
|
if (/^diff --git\s+/.test(value)) return color(value, ansi.bold, ansi.cyan);
|
package/src/model-client.js
CHANGED
|
@@ -107,6 +107,7 @@ function textToolProtocolPrompt(tools = []) {
|
|
|
107
107
|
'A strict id form is also accepted: [TOOL_CALLS]tool_name[ARGS]call_short_id[ARGS]{"arg":"value"}',
|
|
108
108
|
'A JSON block form is accepted too: TOOL_CALLS: ```json [{"name":"tool_name","arguments":{"arg":"value"}}] ```',
|
|
109
109
|
"Return only one or more TOOL_CALLS blocks when calling tools; do not wrap them in markdown.",
|
|
110
|
+
"Keep tool-call JSON valid and complete. For long write_file content, prefer a concise complete file or smaller follow-up edits over emitting huge/truncated JSON.",
|
|
110
111
|
"If no tool is needed, answer normally.",
|
|
111
112
|
"Available text tools:",
|
|
112
113
|
...toolLines,
|
|
@@ -140,7 +141,7 @@ function messagesWithTextToolProtocol(config, messages, tools) {
|
|
|
140
141
|
|
|
141
142
|
export function parseTextToolCalls(content = "") {
|
|
142
143
|
const text = String(content || "");
|
|
143
|
-
if (!
|
|
144
|
+
if (!hasTextToolCallMarker(text)) return [];
|
|
144
145
|
|
|
145
146
|
const calls = [];
|
|
146
147
|
for (const call of parseRequestedToolCalls(text)) calls.push(call);
|
|
@@ -196,6 +197,11 @@ export function parseTextToolCalls(content = "") {
|
|
|
196
197
|
return calls;
|
|
197
198
|
}
|
|
198
199
|
|
|
200
|
+
function hasTextToolCallMarker(content = "") {
|
|
201
|
+
const text = String(content || "");
|
|
202
|
+
return text.includes("[TOOL_CALLS]") || /TOOL_CALLS\s*:/i.test(text) || /Requested tools?\s*:/i.test(text);
|
|
203
|
+
}
|
|
204
|
+
|
|
199
205
|
function findMatchingParen(text = "", openIndex = 0) {
|
|
200
206
|
let depth = 0;
|
|
201
207
|
let quote = "";
|
|
@@ -269,14 +275,30 @@ function textBeforeToolCallMarker(content = "") {
|
|
|
269
275
|
.trim();
|
|
270
276
|
}
|
|
271
277
|
|
|
272
|
-
function normalizeTextToolCallResponse(response) {
|
|
278
|
+
export function normalizeTextToolCallResponse(response) {
|
|
273
279
|
const message = response?.choices?.[0]?.message;
|
|
274
280
|
if (!message || Array.isArray(message.tool_calls) && message.tool_calls.length > 0) return response;
|
|
275
281
|
|
|
276
282
|
const calls = parseTextToolCalls(message.content || "");
|
|
277
283
|
if (calls.length === 0) {
|
|
278
284
|
const cleanedContent = textBeforeToolCallMarker(message.content || "");
|
|
279
|
-
if (!
|
|
285
|
+
if (!hasTextToolCallMarker(message.content || "")) return response;
|
|
286
|
+
if (cleanedContent) {
|
|
287
|
+
return {
|
|
288
|
+
...response,
|
|
289
|
+
choices: response.choices.map((choice, index) =>
|
|
290
|
+
index === 0
|
|
291
|
+
? {
|
|
292
|
+
...choice,
|
|
293
|
+
message: {
|
|
294
|
+
...message,
|
|
295
|
+
content: cleanedContent,
|
|
296
|
+
},
|
|
297
|
+
}
|
|
298
|
+
: choice
|
|
299
|
+
),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
280
302
|
return {
|
|
281
303
|
...response,
|
|
282
304
|
choices: response.choices.map((choice, index) =>
|
|
@@ -285,7 +307,18 @@ function normalizeTextToolCallResponse(response) {
|
|
|
285
307
|
...choice,
|
|
286
308
|
message: {
|
|
287
309
|
...message,
|
|
288
|
-
content:
|
|
310
|
+
content:
|
|
311
|
+
"The previous text tool request was malformed or truncated and was not executed. Retry with one valid tool call using the configured tool interface. For long files, write a complete concise file or split the work into smaller valid edits; do not show raw tool-call JSON to the user.",
|
|
312
|
+
tool_calls: [
|
|
313
|
+
{
|
|
314
|
+
id: "text-tool-retry-1",
|
|
315
|
+
type: "function",
|
|
316
|
+
function: {
|
|
317
|
+
name: "wait",
|
|
318
|
+
arguments: JSON.stringify({ ms: 1 }),
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
],
|
|
289
322
|
},
|
|
290
323
|
}
|
|
291
324
|
: choice
|
|
@@ -480,7 +513,7 @@ export async function createPlan(client, config, state) {
|
|
|
480
513
|
? "Host tmux tools are enabled for long-running sessions. Plan to use tmux_start_session for durable jobs, tmux_capture_pane to monitor, tmux_send_keys to interact after capture, and tmux_list_sessions to discover existing sessions. Do not install or run tmux inside Docker run_command containers; those containers are short-lived and cannot preserve tmux servers."
|
|
481
514
|
: "",
|
|
482
515
|
config.allowFileTools
|
|
483
|
-
? `Workspace file tools are enabled in ${config.commandCwd}: inspect_project, list_files, read_file, search_files, write_file, apply_patch, open_workspace_file, preview_workspace. For large or unfamiliar repos, plan to call inspect_project first, then search/read AGINTI.md/AGENTS.md/README/manifests and exact files. apply_patch supports exact single-file replacements and Codex-style/unified multi-file patches; prefer it for edits after reading relevant context. Keep all paths workspace-relative, for example plot_fx.svg or docs/report.tex, and avoid secrets. For generated local HTML/SVG/PDF/static sites, plan to use open_workspace_file or preview_workspace rather than starting a localhost server inside Docker.`
|
|
516
|
+
? `Workspace file tools are enabled in ${config.commandCwd}: inspect_project, list_files, read_file, search_files, write_file, apply_patch, open_workspace_file, preview_workspace. For large or unfamiliar repos, plan to call inspect_project first, then search/read AGINTI.md/AGENTS.md/README/manifests and exact files. apply_patch supports exact single-file replacements and Codex-style/unified multi-file patches; prefer it for edits after reading relevant context. Keep all paths workspace-relative, for example plot_fx.svg or docs/report.tex, and avoid secrets. For newly generated standalone prose/docs/stories/assets, choose a descriptive non-conflicting filename from the topic/language instead of generic names like story.txt or output.txt; do not overwrite existing files unless the user explicitly asked to update/replace/overwrite that file. For generated local HTML/SVG/PDF/static sites, plan to use open_workspace_file or preview_workspace rather than starting a localhost server inside Docker.`
|
|
484
517
|
: "",
|
|
485
518
|
projectInstructions?.exists
|
|
486
519
|
? `Project instructions: AGINTI.md is loaded from ${projectInstructions.path}${projectInstructions.truncated ? " (truncated)" : ""}. Follow it and update it with file tools when the user asks to remember or change project instructions.`
|
|
@@ -504,6 +537,7 @@ export async function createPlan(client, config, state) {
|
|
|
504
537
|
engineeringGuidance,
|
|
505
538
|
"A canvas/artifacts tunnel is available through send_to_canvas. Use it when an output should be highlighted visually, such as screenshots, image files, important markdown, diffs, or generated artifact paths. It is optional for ordinary text answers.",
|
|
506
539
|
"Work like a practical coding agent: inspect when useful, edit with file tools, run safe checks when they add confidence, and keep outputs inside the workspace.",
|
|
540
|
+
"When saving new standalone content, think of a specific filename from the request and use mode=create. If that name already exists and the user did not ask to replace it, choose a safe variant such as topic-language-v2.md instead of overwriting.",
|
|
507
541
|
"For large apps, websites, LaTeX documents, Python/C/shell projects, or system tasks, plan a coherent minimal implementation, then use tools to create files, run checks, and publish artifacts.",
|
|
508
542
|
"For LaTeX/PDF work, first check whether latexmk or pdflatex already exists in the active host/Docker environment; compile with the existing toolchain before installing packages or rebuilding Docker.",
|
|
509
543
|
"For web search or current information tasks, plan to use browser tools or safe shell network tools when allowed, then preserve useful source notes if the output depends on them.",
|
|
@@ -886,7 +920,7 @@ export async function requestNextStep(client, config, messages) {
|
|
|
886
920
|
function: {
|
|
887
921
|
name: "write_file",
|
|
888
922
|
description:
|
|
889
|
-
"Create or overwrite a small UTF-8 workspace file. Use mode=create for
|
|
923
|
+
"Create or overwrite a small UTF-8 workspace file. Use mode=create with a descriptive non-conflicting filename for newly generated standalone content. Use mode=overwrite only when the user explicitly asks to replace/update that file, or after reading the file and the task is clearly to modify it. Secret paths, .git, node_modules writes, and outside-workspace paths are blocked. The runtime records before/after hashes and a compact diff.",
|
|
890
924
|
parameters: {
|
|
891
925
|
type: "object",
|
|
892
926
|
properties: {
|