@lazyingart/agintiflow 0.8.4 → 0.8.6
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/README.md +2 -2
- package/docs/agent-runtime-pipe.md +4 -0
- package/package.json +1 -1
- package/public/app.js +55 -1
- package/public/index.html +4 -1
- package/public/styles.css +9 -0
- package/src/agent-runner.js +280 -25
- package/src/docker-sandbox.js +3 -1
- package/src/guardrails.js +21 -0
- package/src/interactive-cli.js +221 -75
- package/src/model-client.js +86 -16
- package/src/static-preview-server.js +105 -0
- package/src/workspace-tools.js +1 -1
- package/web.js +27 -0
package/src/interactive-cli.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import readline from "node:readline/promises";
|
|
2
|
+
import { emitKeypressEvents } from "node:readline";
|
|
2
3
|
import { stdin as input, stdout as output } from "node:process";
|
|
3
4
|
import { runAgent } from "./agent-runner.js";
|
|
4
5
|
import { loadConfig } from "./config.js";
|
|
@@ -6,8 +7,96 @@ import { initProject, listProjectSessions } from "./project.js";
|
|
|
6
7
|
import { normalizePackageInstallPolicy, normalizeSandboxMode } from "./command-policy.js";
|
|
7
8
|
import { normalizeTaskProfile } from "./task-profiles.js";
|
|
8
9
|
|
|
10
|
+
const useColor = Boolean(input.isTTY && output.isTTY && !process.env.NO_COLOR);
|
|
11
|
+
const ansi = {
|
|
12
|
+
reset: "\x1b[0m",
|
|
13
|
+
bold: "\x1b[1m",
|
|
14
|
+
dim: "\x1b[2m",
|
|
15
|
+
cyan: "\x1b[36m",
|
|
16
|
+
green: "\x1b[32m",
|
|
17
|
+
yellow: "\x1b[33m",
|
|
18
|
+
red: "\x1b[31m",
|
|
19
|
+
userBg: "\x1b[48;5;24m\x1b[38;5;231m",
|
|
20
|
+
agentBg: "\x1b[48;5;29m\x1b[38;5;231m",
|
|
21
|
+
systemBg: "\x1b[48;5;236m\x1b[38;5;245m",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function color(value, ...codes) {
|
|
25
|
+
if (!useColor || codes.length === 0) return String(value);
|
|
26
|
+
return `${codes.join("")}${value}${ansi.reset}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function label(name, bgCode) {
|
|
30
|
+
return color(` ${name} `, bgCode, ansi.bold);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function userPrompt() {
|
|
34
|
+
return `\n${label("user>", ansi.userBg)} `;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function stripMarkdown(text) {
|
|
38
|
+
const lines = String(text || "").split(/\r?\n/);
|
|
39
|
+
let inFence = false;
|
|
40
|
+
const rendered = [];
|
|
41
|
+
|
|
42
|
+
for (const rawLine of lines) {
|
|
43
|
+
let line = rawLine;
|
|
44
|
+
if (/^\s*```/.test(line)) {
|
|
45
|
+
inFence = !inFence;
|
|
46
|
+
if (inFence) rendered.push(color("code", ansi.dim));
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!inFence) {
|
|
51
|
+
if (/^\s*[-*_]{3,}\s*$/.test(line)) {
|
|
52
|
+
rendered.push("");
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
line = line.replace(/^\s{0,3}#{1,6}\s+/, "");
|
|
56
|
+
line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)");
|
|
57
|
+
line = line.replace(/\*\*([^*]+)\*\*/g, (_, value) => color(value, ansi.bold));
|
|
58
|
+
line = line.replace(/__([^_]+)__/g, (_, value) => color(value, ansi.bold));
|
|
59
|
+
line = line.replace(/(^|[^\w])\*([^*\n]+)\*/g, "$1$2");
|
|
60
|
+
line = line.replace(/(^|[^\w])_([^_\n]+)_/g, "$1$2");
|
|
61
|
+
line = line.replace(/`([^`]+)`/g, (_, value) => color(value, ansi.yellow));
|
|
62
|
+
line = line.replace(/^(\s*)[-*+]\s+/, "$1- ");
|
|
63
|
+
line = line.replace(/^\s*>\s?/, " ");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
rendered.push(line);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return rendered.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function printWrapped(prefix, text) {
|
|
73
|
+
const rendered = stripMarkdown(text);
|
|
74
|
+
const lines = rendered.split("\n");
|
|
75
|
+
const gutter = " ".repeat(useColor ? 9 : prefix.length);
|
|
76
|
+
console.log(`${prefix}${lines[0] || ""}`);
|
|
77
|
+
for (const line of lines.slice(1)) {
|
|
78
|
+
console.log(`${gutter}${line}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function printAgentMessage(text) {
|
|
83
|
+
printWrapped(`${label("aginti>", ansi.agentBg)} `, text);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function printSystemLine(text) {
|
|
87
|
+
if (!String(text || "").trim()) {
|
|
88
|
+
console.log("");
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
console.log(`${label("state", ansi.systemBg)} ${color(text, ansi.dim)}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function printHeading(text) {
|
|
95
|
+
console.log(color(stripMarkdown(text), ansi.bold, ansi.cyan));
|
|
96
|
+
}
|
|
97
|
+
|
|
9
98
|
function printHelp() {
|
|
10
|
-
|
|
99
|
+
printAgentMessage(
|
|
11
100
|
[
|
|
12
101
|
"Commands:",
|
|
13
102
|
" /help Show this help.",
|
|
@@ -27,23 +116,24 @@ function printHelp() {
|
|
|
27
116
|
" /exit Quit.",
|
|
28
117
|
"",
|
|
29
118
|
"Type a normal request to run the agent. Example: write a Python CLI app with tests",
|
|
119
|
+
"While a run is active, press Esc or Ctrl+C once to stop gracefully and print a resume command.",
|
|
30
120
|
].join("\n")
|
|
31
121
|
);
|
|
32
122
|
}
|
|
33
123
|
|
|
34
124
|
function printStatus(state) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (state.lastEvent)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
125
|
+
printSystemLine(`project=${process.cwd()}`);
|
|
126
|
+
printSystemLine(`cwd=${state.commandCwd || process.cwd()}`);
|
|
127
|
+
printSystemLine(`session=${state.sessionId || "new"}`);
|
|
128
|
+
printSystemLine(`status=${state.status || "idle"}${state.activeGoal ? ` workingOn=${state.activeGoal}` : ""}`);
|
|
129
|
+
if (state.lastEvent) printSystemLine(`last=${state.lastEvent}`);
|
|
130
|
+
printSystemLine(`provider=${state.provider || "auto"} routing=${state.routingMode} model=${state.model || "auto"}`);
|
|
131
|
+
printSystemLine(`profile=${state.taskProfile} maxSteps=${state.maxSteps}`);
|
|
132
|
+
printSystemLine(
|
|
43
133
|
`shell=${state.allowShellTool} files=${state.allowFileTools} sandbox=${state.sandboxMode} installs=${state.packageInstallPolicy}`
|
|
44
134
|
);
|
|
45
135
|
if (state.sandboxMode !== "host") {
|
|
46
|
-
|
|
136
|
+
printSystemLine(`dockerWorkspace=/workspace -> ${state.commandCwd || process.cwd()}`);
|
|
47
137
|
}
|
|
48
138
|
}
|
|
49
139
|
|
|
@@ -63,26 +153,52 @@ async function latestSession() {
|
|
|
63
153
|
|
|
64
154
|
function printStatusEvent(state, label, details = "") {
|
|
65
155
|
state.lastEvent = details ? `${label}: ${details}` : label;
|
|
66
|
-
|
|
156
|
+
printSystemLine(`status=${state.status || "running"} ${state.lastEvent}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function attachRunInterrupts(controller) {
|
|
160
|
+
if (!input.isTTY || typeof input.setRawMode !== "function") return () => {};
|
|
161
|
+
|
|
162
|
+
emitKeypressEvents(input);
|
|
163
|
+
const wasRaw = Boolean(input.isRaw);
|
|
164
|
+
input.setRawMode(true);
|
|
165
|
+
const handler = (_str, key = {}) => {
|
|
166
|
+
const isEscape = key.name === "escape";
|
|
167
|
+
const isCtrlC = key.ctrl && key.name === "c";
|
|
168
|
+
if (!isEscape && !isCtrlC) return;
|
|
169
|
+
if (controller.signal.aborted) return;
|
|
170
|
+
const reason = isEscape ? "escape" : "ctrl-c";
|
|
171
|
+
printSystemLine(`status=stopping reason=${reason}`);
|
|
172
|
+
controller.abort(new Error(`Interrupted by ${reason}.`));
|
|
173
|
+
};
|
|
174
|
+
input.on("keypress", handler);
|
|
175
|
+
return () => {
|
|
176
|
+
input.off("keypress", handler);
|
|
177
|
+
if (typeof input.setRawMode === "function") {
|
|
178
|
+
input.setRawMode(wasRaw);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
67
181
|
}
|
|
68
182
|
|
|
69
183
|
async function printResumeHint(state) {
|
|
70
184
|
const sessionId = state.sessionId || "";
|
|
71
185
|
console.log("");
|
|
72
186
|
if (sessionId) {
|
|
73
|
-
|
|
74
|
-
console.log(`Resume: aginti resume ${sessionId}`);
|
|
75
|
-
console.log(`One-shot: aginti resume ${sessionId} "continue"`);
|
|
187
|
+
printAgentMessage(["Interrupted. Session saved.", `Resume: aginti resume ${sessionId}`, `One-shot: aginti resume ${sessionId} "continue"`].join("\n"));
|
|
76
188
|
} else {
|
|
77
|
-
|
|
189
|
+
printAgentMessage("Interrupted. No active session yet.");
|
|
78
190
|
const sessions = await listProjectSessions(process.cwd(), 5).catch(() => []);
|
|
79
191
|
if (sessions.length > 0) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
192
|
+
printAgentMessage(
|
|
193
|
+
[
|
|
194
|
+
"Recent sessions:",
|
|
195
|
+
...sessions.map((session) => ` ${formatSessionLine(session)}`),
|
|
196
|
+
`Resume latest: aginti resume ${sessions[0].sessionId}`,
|
|
197
|
+
"List all: aginti sessions list",
|
|
198
|
+
].join("\n")
|
|
199
|
+
);
|
|
84
200
|
} else {
|
|
85
|
-
|
|
201
|
+
printAgentMessage("Restart: aginti");
|
|
86
202
|
}
|
|
87
203
|
}
|
|
88
204
|
}
|
|
@@ -122,71 +238,75 @@ async function handleCommand(line, state, packageDir) {
|
|
|
122
238
|
}
|
|
123
239
|
if (command === "new") {
|
|
124
240
|
state.sessionId = "";
|
|
125
|
-
|
|
241
|
+
printAgentMessage("Next message will start a new session.");
|
|
126
242
|
return true;
|
|
127
243
|
}
|
|
128
244
|
if (command === "resume") {
|
|
129
245
|
if (!value || value === "latest") {
|
|
130
246
|
const latest = await latestSession();
|
|
131
247
|
if (!latest) {
|
|
132
|
-
|
|
248
|
+
printAgentMessage("No project-local sessions found. Use /new or type a request to start one.");
|
|
133
249
|
} else {
|
|
134
250
|
state.sessionId = latest.sessionId;
|
|
135
|
-
|
|
251
|
+
printAgentMessage(`Resuming latest ${formatSessionLine(latest)}`);
|
|
136
252
|
}
|
|
137
253
|
} else {
|
|
138
254
|
state.sessionId = value;
|
|
139
|
-
|
|
255
|
+
printAgentMessage(`Resuming ${state.sessionId}`);
|
|
140
256
|
}
|
|
141
257
|
return true;
|
|
142
258
|
}
|
|
143
259
|
if (command === "sessions") {
|
|
144
260
|
const sessions = await listProjectSessions(process.cwd(), 20);
|
|
145
|
-
if (sessions.length === 0)
|
|
261
|
+
if (sessions.length === 0) printAgentMessage("No project-local sessions found.");
|
|
146
262
|
else {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
263
|
+
printAgentMessage(
|
|
264
|
+
sessions
|
|
265
|
+
.map((session) => {
|
|
266
|
+
const goal = session.goal ? ` ${session.goal.slice(0, 80)}` : "";
|
|
267
|
+
return `${session.sessionId} ${session.provider}/${session.model} ${session.updatedAt}${goal}`;
|
|
268
|
+
})
|
|
269
|
+
.join("\n")
|
|
270
|
+
);
|
|
151
271
|
}
|
|
152
272
|
return true;
|
|
153
273
|
}
|
|
154
274
|
if (command === "profile") {
|
|
155
275
|
state.taskProfile = normalizeTaskProfile(value || "auto");
|
|
156
|
-
|
|
276
|
+
printSystemLine(`profile=${state.taskProfile}`);
|
|
157
277
|
return true;
|
|
158
278
|
}
|
|
159
279
|
if (command === "routing") {
|
|
160
280
|
state.routingMode = value || "smart";
|
|
161
|
-
|
|
281
|
+
printSystemLine(`routing=${state.routingMode}`);
|
|
162
282
|
return true;
|
|
163
283
|
}
|
|
164
284
|
if (command === "provider") {
|
|
165
285
|
state.provider = value === "auto" ? "" : value;
|
|
166
|
-
|
|
286
|
+
printSystemLine(`provider=${state.provider || "auto"}`);
|
|
167
287
|
return true;
|
|
168
288
|
}
|
|
169
289
|
if (command === "model") {
|
|
170
290
|
state.model = value === "auto" ? "" : value;
|
|
171
|
-
|
|
291
|
+
printSystemLine(`model=${state.model || "auto"}`);
|
|
172
292
|
return true;
|
|
173
293
|
}
|
|
174
294
|
if (command === "installs") {
|
|
175
295
|
state.packageInstallPolicy = normalizePackageInstallPolicy(value || "prompt");
|
|
176
|
-
|
|
296
|
+
printSystemLine(`installs=${state.packageInstallPolicy}`);
|
|
177
297
|
return true;
|
|
178
298
|
}
|
|
179
299
|
if (command === "docker") {
|
|
180
300
|
if (value === "on") {
|
|
181
301
|
state.sandboxMode = "docker-workspace";
|
|
182
302
|
state.packageInstallPolicy = "allow";
|
|
183
|
-
|
|
303
|
+
printSystemLine(`docker=on /workspace -> ${state.commandCwd || process.cwd()} installs=allow`);
|
|
184
304
|
} else if (value === "off") {
|
|
185
305
|
state.sandboxMode = "host";
|
|
186
306
|
state.packageInstallPolicy = "prompt";
|
|
187
|
-
|
|
307
|
+
printSystemLine("docker=off sandbox=host installs=prompt");
|
|
188
308
|
} else {
|
|
189
|
-
|
|
309
|
+
printAgentMessage("Usage: /docker on OR /docker off");
|
|
190
310
|
}
|
|
191
311
|
return true;
|
|
192
312
|
}
|
|
@@ -196,36 +316,37 @@ async function handleCommand(line, state, packageDir) {
|
|
|
196
316
|
state.sandboxMode = "docker-workspace";
|
|
197
317
|
state.packageInstallPolicy = "allow";
|
|
198
318
|
state.maxSteps = Math.max(state.maxSteps, 30);
|
|
199
|
-
|
|
319
|
+
printSystemLine("latex=on profile=latex sandbox=docker-workspace installs=allow maxSteps=30");
|
|
200
320
|
} else if (value === "off") {
|
|
201
321
|
state.taskProfile = "auto";
|
|
202
|
-
|
|
322
|
+
printSystemLine("latex=off profile=auto");
|
|
203
323
|
} else {
|
|
204
|
-
|
|
324
|
+
printAgentMessage("Usage: /latex on OR /latex off");
|
|
205
325
|
}
|
|
206
326
|
return true;
|
|
207
327
|
}
|
|
208
328
|
if (command === "cwd") {
|
|
209
329
|
state.commandCwd = value || process.cwd();
|
|
210
|
-
|
|
330
|
+
printSystemLine(`cwd=${state.commandCwd}`);
|
|
211
331
|
return true;
|
|
212
332
|
}
|
|
213
333
|
if (command === "init") {
|
|
214
334
|
const result = await initProject(process.cwd());
|
|
215
|
-
|
|
335
|
+
printAgentMessage(`initialized project=${result.projectRoot}`);
|
|
216
336
|
return true;
|
|
217
337
|
}
|
|
218
338
|
if (command === "web") {
|
|
219
339
|
const port = value || "3220";
|
|
220
|
-
|
|
340
|
+
printAgentMessage(`Run in another terminal: aginti web --port ${port}`);
|
|
221
341
|
return true;
|
|
222
342
|
}
|
|
223
343
|
|
|
224
|
-
|
|
344
|
+
printAgentMessage(`Unknown command: /${command}. Use /help.`);
|
|
225
345
|
return true;
|
|
226
346
|
}
|
|
227
347
|
|
|
228
348
|
async function runPrompt(prompt, state, packageDir) {
|
|
349
|
+
const controller = new AbortController();
|
|
229
350
|
const config = loadConfig(
|
|
230
351
|
{
|
|
231
352
|
provider: state.provider,
|
|
@@ -252,51 +373,76 @@ async function runPrompt(prompt, state, packageDir) {
|
|
|
252
373
|
state.status = "running";
|
|
253
374
|
state.activeGoal = prompt.replace(/\s+/g, " ").slice(0, 120);
|
|
254
375
|
state.lastEvent = "";
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
|
|
376
|
+
printSystemLine(`session=${state.sessionId}`);
|
|
377
|
+
printSystemLine(`status=running workingOn=${state.activeGoal}`);
|
|
378
|
+
|
|
379
|
+
const detachInterrupts = attachRunInterrupts(controller);
|
|
380
|
+
let result;
|
|
381
|
+
try {
|
|
382
|
+
result = await runAgent({
|
|
383
|
+
...config,
|
|
384
|
+
abortSignal: controller.signal,
|
|
385
|
+
onConsole: (text, options = {}) => {
|
|
386
|
+
if (options.kind === "assistant") {
|
|
387
|
+
printAgentMessage(text);
|
|
388
|
+
} else if (options.kind === "plan") {
|
|
389
|
+
printWrapped(`${label("plan", ansi.systemBg)} `, text);
|
|
390
|
+
} else if (options.kind === "heading") {
|
|
391
|
+
printHeading(text);
|
|
392
|
+
} else if (options.error) {
|
|
393
|
+
console.error(`${label("error", ansi.systemBg)} ${stripMarkdown(text)}`);
|
|
394
|
+
} else {
|
|
395
|
+
printSystemLine(text);
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
onEvent: (type, data = {}) => {
|
|
399
|
+
if (type === "plan.created") {
|
|
400
|
+
printStatusEvent(state, "planned");
|
|
401
|
+
} else if (type === "tool.started") {
|
|
402
|
+
printStatusEvent(state, "tool", data.toolName || "unknown");
|
|
403
|
+
} else if (type === "tool.completed") {
|
|
404
|
+
printStatusEvent(state, "tool_done", data.toolName || "unknown");
|
|
405
|
+
} else if (type === "tool.blocked") {
|
|
406
|
+
printStatusEvent(state, "tool_blocked", data.toolName || data.reason || "unknown");
|
|
407
|
+
} else if (type === "loop.guard") {
|
|
408
|
+
printStatusEvent(state, "loop_guard", data.toolName || "");
|
|
409
|
+
} else if (type === "conversation.queued_input_applied") {
|
|
410
|
+
printStatusEvent(state, "queued_input_applied");
|
|
411
|
+
} else if (type === "session.finished") {
|
|
412
|
+
printStatusEvent(state, "finished");
|
|
413
|
+
} else if (type === "session.stopped") {
|
|
414
|
+
printStatusEvent(state, "stopped", data.reason || "");
|
|
415
|
+
} else if (type === "model.responded") {
|
|
416
|
+
printStatusEvent(state, "model_responded", data.content ? data.content.slice(0, 80).replace(/\s+/g, " ") : "");
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
} finally {
|
|
421
|
+
detachInterrupts();
|
|
422
|
+
}
|
|
280
423
|
state.sessionId = result.sessionId || state.sessionId;
|
|
281
424
|
state.status = result.stopped ? "stopped" : "idle";
|
|
282
425
|
state.activeGoal = "";
|
|
283
|
-
|
|
426
|
+
printSystemLine(`status=${state.status} session=${state.sessionId}`);
|
|
427
|
+
if (result.stopped && result.reason === "user_interrupt") {
|
|
428
|
+
await printResumeHint(state);
|
|
429
|
+
}
|
|
284
430
|
}
|
|
285
431
|
|
|
286
432
|
export async function startInteractiveCli(args = {}, { packageDir, packageVersion } = {}) {
|
|
287
433
|
const state = createState(args);
|
|
288
434
|
const rl = readline.createInterface({ input, output, terminal: Boolean(input.isTTY && output.isTTY) });
|
|
289
435
|
|
|
290
|
-
console.log(`AgInTiFlow ${packageVersion || ""}
|
|
291
|
-
|
|
292
|
-
|
|
436
|
+
console.log(color(` AgInTiFlow ${packageVersion || ""} `, ansi.agentBg, ansi.bold).trimEnd());
|
|
437
|
+
printSystemLine(`Project: ${process.cwd()}`);
|
|
438
|
+
printAgentMessage("Interactive agent chat. Type /help for commands, /exit to quit.");
|
|
293
439
|
printStatus(state);
|
|
294
440
|
|
|
295
441
|
try {
|
|
296
442
|
while (true) {
|
|
297
443
|
let answer = "";
|
|
298
444
|
try {
|
|
299
|
-
answer = await rl.question(
|
|
445
|
+
answer = await rl.question(userPrompt());
|
|
300
446
|
} catch (error) {
|
|
301
447
|
if (error?.code === "ERR_USE_AFTER_CLOSE") break;
|
|
302
448
|
if (isAbortError(error)) {
|
|
@@ -320,7 +466,7 @@ export async function startInteractiveCli(args = {}, { packageDir, packageVersio
|
|
|
320
466
|
await printResumeHint(state);
|
|
321
467
|
break;
|
|
322
468
|
}
|
|
323
|
-
console.error(
|
|
469
|
+
console.error(`${label("error", ansi.systemBg)} ${error.message}`);
|
|
324
470
|
}
|
|
325
471
|
}
|
|
326
472
|
} finally {
|
package/src/model-client.js
CHANGED
|
@@ -53,6 +53,10 @@ function prepareMessages(config, messages) {
|
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
function requestOptions(config) {
|
|
57
|
+
return config.abortSignal ? { signal: config.abortSignal } : undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
56
60
|
function mockCommandForGoal(goal = "") {
|
|
57
61
|
const text = String(goal).toLowerCase();
|
|
58
62
|
if (/\blist\b|folder contents|directory contents|files?/.test(text)) return "ls -la";
|
|
@@ -94,6 +98,18 @@ function mockWorkspaceToolForGoal(goal = "") {
|
|
|
94
98
|
return null;
|
|
95
99
|
}
|
|
96
100
|
|
|
101
|
+
function mockPreviewToolForGoal(goal = "") {
|
|
102
|
+
const text = String(goal).toLowerCase();
|
|
103
|
+
if (!/(open|preview|view|browser|website|web\s*site)/.test(text)) return null;
|
|
104
|
+
const targetPath = mockPathForGoal(goal);
|
|
105
|
+
if (targetPath === "mock-output.txt") return null;
|
|
106
|
+
if (!/\.(html|htm|svg|png|jpe?g|webp|pdf|txt|md)$/i.test(targetPath)) return null;
|
|
107
|
+
return mockToolCall("preview_workspace", {
|
|
108
|
+
path: targetPath,
|
|
109
|
+
port: 8765,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
97
113
|
function mockCanvasToolForGoal(goal = "") {
|
|
98
114
|
const text = String(goal).toLowerCase();
|
|
99
115
|
if (!/canvas|artifact|image|figure|visual|preview|render/.test(text)) return null;
|
|
@@ -130,10 +146,11 @@ export async function createPlan(client, config, state) {
|
|
|
130
146
|
].join("\n");
|
|
131
147
|
}
|
|
132
148
|
|
|
133
|
-
const response = await client.chat.completions.create(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
149
|
+
const response = await client.chat.completions.create(
|
|
150
|
+
{
|
|
151
|
+
model: config.model,
|
|
152
|
+
temperature: 0,
|
|
153
|
+
messages: [
|
|
137
154
|
{
|
|
138
155
|
role: "system",
|
|
139
156
|
content:
|
|
@@ -149,7 +166,7 @@ export async function createPlan(client, config, state) {
|
|
|
149
166
|
? `Shell tool is enabled in ${config.commandCwd}. In Docker, this path is mounted as /workspace with persistent /aginti-env and /aginti-cache mounts. Use relative paths or /workspace paths, not absolute host temp paths. Sandbox mode: ${config.sandboxMode}. Package install policy: ${config.packageInstallPolicy}. For npm/pip/conda/venv setup, explain the need and wait for approval unless policy is allow.`
|
|
150
167
|
: "",
|
|
151
168
|
config.allowFileTools
|
|
152
|
-
? `Workspace file tools are enabled in ${config.commandCwd}: list_files, read_file, search_files, write_file, apply_patch. Keep all paths workspace-relative, for example plot_fx.svg or docs/report.tex, and avoid secrets.`
|
|
169
|
+
? `Workspace file tools are enabled in ${config.commandCwd}: list_files, read_file, search_files, write_file, apply_patch, open_workspace_file, preview_workspace. 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.`
|
|
153
170
|
: "",
|
|
154
171
|
config.allowWrapperTools
|
|
155
172
|
? `Agent wrappers are enabled. Use the selected wrapper only: ${normalizeWrapperName(config.preferredWrapper)}. Status: ${wrapperStatusText()}.`
|
|
@@ -160,6 +177,7 @@ export async function createPlan(client, config, state) {
|
|
|
160
177
|
"Use the canvas tunnel for outputs the user would likely want to inspect visually, such as figures, PDFs, screenshots, images, important markdown, or generated files.",
|
|
161
178
|
"For environment or system-maintenance work, prefer project-local dry-run plans/scripts unless the configured policy explicitly allows stronger actions.",
|
|
162
179
|
"Docker language/toolchain installs should prefer /aginti-env or project files so they persist across runs; apt/apk changes are ephemeral unless the image is rebuilt.",
|
|
180
|
+
"If a localhost/browser preview fails, do not loop on the same URL. Switch to open_workspace_file or preview_workspace, or finish with the local path and honest limitation.",
|
|
163
181
|
"If the run is close to the max-step limit, finish with the best complete artifact and honest limitations instead of starting a new approach.",
|
|
164
182
|
"Plan for a complete result, not endless exploration; finish once the request is satisfied and checks have passed or been honestly skipped.",
|
|
165
183
|
"Return a numbered plan only.",
|
|
@@ -167,8 +185,10 @@ export async function createPlan(client, config, state) {
|
|
|
167
185
|
.filter(Boolean)
|
|
168
186
|
.join("\n"),
|
|
169
187
|
},
|
|
170
|
-
|
|
171
|
-
|
|
188
|
+
],
|
|
189
|
+
},
|
|
190
|
+
requestOptions(config)
|
|
191
|
+
);
|
|
172
192
|
|
|
173
193
|
return response.choices[0]?.message?.content?.trim() || "1. Inspect the page.\n2. Use the smallest safe action.\n3. Finish with a concise answer.";
|
|
174
194
|
}
|
|
@@ -179,7 +199,8 @@ export async function requestNextStep(client, config, messages) {
|
|
|
179
199
|
type: "function",
|
|
180
200
|
function: {
|
|
181
201
|
name: "open_url",
|
|
182
|
-
description:
|
|
202
|
+
description:
|
|
203
|
+
"Open a remote absolute http or https URL in the browser. Do not use this for generated local workspace files or localhost preview loops; use open_workspace_file or preview_workspace when available.",
|
|
183
204
|
parameters: {
|
|
184
205
|
type: "object",
|
|
185
206
|
properties: {
|
|
@@ -296,6 +317,45 @@ export async function requestNextStep(client, config, messages) {
|
|
|
296
317
|
},
|
|
297
318
|
];
|
|
298
319
|
|
|
320
|
+
if (config.allowFileTools) {
|
|
321
|
+
tools.splice(
|
|
322
|
+
0,
|
|
323
|
+
0,
|
|
324
|
+
{
|
|
325
|
+
type: "function",
|
|
326
|
+
function: {
|
|
327
|
+
name: "open_workspace_file",
|
|
328
|
+
description:
|
|
329
|
+
"Open a workspace-local file directly in the browser, such as generated HTML, SVG, PNG, PDF, or text. Prefer this over starting a localhost server when the user asks to open a generated local page.",
|
|
330
|
+
parameters: {
|
|
331
|
+
type: "object",
|
|
332
|
+
properties: {
|
|
333
|
+
path: { type: "string", description: "Workspace-relative file path to open." },
|
|
334
|
+
},
|
|
335
|
+
required: ["path"],
|
|
336
|
+
additionalProperties: false,
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
type: "function",
|
|
342
|
+
function: {
|
|
343
|
+
name: "preview_workspace",
|
|
344
|
+
description:
|
|
345
|
+
"Start a persistent host-side static preview server for a workspace file or directory, automatically choosing a free port, then open it in the browser. Use this for generated local websites instead of running python -m http.server inside Docker or repeatedly opening localhost URLs.",
|
|
346
|
+
parameters: {
|
|
347
|
+
type: "object",
|
|
348
|
+
properties: {
|
|
349
|
+
path: { type: "string", description: "Workspace-relative file or directory to preview. Defaults to ." },
|
|
350
|
+
port: { type: "integer", description: "Preferred localhost port. The runtime chooses another if busy." },
|
|
351
|
+
},
|
|
352
|
+
additionalProperties: false,
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
299
359
|
if (config.allowShellTool) {
|
|
300
360
|
tools.splice(-1, 0, {
|
|
301
361
|
type: "function",
|
|
@@ -495,6 +555,13 @@ export async function requestNextStep(client, config, messages) {
|
|
|
495
555
|
]);
|
|
496
556
|
}
|
|
497
557
|
|
|
558
|
+
if (config.allowFileTools) {
|
|
559
|
+
const previewTool = mockPreviewToolForGoal(config.goal);
|
|
560
|
+
if (previewTool) {
|
|
561
|
+
return mockChatResponse("Mock mode will exercise the workspace preview tool.", [previewTool]);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
498
565
|
const canvasTool = mockCanvasToolForGoal(config.goal);
|
|
499
566
|
if (canvasTool) {
|
|
500
567
|
return mockChatResponse("Mock mode will publish a canvas artifact for the UI tunnel.", [canvasTool]);
|
|
@@ -520,12 +587,15 @@ export async function requestNextStep(client, config, messages) {
|
|
|
520
587
|
]);
|
|
521
588
|
}
|
|
522
589
|
|
|
523
|
-
return client.chat.completions.create(
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
590
|
+
return client.chat.completions.create(
|
|
591
|
+
{
|
|
592
|
+
model: config.model,
|
|
593
|
+
temperature: 0,
|
|
594
|
+
tool_choice: "auto",
|
|
595
|
+
parallel_tool_calls: false,
|
|
596
|
+
messages: prepareMessages(config, messages),
|
|
597
|
+
tools,
|
|
598
|
+
},
|
|
599
|
+
requestOptions(config)
|
|
600
|
+
);
|
|
531
601
|
}
|