@lazyingart/agintiflow 0.8.8 → 0.8.9
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 -1
- package/package.json +1 -1
- package/src/agent-runner.js +7 -0
- package/src/interactive-cli.js +73 -4
- package/src/model-client.js +3 -1
- package/src/snapshot.js +62 -46
- package/src/task-profiles.js +4 -4
package/README.md
CHANGED
|
@@ -50,6 +50,7 @@ On first interactive use, if no DeepSeek key is detected, `aginti` asks you to p
|
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
52
|
aginti login deepseek
|
|
53
|
+
# inside chat, use /login or /auth
|
|
53
54
|
# or non-interactively:
|
|
54
55
|
printf '%s' "$DEEPSEEK_API_KEY" | aginti keys set deepseek --stdin
|
|
55
56
|
```
|
|
@@ -62,7 +63,7 @@ aginti
|
|
|
62
63
|
aginti chat
|
|
63
64
|
```
|
|
64
65
|
|
|
65
|
-
Inside chat, type normal requests such as `write a small Python CLI app with tests`. The default is Docker workspace mode with approved package installs, so coding, plotting, and LaTeX tasks can set up project-local tools without touching the host. Use `/help` for commands, `/latex on` for PDF work, `/docker off` only when you intentionally want host mode, `/sessions` to list project runs, and `/resume latest` or `/resume <session-id>` to continue work. Esc or Ctrl+C stops the active run cleanly and prints the resume command.
|
|
66
|
+
Inside chat, type normal requests such as `write a small Python CLI app with tests`. The default is Docker workspace mode with approved package installs, so coding, plotting, and LaTeX tasks can set up project-local tools without touching the host. Use `/help` for commands, `/login` or `/auth` to paste a provider key, `/latex on` for PDF work, `/docker off` only when you intentionally want host mode, `/sessions` to list project runs, and `/resume latest` or `/resume <session-id>` to continue work. Type `/` then Tab for command completion. Esc or Ctrl+C stops the active run cleanly and prints the resume command.
|
|
66
67
|
|
|
67
68
|
Launch the local web UI from an installed package:
|
|
68
69
|
|
package/package.json
CHANGED
package/src/agent-runner.js
CHANGED
|
@@ -258,6 +258,7 @@ function createInitialState(config, sessionId) {
|
|
|
258
258
|
content: [
|
|
259
259
|
"You are a careful browser and shell agent with a small tool surface.",
|
|
260
260
|
"Use only the provided tools.",
|
|
261
|
+
"The execution plan is not the final answer. After planning, actively use tools until the requested task is complete or genuinely blocked.",
|
|
261
262
|
"If the shell tool can satisfy a local task, prefer it before opening a browser.",
|
|
262
263
|
"Do not open a browser page just because a start URL exists. Treat it as a suggestion only.",
|
|
263
264
|
"Only reference element ids from the latest browser snapshot.",
|
|
@@ -279,6 +280,9 @@ function createInitialState(config, sessionId) {
|
|
|
279
280
|
"A frontend canvas/artifacts tunnel exists. Use send_to_canvas when important markdown, diffs, screenshots, images, or workspace files should be highlighted in the UI. It is optional and ordinary final text can still go directly to finish.",
|
|
280
281
|
"For visual-output requests such as draw, plot, graph, chart, diagram, figure, image, or visualization, proactively publish a canvas artifact even when the user does not mention canvas. If workspace file tools are enabled, prefer creating a small SVG or markdown artifact and call send_to_canvas with selected=true.",
|
|
281
282
|
"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.",
|
|
283
|
+
"For large projects, decompose into useful files and milestones, implement a coherent minimal version first, then iterate with checks rather than only describing what you would do.",
|
|
284
|
+
"For website/app/code/LaTeX/Python/C/shell tasks, create or edit real workspace files, run available build/compile/test commands, and surface artifacts through the canvas when useful.",
|
|
285
|
+
"For research or web-search tasks, use browser tools or safe shell network tools when the current policy allows; cite or save useful sources in workspace notes when the task needs traceability.",
|
|
282
286
|
"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.",
|
|
283
287
|
"For environment or system-maintenance work, use the configured sandbox and package policy; Docker workspace mode is the preferred place for installs and toolchain setup.",
|
|
284
288
|
"If the user asks to open a generated local website or file, use open_workspace_file for a file or preview_workspace for a static site. Do not keep retrying the same localhost URL when a preview fails.",
|
|
@@ -309,6 +313,7 @@ function createInitialState(config, sessionId) {
|
|
|
309
313
|
"Canvas/artifacts tunnel: available through send_to_canvas for optional frontend rendering.",
|
|
310
314
|
"Visual-output requests should produce a canvas artifact without requiring the user to ask for canvas explicitly.",
|
|
311
315
|
"Use file, shell, browser, canvas, and wrapper tools when they are useful; choose the workflow from the user's request.",
|
|
316
|
+
"Do not stop at a plan when tools can accomplish the request. Continue through implementation, checks, artifact selection, and finish.",
|
|
312
317
|
"Use the configured sandbox and package policy for environment or system-maintenance work.",
|
|
313
318
|
]
|
|
314
319
|
.filter(Boolean)
|
|
@@ -1078,6 +1083,7 @@ export async function runAgent(config) {
|
|
|
1078
1083
|
url: snapshot.url,
|
|
1079
1084
|
title: snapshot.title,
|
|
1080
1085
|
screenshotPath: snapshot.screenshotPath,
|
|
1086
|
+
screenshotWarning: snapshot.screenshotWarning || "",
|
|
1081
1087
|
snapshotPath: snapshot.snapshotPath,
|
|
1082
1088
|
});
|
|
1083
1089
|
observers.event("snapshot.captured", {
|
|
@@ -1085,6 +1091,7 @@ export async function runAgent(config) {
|
|
|
1085
1091
|
url: snapshot.url,
|
|
1086
1092
|
title: snapshot.title,
|
|
1087
1093
|
screenshotPath: snapshot.screenshotPath,
|
|
1094
|
+
screenshotWarning: snapshot.screenshotWarning || "",
|
|
1088
1095
|
});
|
|
1089
1096
|
|
|
1090
1097
|
state.messages.push({
|
package/src/interactive-cli.js
CHANGED
|
@@ -3,10 +3,10 @@ import { emitKeypressEvents } from "node:readline";
|
|
|
3
3
|
import { stdin as input, stdout as output } from "node:process";
|
|
4
4
|
import { runAgent } from "./agent-runner.js";
|
|
5
5
|
import { loadConfig } from "./config.js";
|
|
6
|
-
import { initProject, listProjectSessions } from "./project.js";
|
|
6
|
+
import { initProject, listProjectSessions, providerKeyStatus, setProviderKey } from "./project.js";
|
|
7
7
|
import { normalizePackageInstallPolicy, normalizeSandboxMode } from "./command-policy.js";
|
|
8
8
|
import { normalizeTaskProfile } from "./task-profiles.js";
|
|
9
|
-
import { promptAndSaveDeepSeekKey, shouldPromptForDeepSeek } from "./auth-onboarding.js";
|
|
9
|
+
import { promptAndSaveDeepSeekKey, promptHidden, shouldPromptForDeepSeek } from "./auth-onboarding.js";
|
|
10
10
|
|
|
11
11
|
const useColor = Boolean(input.isTTY && output.isTTY && process.env.AGINTIFLOW_NO_COLOR !== "1");
|
|
12
12
|
const ansi = {
|
|
@@ -25,6 +25,26 @@ const ansi = {
|
|
|
25
25
|
systemBg: "\x1b[48;5;236m\x1b[38;5;245m",
|
|
26
26
|
};
|
|
27
27
|
const brandPalette = ["\x1b[38;5;45m", "\x1b[38;5;81m", "\x1b[38;5;86m", "\x1b[38;5;118m", "\x1b[38;5;226m"];
|
|
28
|
+
const SLASH_COMMANDS = [
|
|
29
|
+
"/help",
|
|
30
|
+
"/status",
|
|
31
|
+
"/login",
|
|
32
|
+
"/auth",
|
|
33
|
+
"/new",
|
|
34
|
+
"/resume",
|
|
35
|
+
"/sessions",
|
|
36
|
+
"/profile",
|
|
37
|
+
"/routing",
|
|
38
|
+
"/provider",
|
|
39
|
+
"/model",
|
|
40
|
+
"/docker",
|
|
41
|
+
"/latex",
|
|
42
|
+
"/installs",
|
|
43
|
+
"/cwd",
|
|
44
|
+
"/init",
|
|
45
|
+
"/web",
|
|
46
|
+
"/exit",
|
|
47
|
+
];
|
|
28
48
|
|
|
29
49
|
function color(value, ...codes) {
|
|
30
50
|
if (!useColor || codes.length === 0) return String(value);
|
|
@@ -43,6 +63,13 @@ function userPrompt() {
|
|
|
43
63
|
return `\n${label("user>", ansi.userBg)} `;
|
|
44
64
|
}
|
|
45
65
|
|
|
66
|
+
function commandCompleter(line = "") {
|
|
67
|
+
const trimmed = String(line || "");
|
|
68
|
+
if (!trimmed.startsWith("/")) return [[], trimmed];
|
|
69
|
+
const hits = SLASH_COMMANDS.filter((command) => command.startsWith(trimmed));
|
|
70
|
+
return [hits.length > 0 ? hits : SLASH_COMMANDS, trimmed];
|
|
71
|
+
}
|
|
72
|
+
|
|
46
73
|
function stripMarkdown(text) {
|
|
47
74
|
const lines = String(text || "").split(/\r?\n/);
|
|
48
75
|
let inFence = false;
|
|
@@ -147,6 +174,8 @@ function printHelp() {
|
|
|
147
174
|
"Commands:",
|
|
148
175
|
" /help Show this help.",
|
|
149
176
|
" /status Show active route, workspace, sandbox, and session.",
|
|
177
|
+
" /login [deepseek|openai] Paste and save a project-local API key.",
|
|
178
|
+
" /auth [deepseek|openai] Alias for /login.",
|
|
150
179
|
" /new Start a fresh session on the next message.",
|
|
151
180
|
" /resume <session-id> Continue a saved session.",
|
|
152
181
|
" /sessions List recent sessions in this project.",
|
|
@@ -162,6 +191,7 @@ function printHelp() {
|
|
|
162
191
|
" /exit Quit.",
|
|
163
192
|
"",
|
|
164
193
|
"Type a normal request to run the agent. Example: write a Python CLI app with tests",
|
|
194
|
+
"Type / then Tab to autocomplete commands.",
|
|
165
195
|
"While a run is active, press Esc or Ctrl+C once to stop gracefully and print a resume command.",
|
|
166
196
|
].join("\n")
|
|
167
197
|
);
|
|
@@ -292,6 +322,28 @@ async function maybeOnboardDeepSeekKey(state) {
|
|
|
292
322
|
printAgentMessage("No key saved. Continuing in local mock mode. Use `/provider deepseek` after running `aginti login deepseek`.");
|
|
293
323
|
}
|
|
294
324
|
|
|
325
|
+
async function promptAndSaveProviderKey(provider = "deepseek", state = null) {
|
|
326
|
+
const normalized = ["openai", "deepseek"].includes(String(provider || "").toLowerCase())
|
|
327
|
+
? String(provider || "").toLowerCase()
|
|
328
|
+
: "deepseek";
|
|
329
|
+
const labelText = normalized === "openai" ? "OpenAI" : "DeepSeek";
|
|
330
|
+
const key = await promptHidden(`${labelText} API key/token (paste, Enter to save): `);
|
|
331
|
+
if (!key) {
|
|
332
|
+
printAgentMessage("No key saved.");
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const result = await setProviderKey(process.cwd(), normalized, key);
|
|
337
|
+
if (state) {
|
|
338
|
+
state.provider = normalized;
|
|
339
|
+
if (state.routingMode === "manual" && state.model === "mock-agent") {
|
|
340
|
+
state.routingMode = "smart";
|
|
341
|
+
state.model = "";
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
printAgentMessage(`Saved ${result.keyName} to project-local ignored env. Raw key was not printed.`);
|
|
345
|
+
}
|
|
346
|
+
|
|
295
347
|
async function handleCommand(line, state, packageDir) {
|
|
296
348
|
const [command, ...rest] = line.slice(1).trim().split(/\s+/);
|
|
297
349
|
const value = rest.join(" ").trim();
|
|
@@ -303,6 +355,12 @@ async function handleCommand(line, state, packageDir) {
|
|
|
303
355
|
if (command === "exit" || command === "quit" || command === "q") return false;
|
|
304
356
|
if (command === "status") {
|
|
305
357
|
printStatus(state);
|
|
358
|
+
const keys = providerKeyStatus(process.cwd());
|
|
359
|
+
printSystemLine(`keys deepseek=${keys.deepseek ? "available" : "missing"} openai=${keys.openai ? "available" : "missing"}`);
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
if (command === "login" || command === "auth") {
|
|
363
|
+
await promptAndSaveProviderKey(value || "deepseek", state);
|
|
306
364
|
return true;
|
|
307
365
|
}
|
|
308
366
|
if (command === "new") {
|
|
@@ -410,7 +468,13 @@ async function handleCommand(line, state, packageDir) {
|
|
|
410
468
|
return true;
|
|
411
469
|
}
|
|
412
470
|
|
|
413
|
-
|
|
471
|
+
const typed = `/${command}`;
|
|
472
|
+
const suggestions = SLASH_COMMANDS.filter((candidate) => candidate.startsWith(typed));
|
|
473
|
+
printAgentMessage(
|
|
474
|
+
suggestions.length > 0
|
|
475
|
+
? `Unknown command: /${command}. Did you mean:\n${suggestions.map((item) => ` ${item}`).join("\n")}`
|
|
476
|
+
: `Unknown command: /${command}. Use /help.`
|
|
477
|
+
);
|
|
414
478
|
return true;
|
|
415
479
|
}
|
|
416
480
|
|
|
@@ -500,7 +564,12 @@ async function runPrompt(prompt, state, packageDir) {
|
|
|
500
564
|
|
|
501
565
|
export async function startInteractiveCli(args = {}, { packageDir, packageVersion } = {}) {
|
|
502
566
|
const state = createState(args);
|
|
503
|
-
const rl = readline.createInterface({
|
|
567
|
+
const rl = readline.createInterface({
|
|
568
|
+
input,
|
|
569
|
+
output,
|
|
570
|
+
terminal: Boolean(input.isTTY && output.isTTY),
|
|
571
|
+
completer: commandCompleter,
|
|
572
|
+
});
|
|
504
573
|
|
|
505
574
|
await renderLaunchHeader(packageVersion);
|
|
506
575
|
printSystemLine(`Project: ${process.cwd()}`);
|
package/src/model-client.js
CHANGED
|
@@ -154,7 +154,7 @@ export async function createPlan(client, config, state) {
|
|
|
154
154
|
{
|
|
155
155
|
role: "system",
|
|
156
156
|
content:
|
|
157
|
-
"You are planning a browser
|
|
157
|
+
"You are planning a browser, shell, workspace, and coding-agent task. The plan is only a launchpad: after planning, the runtime will continue with tools until the task is complete or genuinely blocked. Prefer real workspace edits/checks over advice-only answers. If a local shell command can satisfy the goal, prefer that before browser actions. Treat any suggested start URL as optional. Write a concise execution plan with 3 to 6 steps. Mention risks or blockers when relevant. Keep it short and practical.",
|
|
158
158
|
},
|
|
159
159
|
{
|
|
160
160
|
role: "user",
|
|
@@ -174,6 +174,8 @@ export async function createPlan(client, config, state) {
|
|
|
174
174
|
`Task profile: ${taskProfile.label}. ${taskProfile.prompt}`,
|
|
175
175
|
"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.",
|
|
176
176
|
"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.",
|
|
177
|
+
"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.",
|
|
178
|
+
"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.",
|
|
177
179
|
"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.",
|
|
178
180
|
"For environment or system-maintenance work, prefer project-local dry-run plans/scripts unless the configured policy explicitly allows stronger actions.",
|
|
179
181
|
"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.",
|
package/src/snapshot.js
CHANGED
|
@@ -1,55 +1,71 @@
|
|
|
1
1
|
export async function captureSnapshot(page, store, step) {
|
|
2
|
-
const snapshot = await page
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
2
|
+
const snapshot = await page
|
|
3
|
+
.evaluate(() => {
|
|
4
|
+
const isVisible = (el) => {
|
|
5
|
+
const rect = el.getBoundingClientRect();
|
|
6
|
+
const style = window.getComputedStyle(el);
|
|
7
|
+
return rect.width > 0 && rect.height > 0 && style.display !== "none" && style.visibility !== "hidden";
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
document.querySelectorAll("[data-agent-id]").forEach((el) => el.removeAttribute("data-agent-id"));
|
|
11
|
+
|
|
12
|
+
const candidates = Array.from(
|
|
13
|
+
document.querySelectorAll("a, button, input, textarea, select, [role='button'], [contenteditable='true']")
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const elements = [];
|
|
17
|
+
|
|
18
|
+
for (const el of candidates) {
|
|
19
|
+
if (!isVisible(el)) continue;
|
|
20
|
+
if (elements.length >= 50) break;
|
|
21
|
+
|
|
22
|
+
const id = String(elements.length + 1);
|
|
23
|
+
el.setAttribute("data-agent-id", id);
|
|
24
|
+
|
|
25
|
+
const tag = el.tagName.toLowerCase();
|
|
26
|
+
elements.push({
|
|
27
|
+
id,
|
|
28
|
+
tag,
|
|
29
|
+
role: el.getAttribute("role") || "",
|
|
30
|
+
text: (el.innerText || el.textContent || "").replace(/\s+/g, " ").trim().slice(0, 100),
|
|
31
|
+
ariaLabel: (el.getAttribute("aria-label") || "").trim().slice(0, 100),
|
|
32
|
+
placeholder: (el.getAttribute("placeholder") || "").trim().slice(0, 100),
|
|
33
|
+
href: tag === "a" ? (el.getAttribute("href") || "").trim() : "",
|
|
34
|
+
inputType: tag === "input" ? (el.getAttribute("type") || "text").trim() : "",
|
|
35
|
+
autocomplete: (el.getAttribute("autocomplete") || "").trim(),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
title: document.title,
|
|
41
|
+
url: window.location.href,
|
|
42
|
+
pageText: (document.body?.innerText || "").replace(/\s+/g, " ").trim().slice(0, 2500),
|
|
43
|
+
elements,
|
|
44
|
+
};
|
|
45
|
+
})
|
|
46
|
+
.catch((error) => ({
|
|
47
|
+
title: "Browser page open",
|
|
48
|
+
url: page.url(),
|
|
49
|
+
pageText: `DOM snapshot unavailable: ${error instanceof Error ? error.message : String(error)}`.slice(0, 2500),
|
|
50
|
+
elements: [],
|
|
51
|
+
snapshotWarning: "dom-unavailable",
|
|
52
|
+
}));
|
|
45
53
|
|
|
46
54
|
const screenshotPath = store.screenshotPath(step);
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
let savedScreenshotPath = screenshotPath;
|
|
56
|
+
let screenshotWarning = "";
|
|
57
|
+
try {
|
|
58
|
+
await page.screenshot({ path: screenshotPath, fullPage: true });
|
|
59
|
+
} catch (error) {
|
|
60
|
+
savedScreenshotPath = "";
|
|
61
|
+
screenshotWarning = `Screenshot unavailable: ${error instanceof Error ? error.message : String(error)}`.slice(0, 500);
|
|
62
|
+
}
|
|
63
|
+
const snapshotPath = await store.saveSnapshot(step, { ...snapshot, screenshotWarning });
|
|
49
64
|
|
|
50
65
|
return {
|
|
51
66
|
...snapshot,
|
|
52
|
-
screenshotPath,
|
|
67
|
+
screenshotPath: savedScreenshotPath,
|
|
68
|
+
screenshotWarning,
|
|
53
69
|
snapshotPath,
|
|
54
70
|
};
|
|
55
71
|
}
|
package/src/task-profiles.js
CHANGED
|
@@ -3,14 +3,14 @@ export const TASK_PROFILES = {
|
|
|
3
3
|
id: "auto",
|
|
4
4
|
label: "Auto",
|
|
5
5
|
prompt:
|
|
6
|
-
"Infer the task type from the user request. Prefer the smallest safe tool sequence, preserve workspace files, and summarize what changed.",
|
|
6
|
+
"Infer the task type from the user request. Prefer the smallest safe tool sequence that actually completes the work, preserve workspace files, run useful checks, and summarize what changed.",
|
|
7
7
|
tools: ["browser", "shell", "files", "canvas"],
|
|
8
8
|
},
|
|
9
9
|
code: {
|
|
10
10
|
id: "code",
|
|
11
11
|
label: "Code writing",
|
|
12
12
|
prompt:
|
|
13
|
-
"Act like a coding agent: understand the request, edit workspace files, run useful safe checks, and report changed files and residual risks.",
|
|
13
|
+
"Act like a coding agent: understand the request, edit workspace files, run useful safe checks, iterate on failures, and report changed files and residual risks.",
|
|
14
14
|
tools: ["files", "shell", "sandbox"],
|
|
15
15
|
},
|
|
16
16
|
writing: {
|
|
@@ -52,7 +52,7 @@ export const TASK_PROFILES = {
|
|
|
52
52
|
id: "website",
|
|
53
53
|
label: "Website testing",
|
|
54
54
|
prompt:
|
|
55
|
-
"For website
|
|
55
|
+
"For website/app tasks, create or inspect real site files, preview with workspace preview tools, add local checks when useful, and use the configured sandbox/package policy for dependencies.",
|
|
56
56
|
tools: ["files", "shell", "canvas", "sandbox"],
|
|
57
57
|
},
|
|
58
58
|
aaps: {
|
|
@@ -66,7 +66,7 @@ export const TASK_PROFILES = {
|
|
|
66
66
|
id: "latex",
|
|
67
67
|
label: "LaTeX",
|
|
68
68
|
prompt:
|
|
69
|
-
"For LaTeX/PDF tasks, create source and figures in a subfolder, compile when a TeX toolchain is available, and send the PDF through the canvas tunnel. In Docker, use /workspace for project outputs and the persistent Python/conda/tool cache under /aginti-env when setup is needed.",
|
|
69
|
+
"For LaTeX/PDF tasks, locate or create source and figures in a subfolder, compile when a TeX toolchain is available, run enough passes for references, and send the PDF through the canvas tunnel. In Docker, use /workspace for project outputs and the persistent Python/conda/tool cache under /aginti-env when setup is needed.",
|
|
70
70
|
tools: ["files", "shell", "canvas", "sandbox"],
|
|
71
71
|
},
|
|
72
72
|
maintenance: {
|