@lazyingart/agintiflow 0.1.0 → 0.2.0

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.
@@ -0,0 +1,401 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { redactSensitiveText } from "./redaction.js";
5
+ import { checkWorkspaceToolUse, resolveWorkspacePath } from "./workspace-tools.js";
6
+
7
+ const MAX_INLINE_TEXT_BYTES = 120_000;
8
+ const MAX_ARTIFACT_TEXT_BYTES = 520_000;
9
+ const MAX_ARTIFACT_IMAGE_BYTES = 4_000_000;
10
+ const IMAGE_MIME_BY_EXT = new Map([
11
+ [".png", "image/png"],
12
+ [".jpg", "image/jpeg"],
13
+ [".jpeg", "image/jpeg"],
14
+ [".gif", "image/gif"],
15
+ [".webp", "image/webp"],
16
+ [".svg", "image/svg+xml"],
17
+ ]);
18
+ const BINARY_RENDER_MIME_BY_EXT = new Map([[".pdf", "application/pdf"]]);
19
+
20
+ function normalizeDisplayPath(value) {
21
+ return String(value || "")
22
+ .split(path.sep)
23
+ .join("/");
24
+ }
25
+
26
+ function isInside(root, target) {
27
+ const relative = path.relative(root, target);
28
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
29
+ }
30
+
31
+ function stableId(...parts) {
32
+ return crypto.createHash("sha256").update(parts.map((part) => String(part ?? "")).join("\0")).digest("base64url");
33
+ }
34
+
35
+ function previewText(value, limit = 220) {
36
+ const text = redactSensitiveText(String(value || "")).replace(/\s+/g, " ").trim();
37
+ return text.length > limit ? `${text.slice(0, limit).trim()}...` : text;
38
+ }
39
+
40
+ function normalizeKind(kind, filePath = "") {
41
+ const candidate = String(kind || "").toLowerCase();
42
+ if (candidate === "file") {
43
+ if (IMAGE_MIME_BY_EXT.has(path.extname(String(filePath || "")).toLowerCase())) return "image";
44
+ if (BINARY_RENDER_MIME_BY_EXT.has(path.extname(String(filePath || "")).toLowerCase())) return "pdf";
45
+ if (String(filePath || "").toLowerCase().endsWith(".json")) return "json";
46
+ if (String(filePath || "").toLowerCase().endsWith(".md")) return "markdown";
47
+ return "file";
48
+ }
49
+ if (["image", "markdown", "text", "json", "diff", "pdf"].includes(candidate)) return candidate;
50
+ if (IMAGE_MIME_BY_EXT.has(path.extname(String(filePath || "")).toLowerCase())) return "image";
51
+ if (BINARY_RENDER_MIME_BY_EXT.has(path.extname(String(filePath || "")).toLowerCase())) return "pdf";
52
+ if (String(filePath || "").toLowerCase().endsWith(".json")) return "json";
53
+ if (String(filePath || "").toLowerCase().endsWith(".md")) return "markdown";
54
+ return "text";
55
+ }
56
+
57
+ function mimeForPath(filePath) {
58
+ const ext = path.extname(String(filePath || "")).toLowerCase();
59
+ return IMAGE_MIME_BY_EXT.get(ext) || BINARY_RENDER_MIME_BY_EXT.get(ext) || "text/plain; charset=utf-8";
60
+ }
61
+
62
+ function publicArtifact(item) {
63
+ return {
64
+ id: item.id,
65
+ sessionId: item.sessionId,
66
+ kind: item.kind,
67
+ title: item.title,
68
+ path: item.path || "",
69
+ preview: item.preview || "",
70
+ source: item.source,
71
+ tab: item.tab,
72
+ createdAt: item.createdAt,
73
+ selected: Boolean(item.selected),
74
+ mime: item.mime || "",
75
+ };
76
+ }
77
+
78
+ function sessionRelativePath(store, filePath) {
79
+ const absolutePath = path.resolve(String(filePath || ""));
80
+ if (!isInside(store.sessionDir, absolutePath)) return "";
81
+ return normalizeDisplayPath(path.relative(store.sessionDir, absolutePath));
82
+ }
83
+
84
+ function addSnapshotArtifacts(items, event, store, sessionId) {
85
+ const data = event.data || {};
86
+ if (data.screenshotPath) {
87
+ const displayPath = sessionRelativePath(store, data.screenshotPath);
88
+ if (displayPath) {
89
+ items.push({
90
+ id: stableId(sessionId, event.timestamp, "snapshot-image", displayPath),
91
+ sessionId,
92
+ kind: "image",
93
+ title: `Browser screenshot step ${data.step || "?"}`,
94
+ path: displayPath,
95
+ preview: data.title || data.url || "Captured browser screenshot.",
96
+ source: "snapshot",
97
+ tab: "canvas",
98
+ createdAt: event.timestamp,
99
+ mime: "image/png",
100
+ ref: {
101
+ type: "session-file",
102
+ path: path.resolve(data.screenshotPath),
103
+ },
104
+ });
105
+ }
106
+ }
107
+
108
+ if (data.snapshotPath) {
109
+ const displayPath = sessionRelativePath(store, data.snapshotPath);
110
+ if (displayPath) {
111
+ items.push({
112
+ id: stableId(sessionId, event.timestamp, "snapshot-json", displayPath),
113
+ sessionId,
114
+ kind: "json",
115
+ title: `Runtime snapshot step ${data.step || "?"}`,
116
+ path: displayPath,
117
+ preview: data.title || data.url || "Browser/runtime snapshot JSON.",
118
+ source: "snapshot",
119
+ tab: "explorer",
120
+ createdAt: event.timestamp,
121
+ mime: "application/json",
122
+ ref: {
123
+ type: "session-file",
124
+ path: path.resolve(data.snapshotPath),
125
+ },
126
+ });
127
+ }
128
+ }
129
+ }
130
+
131
+ function addWorkspaceArtifacts(items, event, sessionId) {
132
+ const data = event.data || {};
133
+ const filePath = normalizeDisplayPath(data.path || "");
134
+ if (!filePath) return;
135
+
136
+ const diff = redactSensitiveText(String(data.diff || ""));
137
+ items.push({
138
+ id: stableId(sessionId, event.timestamp, "file-diff", filePath, diff),
139
+ sessionId,
140
+ kind: "diff",
141
+ title: `Change: ${filePath}`,
142
+ path: filePath,
143
+ preview: previewText(diff || `${data.toolName || "file tool"} changed ${filePath}`),
144
+ source: "file-change",
145
+ tab: "notifications",
146
+ createdAt: event.timestamp,
147
+ mime: "text/x-diff",
148
+ ref: {
149
+ type: "inline",
150
+ text: diff || JSON.stringify(data, null, 2),
151
+ },
152
+ });
153
+
154
+ items.push({
155
+ id: stableId(sessionId, event.timestamp, "workspace-file", filePath),
156
+ sessionId,
157
+ kind: normalizeKind("file", filePath),
158
+ title: `Workspace file: ${filePath}`,
159
+ path: filePath,
160
+ preview: "Open the current workspace file through guarded read policy.",
161
+ source: "workspace-file",
162
+ tab: "explorer",
163
+ createdAt: event.timestamp,
164
+ mime: mimeForPath(filePath),
165
+ ref: {
166
+ type: "workspace-file",
167
+ path: filePath,
168
+ commandCwd: data.commandCwd || "",
169
+ },
170
+ });
171
+ }
172
+
173
+ function addCanvasArtifact(items, event, sessionId) {
174
+ const data = event.data || {};
175
+ const artifactId = data.artifactId || stableId(sessionId, event.timestamp, "canvas", data.title, data.path);
176
+ const artifactPath = normalizeDisplayPath(data.path || "");
177
+ const text = typeof data.content === "string" ? redactSensitiveText(data.content) : "";
178
+ items.push({
179
+ id: artifactId,
180
+ sessionId,
181
+ kind: normalizeKind(data.kind, artifactPath),
182
+ title: String(data.title || "Agent canvas item").slice(0, 120),
183
+ path: artifactPath,
184
+ preview: previewText(data.note || text || artifactPath || "Agent selected this item for canvas display."),
185
+ source: "agent-canvas",
186
+ tab: "canvas",
187
+ createdAt: event.timestamp,
188
+ mime: mimeForPath(artifactPath),
189
+ ref: text
190
+ ? {
191
+ type: "inline",
192
+ text,
193
+ }
194
+ : artifactPath
195
+ ? {
196
+ type: "workspace-file",
197
+ path: artifactPath,
198
+ commandCwd: data.commandCwd || "",
199
+ }
200
+ : {
201
+ type: "inline",
202
+ text: redactSensitiveText(String(data.note || "")),
203
+ },
204
+ });
205
+ }
206
+
207
+ function addFinalAnswerArtifact(items, event, sessionId) {
208
+ const result = String(event.data?.result || "");
209
+ if (!result.trim()) return;
210
+ items.push({
211
+ id: stableId(sessionId, event.timestamp, "final-answer", result),
212
+ sessionId,
213
+ kind: "markdown",
214
+ title: "Final answer",
215
+ path: "",
216
+ preview: previewText(result),
217
+ source: "session",
218
+ tab: "notifications",
219
+ createdAt: event.timestamp,
220
+ mime: "text/markdown; charset=utf-8",
221
+ ref: {
222
+ type: "inline",
223
+ text: redactSensitiveText(result),
224
+ },
225
+ });
226
+ }
227
+
228
+ function selectedArtifactId(events) {
229
+ return [...events].reverse().find((event) => event.type === "canvas.selected")?.data?.artifactId || "";
230
+ }
231
+
232
+ export function buildArtifacts({ sessionId, events, store }) {
233
+ const items = [];
234
+ const selectedId = selectedArtifactId(events);
235
+
236
+ for (const event of events) {
237
+ if (event.type === "canvas.item") addCanvasArtifact(items, event, sessionId);
238
+ if (event.type === "snapshot.captured") addSnapshotArtifacts(items, event, store, sessionId);
239
+ if (event.type === "file.changed") addWorkspaceArtifacts(items, event, sessionId);
240
+ if (event.type === "session.finished") addFinalAnswerArtifact(items, event, sessionId);
241
+ }
242
+
243
+ items.sort((a, b) => String(b.createdAt).localeCompare(String(a.createdAt)));
244
+ const fallbackSelected = selectedId || items.find((item) => item.tab === "canvas")?.id || items[0]?.id || "";
245
+ for (const item of items) item.selected = item.id === fallbackSelected;
246
+
247
+ return {
248
+ items,
249
+ selectedItemId: fallbackSelected,
250
+ };
251
+ }
252
+
253
+ export function serializeArtifacts(items) {
254
+ return items.map(publicArtifact);
255
+ }
256
+
257
+ export function countUnreadArtifacts(items, seenAfter = "") {
258
+ const seenAt = Date.parse(String(seenAfter || ""));
259
+ if (!Number.isFinite(seenAt)) return items.length;
260
+ return items.filter((item) => Date.parse(item.createdAt) > seenAt).length;
261
+ }
262
+
263
+ export function findArtifact(items, artifactId) {
264
+ return items.find((item) => item.id === artifactId) || null;
265
+ }
266
+
267
+ export async function readArtifactContent(item, { store, config }) {
268
+ if (!item) {
269
+ return { ok: false, error: "Artifact not found." };
270
+ }
271
+
272
+ if (item.ref?.type === "inline") {
273
+ return {
274
+ ok: true,
275
+ id: item.id,
276
+ kind: item.kind,
277
+ title: item.title,
278
+ path: item.path || "",
279
+ mime: item.mime || "text/plain; charset=utf-8",
280
+ text: redactSensitiveText(String(item.ref.text || "")),
281
+ };
282
+ }
283
+
284
+ if (item.ref?.type === "session-file") {
285
+ const absolutePath = path.resolve(item.ref.path);
286
+ if (!isInside(store.sessionDir, absolutePath)) {
287
+ return { ok: false, error: "Artifact path is outside this session." };
288
+ }
289
+
290
+ const stat = await fs.stat(absolutePath).catch(() => null);
291
+ if (!stat?.isFile()) return { ok: false, error: "Artifact file is missing." };
292
+ const mime = mimeForPath(absolutePath);
293
+ const isBinaryRenderable = mime.startsWith("image/") || mime === "application/pdf";
294
+ const maxBytes = isBinaryRenderable ? MAX_ARTIFACT_IMAGE_BYTES : MAX_ARTIFACT_TEXT_BYTES;
295
+ if (stat.size > maxBytes) return { ok: false, error: "Artifact is too large to preview safely." };
296
+
297
+ const buffer = await fs.readFile(absolutePath);
298
+ if (isBinaryRenderable) {
299
+ return {
300
+ ok: true,
301
+ id: item.id,
302
+ kind: mime === "application/pdf" ? "pdf" : "image",
303
+ title: item.title,
304
+ path: item.path || "",
305
+ mime,
306
+ dataUrl: `data:${mime};base64,${buffer.toString("base64")}`,
307
+ };
308
+ }
309
+
310
+ if (buffer.includes(0)) return { ok: false, error: "Binary artifact cannot be rendered as text." };
311
+ return {
312
+ ok: true,
313
+ id: item.id,
314
+ kind: item.kind,
315
+ title: item.title,
316
+ path: item.path || "",
317
+ mime,
318
+ text: redactSensitiveText(buffer.toString("utf8")),
319
+ };
320
+ }
321
+
322
+ if (item.ref?.type === "workspace-file") {
323
+ const itemConfig = item.ref.commandCwd ? { ...config, commandCwd: item.ref.commandCwd } : config;
324
+ const guard = checkWorkspaceToolUse("read_file", { path: item.ref.path }, itemConfig);
325
+ if (!guard.allowed) {
326
+ return { ok: false, error: guard.reason || "Workspace read blocked." };
327
+ }
328
+
329
+ const target = resolveWorkspacePath(itemConfig, item.ref.path);
330
+ const stat = await fs.stat(target.absolutePath).catch(() => null);
331
+ if (!stat?.isFile()) return { ok: false, error: "Workspace file is missing." };
332
+
333
+ const mime = mimeForPath(target.absolutePath);
334
+ const isBinaryRenderable = mime.startsWith("image/") || mime === "application/pdf";
335
+ const maxBytes = isBinaryRenderable ? MAX_ARTIFACT_IMAGE_BYTES : MAX_ARTIFACT_TEXT_BYTES;
336
+ if (stat.size > maxBytes) return { ok: false, error: "Workspace file is too large to preview safely." };
337
+
338
+ const buffer = await fs.readFile(target.absolutePath);
339
+ if (isBinaryRenderable) {
340
+ return {
341
+ ok: true,
342
+ id: item.id,
343
+ kind: mime === "application/pdf" ? "pdf" : "image",
344
+ title: item.title,
345
+ path: item.path || "",
346
+ mime,
347
+ dataUrl: `data:${mime};base64,${buffer.toString("base64")}`,
348
+ };
349
+ }
350
+
351
+ if (buffer.includes(0)) return { ok: false, error: "Binary workspace file cannot be rendered as text." };
352
+ return {
353
+ ok: true,
354
+ id: item.id,
355
+ kind: item.kind,
356
+ title: item.title,
357
+ path: item.path || "",
358
+ mime,
359
+ text: redactSensitiveText(buffer.toString("utf8")),
360
+ };
361
+ }
362
+
363
+ return { ok: false, error: "Artifact has no readable content." };
364
+ }
365
+
366
+ export function normalizeCanvasPayload(args, config) {
367
+ const artifactId = `canvas-${crypto.randomUUID()}`;
368
+ const title = String(args.title || "Agent canvas item").replace(/\s+/g, " ").trim().slice(0, 120);
369
+ const pathInput = String(args.path || "").trim();
370
+ const content = typeof args.content === "string" ? args.content : "";
371
+ const note = typeof args.note === "string" ? args.note : "";
372
+
373
+ if (!title) {
374
+ return { ok: false, reason: "Canvas title is required." };
375
+ }
376
+
377
+ if (Buffer.byteLength(content, "utf8") > MAX_INLINE_TEXT_BYTES) {
378
+ return { ok: false, reason: "Canvas content is too large. Write it to a workspace file and send the path instead." };
379
+ }
380
+
381
+ let relativePath = "";
382
+ if (pathInput) {
383
+ const guard = checkWorkspaceToolUse("read_file", { path: pathInput }, config);
384
+ if (!guard.allowed) return { ok: false, reason: guard.reason || "Canvas path is blocked." };
385
+ relativePath = resolveWorkspacePath(config, pathInput).relativePath;
386
+ }
387
+
388
+ return {
389
+ ok: true,
390
+ payload: {
391
+ artifactId,
392
+ title,
393
+ kind: normalizeKind(args.kind, relativePath),
394
+ path: relativePath,
395
+ content: content ? redactSensitiveText(content) : "",
396
+ contentBytes: content ? Buffer.byteLength(content, "utf8") : 0,
397
+ note: note ? previewText(note, 500) : "",
398
+ selected: args.selected !== false,
399
+ },
400
+ };
401
+ }
package/src/cli.js CHANGED
@@ -3,6 +3,10 @@ import { loadConfig } from "./config.js";
3
3
  import { listAgentWrappers } from "./tool-wrappers.js";
