@lazyingart/agintiflow 0.20.86 → 0.20.88
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/smoke-cli-chat.js +1 -0
- package/scripts/smoke-coding-tools.js +11 -0
- package/scripts/smoke-model-roles.js +7 -1
- package/src/agent-runner.js +1 -1
- package/src/command-policy.js +45 -4
- package/src/interactive-cli.js +33 -5
- package/src/static-preview-server.js +14 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.88",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Low-cost, project-aware Web and CLI agents with DeepSeek/Venice/OpenAI routing, visible tool calls, durable sessions, scouts, AAPS, SCS, and guarded local execution.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -266,6 +266,16 @@ try {
|
|
|
266
266
|
packageInstallPolicy: "allow",
|
|
267
267
|
commandCwd: workspace,
|
|
268
268
|
};
|
|
269
|
+
const dockerWorkspaceNoInstallsPolicy = {
|
|
270
|
+
...dockerWorkspacePolicy,
|
|
271
|
+
packageInstallPolicy: "block",
|
|
272
|
+
};
|
|
273
|
+
const readonlyProbePolicy = evaluateCommandPolicy(
|
|
274
|
+
'which pdflatex 2>&1; which latexmk 2>&1; echo "---"; find /workspace -name \'*.tex\' -maxdepth 3 2>/dev/null; echo "exit: $?"',
|
|
275
|
+
dockerWorkspaceNoInstallsPolicy
|
|
276
|
+
);
|
|
277
|
+
assert(readonlyProbePolicy.allowed, "read-only toolchain probe sequence should not require package-install-policy=allow");
|
|
278
|
+
assert(readonlyProbePolicy.category === "read-only", "read-only toolchain probe sequence should be classified as read-only");
|
|
269
279
|
const curlPolicy = evaluateCommandPolicy("curl -s -o /dev/null -w '%{http_code}' https://github.com/lazyingart/AgInTiFlow.git", dockerWorkspacePolicy);
|
|
270
280
|
assert(curlPolicy.allowed, "curl URL probe with flags should be allowed in docker-workspace allow mode");
|
|
271
281
|
assert(curlPolicy.needsNetwork, "curl URL probe with flags was not classified as network");
|
|
@@ -725,6 +735,7 @@ try {
|
|
|
725
735
|
"large_profile_pro_route",
|
|
726
736
|
"auto_system_pro_route",
|
|
727
737
|
"auto_engineering_guidance",
|
|
738
|
+
"command_policy_readonly_probe_sequence_no_installs",
|
|
728
739
|
"command_policy_git_clone_network",
|
|
729
740
|
"command_policy_safe_chmod_sequence",
|
|
730
741
|
"command_policy_cd_workspace",
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
selectModelRoute,
|
|
11
11
|
} from "../src/model-routing.js";
|
|
12
12
|
import { normalizeTextToolCallResponse, parseTextToolCalls, usesTextToolProtocol } from "../src/model-client.js";
|
|
13
|
-
import { modelRoleChoices } from "../src/interactive-cli.js";
|
|
13
|
+
import { modelRoleChoices, selectorVisibleWindow } from "../src/interactive-cli.js";
|
|
14
14
|
import { buildScsEvidencePack, buildSupervisorInstruction } from "../src/scs-controller.js";
|
|
15
15
|
|
|
16
16
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -151,6 +151,11 @@ assert(!routeChoices.includes("venice/e2ee-venice-uncensored-24b-p"), "shared mo
|
|
|
151
151
|
assert(modelRoleChoices("route").some((item) => item.provider === "openai" && item.model === "gpt-5.5" && item.reasoningOptions.includes("xhigh")), "OpenAI selector missing reasoning levels");
|
|
152
152
|
assert(modelRoleChoices("auxiliary").some((item) => item.provider === "grsai"), "auxiliary selector missing GRS AI");
|
|
153
153
|
assert(modelRoleChoices("auxiliary").some((item) => item.provider === "venice" && item.model === "wan-2-7-pro-edit"), "auxiliary selector missing Venice Wan edit");
|
|
154
|
+
const longAuxiliaryWindow = selectorVisibleWindow(32, 17, 24);
|
|
155
|
+
assert(longAuxiliaryWindow.end - longAuxiliaryWindow.start <= 8, "selector should cap visible rows for long option lists");
|
|
156
|
+
assert(longAuxiliaryWindow.topHidden > 0 && longAuxiliaryWindow.bottomHidden > 0, "selector should expose scroll indicators around middle selections");
|
|
157
|
+
const topAuxiliaryWindow = selectorVisibleWindow(32, 0, 24);
|
|
158
|
+
assert(topAuxiliaryWindow.start === 0 && topAuxiliaryWindow.bottomHidden > 0, "selector should keep first option visible at top of long lists");
|
|
154
159
|
const parsedTextToolCalls = parseTextToolCalls('[TOOL_CALLS]list_files[ARGS]call_123[ARGS]{"path":".","maxDepth":1}');
|
|
155
160
|
assert(parsedTextToolCalls.length === 1, "Venice text tool-call parser did not detect encoded tool call");
|
|
156
161
|
assert(parsedTextToolCalls[0].function.name === "list_files", "Venice text tool-call parser returned wrong tool name");
|
|
@@ -253,6 +258,7 @@ console.log(
|
|
|
253
258
|
"route-overrides",
|
|
254
259
|
"provider-groups",
|
|
255
260
|
"auxiliary-catalog",
|
|
261
|
+
"selector-windowing",
|
|
256
262
|
"shared-model-selectors",
|
|
257
263
|
"venice-text-tool-parser",
|
|
258
264
|
"requested-tools-parser",
|
package/src/agent-runner.js
CHANGED
|
@@ -151,7 +151,7 @@ async function isPortAvailable(port) {
|
|
|
151
151
|
async function findAvailablePort(preferredPort = 8765) {
|
|
152
152
|
const preferred = Number(preferredPort);
|
|
153
153
|
const start = Number.isFinite(preferred) && preferred > 0 ? preferred : 8765;
|
|
154
|
-
for (let port = start; port < start +
|
|
154
|
+
for (let port = start; port < start + 1000 && port < 65535; port += 1) {
|
|
155
155
|
if (await isPortAvailable(port)) return port;
|
|
156
156
|
}
|
|
157
157
|
throw new Error(`No available preview port found near ${start}.`);
|
package/src/command-policy.js
CHANGED
|
@@ -26,6 +26,25 @@ const READ_ONLY_PATTERNS = [
|
|
|
26
26
|
/^echo(?:\s+.+)?$/,
|
|
27
27
|
];
|
|
28
28
|
|
|
29
|
+
function stripBenignRedirections(command = "") {
|
|
30
|
+
return String(command || "")
|
|
31
|
+
.replace(/\s+2>&1\b/g, "")
|
|
32
|
+
.replace(/\s+1>&2\b/g, "")
|
|
33
|
+
.replace(/\s+2>\/dev\/null\b/g, "")
|
|
34
|
+
.replace(/\s+1>\/dev\/null\b/g, "")
|
|
35
|
+
.replace(/\s+>\/dev\/null\b/g, "")
|
|
36
|
+
.trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isReadOnlyFindCommand(command = "") {
|
|
40
|
+
const normalized = stripBenignRedirections(command);
|
|
41
|
+
if (!/^find\s+/.test(normalized)) return false;
|
|
42
|
+
if (/(^|\s)(-delete|-exec|-execdir|-ok|-okdir|-fprint|-fprintf|-fls)\b/.test(normalized)) return false;
|
|
43
|
+
const unquoted = stripQuotedSegments(normalized);
|
|
44
|
+
if (/[|<>;&`$]/.test(unquoted)) return false;
|
|
45
|
+
return /^find\s+(?:[-./~\w]+|\/workspace)(?:\s+[-\w]+(?:\s+(?:"[^"\n]*"|'[^'\n]*'|[^\s|<>;&`$]+))?)*$/.test(normalized);
|
|
46
|
+
}
|
|
47
|
+
|
|
29
48
|
const TEST_PATTERNS = [
|
|
30
49
|
/^npm\s+(run\s+)?(check|test|build|lint)(?:\s+--\s+[-\w./:=]+)*$/,
|
|
31
50
|
/^npm\s+--prefix\s+[-\w./]+\s+(run\s+)?(check|test|build|lint)(?:\s+--\s+[-\w./:=]+)*$/,
|
|
@@ -242,6 +261,7 @@ function classifyGitClone(normalized) {
|
|
|
242
261
|
}
|
|
243
262
|
|
|
244
263
|
function classifySimpleCommand(normalized) {
|
|
264
|
+
const readOnlyCommand = stripBenignRedirections(normalized);
|
|
245
265
|
if (ALWAYS_BLOCKED_PATTERNS.some((pattern) => pattern.test(normalized))) {
|
|
246
266
|
return { category: "blocked", reason: "Command is blocked because it may expose secrets or publish packages." };
|
|
247
267
|
}
|
|
@@ -294,7 +314,7 @@ function classifySimpleCommand(normalized) {
|
|
|
294
314
|
const gitCloneClassification = classifyGitClone(normalized);
|
|
295
315
|
if (gitCloneClassification) return gitCloneClassification;
|
|
296
316
|
|
|
297
|
-
const unquoted = stripQuotedSegments(
|
|
317
|
+
const unquoted = stripQuotedSegments(readOnlyCommand);
|
|
298
318
|
const lowered = ` ${unquoted.toLowerCase()} `;
|
|
299
319
|
if (BLOCKED_WRITE_TOKENS.some((part) => lowered.includes(part))) {
|
|
300
320
|
return {
|
|
@@ -313,7 +333,7 @@ function classifySimpleCommand(normalized) {
|
|
|
313
333
|
};
|
|
314
334
|
}
|
|
315
335
|
|
|
316
|
-
if (matchAny(READ_ONLY_PATTERNS, normalized)) {
|
|
336
|
+
if (matchAny(READ_ONLY_PATTERNS, readOnlyCommand) || isReadOnlyFindCommand(normalized)) {
|
|
317
337
|
return { category: "read-only", needsNetwork: false, writesWorkspace: false };
|
|
318
338
|
}
|
|
319
339
|
if (matchAny(TEST_PATTERNS, normalized)) {
|
|
@@ -395,14 +415,35 @@ function classifyShellSequence(normalized) {
|
|
|
395
415
|
const classifications = parts.map((part) => classifyCdCommand(part) || classifySimpleCommand(part));
|
|
396
416
|
const blocked = classifications.find((classification) => classification.category === "blocked" || classification.category === "destructive");
|
|
397
417
|
if (blocked) return blocked;
|
|
398
|
-
|
|
399
|
-
|
|
418
|
+
const broad = classifications.find((classification) => classification.category === "general-shell");
|
|
419
|
+
if (broad) {
|
|
420
|
+
return {
|
|
421
|
+
...broad,
|
|
422
|
+
reason: `Command sequence includes a broad shell segment and requires trusted shell policy: ${normalized}`,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
const categories = new Set(classifications.map((classification) => classification.category));
|
|
426
|
+
const aggregate = {
|
|
400
427
|
needsNetwork: classifications.some((classification) => classification.needsNetwork),
|
|
401
428
|
writesWorkspace: classifications.some((classification) => classification.writesWorkspace),
|
|
402
429
|
requiresDockerRoot: classifications.some((classification) => classification.requiresDockerRoot),
|
|
403
430
|
virtualWorkspacePath: classifications.some((classification) => classification.virtualWorkspacePath),
|
|
404
431
|
reason: `Command sequence uses shell separators with individually classified safe segments: ${normalized}`,
|
|
405
432
|
};
|
|
433
|
+
if (categories.has("system-package-install")) return { category: "system-package-install", ...aggregate };
|
|
434
|
+
if (categories.has("package-install")) return { category: "package-install", ...aggregate };
|
|
435
|
+
if (categories.has("env-setup")) return { category: "env-setup", ...aggregate };
|
|
436
|
+
if (categories.has("permission-change")) return { category: "permission-change", ...aggregate };
|
|
437
|
+
if (categories.has("git-remote")) return { category: "git-remote", ...aggregate };
|
|
438
|
+
if (categories.has("network-fetch")) return { category: "network-fetch", ...aggregate };
|
|
439
|
+
if (categories.has("workspace-write")) return { category: "workspace-write", ...aggregate };
|
|
440
|
+
if (categories.has("toolchain")) return { category: "toolchain", ...aggregate };
|
|
441
|
+
if (categories.has("test")) return { category: "test", ...aggregate };
|
|
442
|
+
if (categories.has("git-workflow")) return { category: "git-workflow", ...aggregate };
|
|
443
|
+
return {
|
|
444
|
+
category: "read-only",
|
|
445
|
+
...aggregate,
|
|
446
|
+
};
|
|
406
447
|
}
|
|
407
448
|
|
|
408
449
|
function classifyCdCommand(normalized) {
|
package/src/interactive-cli.js
CHANGED
|
@@ -2220,21 +2220,49 @@ function clearSelectorSequence(lineCount) {
|
|
|
2220
2220
|
return Array.from({ length: Math.max(lineCount, 0) }, () => "\x1b[1A\r\x1b[2K").join("");
|
|
2221
2221
|
}
|
|
2222
2222
|
|
|
2223
|
+
export function selectorVisibleWindow(optionCount = 0, selectedIndex = 0, height = terminalHeight()) {
|
|
2224
|
+
const count = Math.max(Number(optionCount) || 0, 0);
|
|
2225
|
+
const maxRows = Math.max(4, Math.min(8, Math.floor(Math.max(Number(height) || 24, 10) * 0.36)));
|
|
2226
|
+
if (count <= maxRows) {
|
|
2227
|
+
return { start: 0, end: count, topHidden: 0, bottomHidden: 0 };
|
|
2228
|
+
}
|
|
2229
|
+
const selected = clamp(selectedIndex, 0, count - 1);
|
|
2230
|
+
const half = Math.floor(maxRows / 2);
|
|
2231
|
+
const start = clamp(selected - half, 0, count - maxRows);
|
|
2232
|
+
const end = Math.min(start + maxRows, count);
|
|
2233
|
+
return {
|
|
2234
|
+
start,
|
|
2235
|
+
end,
|
|
2236
|
+
topHidden: start,
|
|
2237
|
+
bottomHidden: count - end,
|
|
2238
|
+
};
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2223
2241
|
function renderSelector({ title, subtitle, options, selectedIndex, lineCount = 0 }) {
|
|
2224
2242
|
const width = Math.min(Math.max(terminalWidth() - 2, 60), 110);
|
|
2225
2243
|
const bodyWidth = width - 4;
|
|
2226
2244
|
const safeTitle = compactLine(title, bodyWidth);
|
|
2227
2245
|
const safeSubtitle = compactLine(subtitle, bodyWidth);
|
|
2246
|
+
const visible = selectorVisibleWindow(options.length, selectedIndex, terminalHeight());
|
|
2247
|
+
const optionRows = [];
|
|
2248
|
+
if (visible.topHidden > 0) {
|
|
2249
|
+
optionRows.push(`│ ${padVisible(color(` ... ${visible.topHidden} earlier option${visible.topHidden === 1 ? "" : "s"}`, ansi.dim), bodyWidth)} │`);
|
|
2250
|
+
}
|
|
2251
|
+
for (let index = visible.start; index < visible.end; index += 1) {
|
|
2252
|
+
const option = options[index];
|
|
2253
|
+
const marker = index === selectedIndex ? ">" : " ";
|
|
2254
|
+
const rendered = `${marker} ${compactLine(modelChoiceLine(option), bodyWidth - 2)}`;
|
|
2255
|
+
optionRows.push(`│ ${padVisible(index === selectedIndex ? color(rendered, ansi.userBg, ansi.bold) : rendered, bodyWidth)} │`);
|
|
2256
|
+
}
|
|
2257
|
+
if (visible.bottomHidden > 0) {
|
|
2258
|
+
optionRows.push(`│ ${padVisible(color(` ... ${visible.bottomHidden} later option${visible.bottomHidden === 1 ? "" : "s"}`, ansi.dim), bodyWidth)} │`);
|
|
2259
|
+
}
|
|
2228
2260
|
const rows = [
|
|
2229
2261
|
`╭${"─".repeat(width - 2)}╮`,
|
|
2230
2262
|
`│ ${padVisible(safeTitle, bodyWidth)} │`,
|
|
2231
2263
|
`│ ${padVisible(safeSubtitle, bodyWidth)} │`,
|
|
2232
2264
|
`├${"─".repeat(width - 2)}┤`,
|
|
2233
|
-
...
|
|
2234
|
-
const marker = index === selectedIndex ? ">" : " ";
|
|
2235
|
-
const rendered = `${marker} ${compactLine(modelChoiceLine(option), bodyWidth - 2)}`;
|
|
2236
|
-
return `│ ${padVisible(index === selectedIndex ? color(rendered, ansi.userBg, ansi.bold) : rendered, bodyWidth)} │`;
|
|
2237
|
-
}),
|
|
2265
|
+
...optionRows,
|
|
2238
2266
|
`╰${"─".repeat(width - 2)}╯`,
|
|
2239
2267
|
];
|
|
2240
2268
|
output.write(`${lineCount > 0 ? clearSelectorSequence(lineCount) : ""}${rows.join("\n")}\n`);
|
|
@@ -5,6 +5,8 @@ import path from "node:path";
|
|
|
5
5
|
const root = path.resolve(process.argv[2] || process.cwd());
|
|
6
6
|
const port = Number(process.argv[3] || 0);
|
|
7
7
|
const host = "127.0.0.1";
|
|
8
|
+
const idleTtlMs = Math.max(Number(process.env.AGINTIFLOW_PREVIEW_TTL_MS) || 6 * 60 * 60 * 1000, 1000);
|
|
9
|
+
let lastRequestAt = Date.now();
|
|
8
10
|
|
|
9
11
|
const MIME_TYPES = new Map([
|
|
10
12
|
[".html", "text/html; charset=utf-8"],
|
|
@@ -62,6 +64,7 @@ function isBlockedPath(relativePath) {
|
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
const server = http.createServer(async (req, res) => {
|
|
67
|
+
lastRequestAt = Date.now();
|
|
65
68
|
if (!["GET", "HEAD"].includes(req.method || "")) {
|
|
66
69
|
send(res, 405, "Method Not Allowed");
|
|
67
70
|
return;
|
|
@@ -110,3 +113,14 @@ server.listen(port, host, () => {
|
|
|
110
113
|
const actualPort = typeof address === "object" && address ? address.port : port;
|
|
111
114
|
console.log(`AgInTiFlow static preview http://${host}:${actualPort}/ root=${root}`);
|
|
112
115
|
});
|
|
116
|
+
|
|
117
|
+
const ttlTimer = setInterval(async () => {
|
|
118
|
+
const rootExists = await fs
|
|
119
|
+
.stat(root)
|
|
120
|
+
.then((stat) => stat.isDirectory())
|
|
121
|
+
.catch(() => false);
|
|
122
|
+
if (rootExists && Date.now() - lastRequestAt < idleTtlMs) return;
|
|
123
|
+
server.close(() => process.exit(0));
|
|
124
|
+
setTimeout(() => process.exit(0), 1000).unref();
|
|
125
|
+
}, Math.min(Math.max(Math.floor(idleTtlMs / 2), 1000), 60_000));
|
|
126
|
+
ttlTimer.unref();
|