4
4
  import { getModelPresets } from "./model-routing.js";
5
5
  import { getDockerSandboxStatus, runDockerPreflight } from "./docker-sandbox.js";
6
+ import path from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ const packageDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
6
10
 
7
11
  function readOption(argv, index) {
8
12
  const value = argv[index + 1];
@@ -23,7 +27,9 @@ export function parseArgs(argv) {
23
27
  sandboxMode: "",
24
28
  packageInstallPolicy: "",
25
29
  allowShellTool: undefined,
30
+ allowFileTools: undefined,
26
31
  allowWrapperTools: undefined,
32
+ preferredWrapper: "",
27
33
  useDockerSandbox: undefined,
28
34
  headless: undefined,
29
35
  maxSteps: undefined,
@@ -31,11 +37,28 @@ export function parseArgs(argv) {
31
37
  listWrappers: false,
32
38
  sandboxStatus: false,
33
39
  sandboxPreflight: false,
40
+ web: false,
41
+ port: "",
42
+ host: "",
34
43
  };
35
44
 
36
45
  const parts = [];
37
46
  for (let i = 0; i < argv.length; i += 1) {
38
47
  const arg = argv[i];
48
+ if (arg === "web" || arg === "--web") {
49
+ result.web = true;
50
+ continue;
51
+ }
52
+ if (arg === "--port") {
53
+ result.port = readOption(argv, i);
54
+ i += 1;
55
+ continue;
56
+ }
57
+ if (arg === "--host") {
58
+ result.host = readOption(argv, i);
59
+ i += 1;
60
+ continue;
61
+ }
39
62
  if (arg === "--start-url") {
40
63
  result.startUrl = readOption(argv, i);
41
64
  i += 1;
@@ -95,10 +118,23 @@ export function parseArgs(argv) {
95
118
  result.allowShellTool = true;
96
119
  continue;
97
120
  }
121
+ if (arg === "--allow-file-tools") {
122
+ result.allowFileTools = true;
123
+ continue;
124
+ }
125
+ if (arg === "--no-file-tools") {
126
+ result.allowFileTools = false;
127
+ continue;
128
+ }
98
129
  if (arg === "--allow-wrappers") {
99
130
  result.allowWrapperTools = true;
100
131
  continue;
101
132
  }
133
+ if (arg === "--wrapper" || arg === "--preferred-wrapper") {
134
+ result.preferredWrapper = readOption(argv, i);
135
+ i += 1;
136
+ continue;
137
+ }
102
138
  if (arg === "--docker-sandbox") {
103
139
  result.useDockerSandbox = true;
104
140
  result.sandboxMode = result.sandboxMode || "docker-readonly";
@@ -150,6 +186,14 @@ function printWrappers() {
150
186
  export async function main(argv = process.argv.slice(2)) {
151
187
  const args = parseArgs(argv);
152
188
 
189
+ if (args.web) {
190
+ if (args.port) process.env.PORT = String(args.port);
191
+ if (args.host) process.env.HOST = String(args.host);
192
+ process.env.AGINTIFLOW_PACKAGE_DIR = packageDir;
193
+ await import("../web.js");
194
+ return;
195
+ }
196
+
153
197
  if (args.listRoutes) {
154
198
  printRoutes();
155
199
  return;
@@ -161,7 +205,7 @@ export async function main(argv = process.argv.slice(2)) {
161
205
  }
162
206
 
163
207
  if (args.sandboxStatus || args.sandboxPreflight) {
164
- const config = loadConfig({ ...args, goal: args.goal || "sandbox preflight" });
208
+ const config = loadConfig({ ...args, goal: args.goal || "sandbox preflight" }, { packageDir });
165
209
  const result = args.sandboxPreflight
166
210
  ? await runDockerPreflight(config, { buildImage: true })
167
211
  : { ok: true, status: await getDockerSandboxStatus(config) };
@@ -171,11 +215,11 @@ export async function main(argv = process.argv.slice(2)) {
171
215
 
172
216
  if (!args.goal && !args.resume) {
173
217
  console.error(
174
- 'Usage: aginti-cli [--routing smart|fast|complex|manual] [--sandbox-mode host|docker-readonly|docker-workspace] [--package-install-policy block|prompt|allow] [--allow-shell] [--allow-wrappers] [--sandbox-status|--sandbox-preflight] "your task"'
218
+ 'Usage: aginti-cli web [--port 3210] OR aginti-cli [--routing smart|fast|complex|manual] [--provider deepseek|openai|mock] [--sandbox-mode host|docker-readonly|docker-workspace] [--package-install-policy block|prompt|allow] [--allow-shell] [--allow-file-tools|--no-file-tools] [--allow-wrappers --wrapper codex] [--sandbox-status|--sandbox-preflight] "your task"'
175
219
  );
176
220
  process.exit(1);
177
221
  }
178
222
 
179
- const config = loadConfig(args);
223
+ const config = loadConfig(args, { packageDir });
180
224
  await runAgent(config);
181
225
  }
@@ -5,6 +5,8 @@ const READ_ONLY_PATTERNS = [
5
5
  /^pwd$/,
6
6
  /^date$/,
7
7
  /^whoami$/,
8
+ /^which\s+[-\w.]+(?:\s+[-\w.]+)*$/,
9
+ /^command\s+-v\s+[-\w.]+$/,
8
10
  /^uname(?:\s+-a)?$/,
9
11
  /^ls(?:\s+[-\w./~*]+)*$/,
10
12
  /^find(?:\s+[./~\w-]+)*(?:\s+-maxdepth\s+\d+)?(?:\s+-type\s+[fd])?$/,
@@ -31,6 +33,12 @@ const TEST_PATTERNS = [
31
33
  /^pytest(?:\s+[-\w./:=]+)*$/,
32
34
  ];
33
35
 
36
+ const TOOLCHAIN_PATTERNS = [
37
+ /^python(?:3)?\s+[-\w./]+\.py(?:\s+[-\w./:=]+)*$/,
38
+ /^latexmk\s+(?=[-\w./=\s]*-pdf\b)(?:(?:-cd|-pdf|-interaction=nonstopmode|-halt-on-error|-output-directory=[-\w./]+)\s+)+[-\w./]+\.tex$/,
39
+ /^pdflatex\s+(?:(?:-interaction=nonstopmode|-halt-on-error|-output-directory=[-\w./]+|-jobname\s+[-\w./]+)\s+)*[-\w./]+\.tex$/,
40
+ ];
41
+
34
42
  const PACKAGE_INSTALL_PATTERNS = [
35
43
  /^npm\s+ci$/,
36
44
  /^npm\s+install$/,
@@ -121,6 +129,9 @@ export function classifyCommand(command) {
121
129
  if (matchAny(TEST_PATTERNS, normalized)) {
122
130
  return { category: "test", needsNetwork: false, writesWorkspace: false };
123
131
  }
132
+ if (matchAny(TOOLCHAIN_PATTERNS, normalized)) {
133
+ return { category: "toolchain", needsNetwork: false, writesWorkspace: true };
134
+ }
124
135
  if (matchAny(PACKAGE_INSTALL_PATTERNS, normalized)) {
125
136
  return { category: "package-install", needsNetwork: true, writesWorkspace: true };
126
137
  }
@@ -176,6 +187,16 @@ export function evaluateCommandPolicy(command, config) {
176
187
  }
177
188
  }
178
189
 
190
+ if (classification.writesWorkspace && config.useDockerSandbox && sandboxMode !== "docker-workspace") {
191
+ return {
192
+ allowed: false,
193
+ category: classification.category,
194
+ reason: "Workspace-writing toolchain commands must run in Docker workspace-write sandbox mode.",
195
+ sandboxMode,
196
+ packageInstallPolicy,
197
+ };
198
+ }
199
+
179
200
  return {
180
201
  allowed: true,
181
202
  ...classification,
package/src/config.js CHANGED
@@ -2,6 +2,7 @@ import path from "node:path";
2
2
  import crypto from "node:crypto";
3
3
  import { getProviderDefaults, normalizeRoutingMode, selectModelRoute } from "./model-routing.js";
4
4
  import { normalizePackageInstallPolicy, normalizeSandboxMode } from "./command-policy.js";
5
+ import { normalizeWrapperName } from "./tool-wrappers.js";
5
6
 
6
7
  function parseBoolean(value, fallback) {
7
8
  if (value === undefined) return fallback;
@@ -37,6 +38,7 @@ export function resolveRuntimeConfig(args, overrides = {}) {
37
38
 
38
39
  const defaults = getProviderDefaults(route.provider);
39
40
  const baseDir = path.resolve(overrides.baseDir || process.cwd());
41
+ const packageDir = path.resolve(overrides.packageDir || process.env.AGINTIFLOW_PACKAGE_DIR || baseDir);
40
42
  const dockerRequested = parseBoolean(overrides.useDockerSandbox ?? args.useDockerSandbox ?? process.env.USE_DOCKER_SANDBOX, false);
41
43
  const requestedSandboxMode =
42
44
  overrides.sandboxMode || args.sandboxMode || process.env.SANDBOX_MODE || (dockerRequested ? "docker-readonly" : "host");
@@ -45,6 +47,7 @@ export function resolveRuntimeConfig(args, overrides = {}) {
45
47
  return {
46
48
  ...defaults,
47
49
  baseDir,
50
+ packageDir,
48
51
  goal: args.goal || "",
49
52
  startUrl: args.startUrl || "",
50
53
  resume: args.resume || "",
@@ -66,10 +69,14 @@ export function resolveRuntimeConfig(args, overrides = {}) {
66
69
  allowPasswords: parseBoolean(overrides.allowPasswords ?? process.env.ALLOW_PASSWORDS, false),
67
70
  allowDestructive: parseBoolean(overrides.allowDestructive ?? process.env.ALLOW_DESTRUCTIVE, false),
68
71
  allowShellTool: parseBoolean(overrides.allowShellTool ?? args.allowShellTool ?? process.env.ALLOW_SHELL_TOOL, false),
72
+ allowFileTools: parseBoolean(overrides.allowFileTools ?? args.allowFileTools ?? process.env.ALLOW_FILE_TOOLS, true),
69
73
  allowWrapperTools: parseBoolean(
70
74
  overrides.allowWrapperTools ?? args.allowWrapperTools ?? process.env.ALLOW_WRAPPER_TOOLS,
71
75
  false
72
76
  ),
77
+ preferredWrapper: normalizeWrapperName(
78
+ overrides.preferredWrapper ?? args.preferredWrapper ?? process.env.PREFERRED_WRAPPER ?? process.env.AGENT_WRAPPER
79
+ ),
73
80
  wrapperTimeoutMs: parseNumber(overrides.wrapperTimeoutMs ?? process.env.WRAPPER_TIMEOUT_MS, 120000),
74
81
  sandboxMode,
75
82
  packageInstallPolicy: normalizePackageInstallPolicy(
@@ -84,6 +91,6 @@ export function resolveRuntimeConfig(args, overrides = {}) {
84
91
  };
85
92
  }
86
93
 
87
- export function loadConfig(args) {
88
- return resolveRuntimeConfig(args);
94
+ export function loadConfig(args, overrides = {}) {
95
+ return resolveRuntimeConfig(args, overrides);
89
96
  }