@lazyingart/agintiflow 0.15.0 → 0.16.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.
- package/README.md +17 -4
- package/package.json +4 -2
- package/public/app.js +1 -0
- package/scripts/setup-agent-toolchain-docker.sh +84 -35
- package/scripts/smoke-capabilities.js +2 -0
- package/scripts/smoke-cli-chat.js +7 -1
- package/scripts/smoke-platform.js +45 -0
- package/src/agent-runner.js +10 -5
- package/src/capabilities.js +26 -2
- package/src/cli.js +5 -0
- package/src/command-policy.js +5 -0
- package/src/docker-sandbox.js +2 -0
- package/src/interactive-cli.js +5 -32
- package/src/model-client.js +4 -1
- package/src/platform.js +103 -0
- package/src/project.js +7 -0
- package/src/task-profiles.js +1 -1
- package/src/tool-wrappers.js +5 -1
- package/web.js +7 -0
package/README.md
CHANGED
|
@@ -179,7 +179,7 @@ aginti capabilities --json
|
|
|
179
179
|
aginti doctor --capabilities
|
|
180
180
|
```
|
|
181
181
|
|
|
182
|
-
The report checks the project root, command cwd, shared `.sessions/`, provider-key presence, DeepSeek routes, guarded file and shell tools, Docker status, wrappers, task profiles, TeX, Node/npm, Python, R, conda, and maintenance command policy. It never prints API key or token values.
|
|
182
|
+
The report checks the project root, command cwd, shared `.sessions/`, provider-key presence, DeepSeek routes, guarded file and shell tools, OS/platform hints, Docker status, wrappers, task profiles, TeX, Node/npm, Python, R, conda, and maintenance command policy. It never prints API key or token values.
|
|
183
183
|
|
|
184
184
|
Live DeepSeek verification is opt-in because it spends provider credits:
|
|
185
185
|
|
|
@@ -337,7 +337,20 @@ npm publish --access public
|
|
|
337
337
|
|
|
338
338
|
Never publish with `npm publish` from inside an agent run. The runtime command policy blocks npm publish and npm token commands by design.
|
|
339
339
|
|
|
340
|
-
## Docker Bootstrap
|
|
340
|
+
## Platform And Docker Bootstrap
|
|
341
|
+
|
|
342
|
+
AgInTiFlow is designed for Linux, macOS, Windows through WSL2, and best-effort native Windows use. The most portable shell/toolchain path is `docker-workspace`; native Windows host shell commands should be treated as best effort unless you run inside WSL2.
|
|
343
|
+
|
|
344
|
+
Platform notes:
|
|
345
|
+
|
|
346
|
+
| Platform | Recommended setup |
|
|
347
|
+
| --- | --- |
|
|
348
|
+
| Ubuntu/Debian | Node.js 22+, optional `scripts/install-docker-ubuntu.sh`, Docker workspace mode for broad toolchains. |
|
|
349
|
+
| Red Hat/Fedora/Rocky/Alma | Node.js 22+, Docker/Podman-compatible Docker CLI from `dnf`/vendor docs; do not use the Ubuntu Docker script. |
|
|
350
|
+
| macOS | Node.js 22+ from Homebrew/nvm/fnm, Docker Desktop or Colima, optional MacTeX/BasicTeX for host LaTeX. |
|
|
351
|
+
| Windows | Prefer WSL2 with Docker Desktop WSL integration. Native Windows host mode is best effort; Docker/WSL is recommended for Bash, LaTeX, Python/R/Stan, and package-manager workflows. |
|
|
352
|
+
|
|
353
|
+
LaTeX support first checks the active environment for `latexmk` or `pdflatex`. If your host already has MacTeX, BasicTeX, TeX Live, or MiKTeX on `PATH`, host-mode LaTeX tasks can compile directly without downloading TeX again. Docker-mode LaTeX uses the companion image; the setup script preflights an existing image and skips rebuilds when the Docker toolchain already has `latexmk` and `pdflatex`.
|
|
341
354
|
|
|
342
355
|
Ubuntu helper:
|
|
343
356
|
|
|
@@ -353,14 +366,14 @@ DOCKER_TARGET_USER=lachlan ./scripts/install-docker-ubuntu.sh
|
|
|
353
366
|
|
|
354
367
|
Open a new login shell, or run `newgrp docker`, before testing non-root Docker access.
|
|
355
368
|
|
|
356
|
-
Build the companion agent toolchain sandbox:
|
|
369
|
+
Build or verify the companion agent toolchain sandbox:
|
|
357
370
|
|
|
358
371
|
```bash
|
|
359
372
|
./scripts/setup-agent-toolchain-docker.sh
|
|
360
373
|
npm run smoke:toolchain-docker
|
|
361
374
|
```
|
|
362
375
|
|
|
363
|
-
The setup script
|
|
376
|
+
The setup script checks whether `agintiflow-sandbox:latest` already exists and runs a Docker preflight before rebuilding. If Node, npm, Python, NumPy, Matplotlib, `latexmk`, and `pdflatex` are already ready inside the image, it exits without redownloading TeX Live. Set `AGINTIFLOW_FORCE_TOOLCHAIN_REBUILD=true` only when you intentionally want a fresh image. The script also creates persistent companion folders under `~/.agintiflow/docker/`: `home/` maps to `/aginti-home`, `cache/` maps to `/aginti-cache`, and `env/` maps to `/aginti-env`. Python/conda-style toolchains should live under `/aginti-env` so they survive across agent runs. OS package changes from `apt-get` are container-ephemeral unless you rebuild the image.
|
|
364
377
|
|
|
365
378
|
## Sandbox Modes
|
|
366
379
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgInTiFlow is a resumable Playwright website-control agent with OpenAI-compatible tool calling.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"scripts/smoke-coding-tools.js",
|
|
48
48
|
"scripts/smoke-capabilities.js",
|
|
49
49
|
"scripts/smoke-inbox.js",
|
|
50
|
+
"scripts/smoke-platform.js",
|
|
50
51
|
"scripts/smoke-skills.js",
|
|
51
52
|
"scripts/smoke-toolchain-docker.js",
|
|
52
53
|
"scripts/smoke-web-api.js",
|
|
@@ -71,9 +72,10 @@
|
|
|
71
72
|
"smoke:skills": "node scripts/smoke-skills.js",
|
|
72
73
|
"smoke:toolchain-docker": "node scripts/smoke-toolchain-docker.js",
|
|
73
74
|
"smoke:inbox": "node scripts/smoke-inbox.js",
|
|
75
|
+
"smoke:platform": "node scripts/smoke-platform.js",
|
|
74
76
|
"smoke:web-api": "node scripts/smoke-web-api.js",
|
|
75
77
|
"real:deepseek": "node scripts/real-deepseek-capabilities.js",
|
|
76
|
-
"test": "npm run check && npm run smoke:web-api && npm run smoke:coding-tools && npm run smoke:auxiliary-tools && npm run smoke:auth && npm run smoke:capabilities && npm run smoke:skills && npm run smoke:cli-chat && npm run smoke:inbox",
|
|
78
|
+
"test": "npm run check && npm run smoke:web-api && npm run smoke:coding-tools && npm run smoke:auxiliary-tools && npm run smoke:auth && npm run smoke:capabilities && npm run smoke:platform && npm run smoke:skills && npm run smoke:cli-chat && npm run smoke:inbox",
|
|
77
79
|
"pack:dry-run": "npm pack --dry-run",
|
|
78
80
|
"smoke:capabilities": "node scripts/smoke-capabilities.js"
|
|
79
81
|
},
|
package/public/app.js
CHANGED
|
@@ -798,6 +798,7 @@ function renderProjectStatus(info = projectInfo) {
|
|
|
798
798
|
projectInfo = info;
|
|
799
799
|
if (!projectStatusEl || !info) return;
|
|
800
800
|
projectStatusEl.textContent = [
|
|
801
|
+
info.platform?.label ? `os=${info.platform.label}` : "",
|
|
801
802
|
`root=${info.root || ""}`,
|
|
802
803
|
`cwd=${info.commandCwd || ""}`,
|
|
803
804
|
`sessions=${info.sessionsDir || ""}`,
|
|
@@ -7,57 +7,106 @@ IMAGE="${AGINTIFLOW_TOOLCHAIN_IMAGE:-agintiflow-sandbox:latest}"
|
|
|
7
7
|
WORKSPACE="${AGINTIFLOW_TOOLCHAIN_WORKSPACE:-${REPO_ROOT}}"
|
|
8
8
|
DOCKERFILE="${AGINTIFLOW_TOOLCHAIN_DOCKERFILE:-${REPO_ROOT}/docker/sandbox.Dockerfile}"
|
|
9
9
|
STATE_DIR="${AGINTIFLOW_DOCKER_STATE_DIR:-${HOME:-/tmp}/.agintiflow/docker}"
|
|
10
|
+
FORCE_REBUILD="${AGINTIFLOW_FORCE_TOOLCHAIN_REBUILD:-false}"
|
|
11
|
+
|
|
12
|
+
os_name="$(uname -s 2>/dev/null || printf unknown)"
|
|
13
|
+
case "${os_name}" in
|
|
14
|
+
Darwin) platform_hint="macOS: use Docker Desktop or Colima; host LaTeX can come from MacTeX/BasicTeX." ;;
|
|
15
|
+
Linux)
|
|
16
|
+
if grep -qi microsoft /proc/version 2>/dev/null; then
|
|
17
|
+
platform_hint="WSL: use Docker Desktop WSL integration or Docker Engine inside WSL."
|
|
18
|
+
elif [ -r /etc/os-release ] && grep -Eqi '^(ID|ID_LIKE)=.*(rhel|fedora|centos|rocky|almalinux|redhat)' /etc/os-release; then
|
|
19
|
+
platform_hint="Red Hat/Fedora family: install Docker with dnf/yum or vendor docs; do not use install-docker-ubuntu.sh."
|
|
20
|
+
else
|
|
21
|
+
platform_hint="Linux: Ubuntu/Debian hosts may use scripts/install-docker-ubuntu.sh."
|
|
22
|
+
fi
|
|
23
|
+
;;
|
|
24
|
+
MINGW*|MSYS*|CYGWIN*) platform_hint="Windows shell: prefer WSL2 for AgInTiFlow Docker/toolchain workflows." ;;
|
|
25
|
+
*) platform_hint="Use a Docker-capable environment with Node.js 22+." ;;
|
|
26
|
+
esac
|
|
10
27
|
|
|
11
28
|
if ! command -v docker >/dev/null 2>&1; then
|
|
12
|
-
echo "Docker is not installed or not on PATH.
|
|
29
|
+
echo "Docker is not installed or not on PATH." >&2
|
|
30
|
+
echo "${platform_hint}" >&2
|
|
13
31
|
exit 1
|
|
14
32
|
fi
|
|
15
33
|
|
|
16
34
|
mkdir -p "${WORKSPACE}"
|
|
17
35
|
mkdir -p "${STATE_DIR}/home" "${STATE_DIR}/cache" "${STATE_DIR}/env"
|
|
18
36
|
|
|
19
|
-
|
|
37
|
+
host_latex="missing"
|
|
38
|
+
if command -v latexmk >/dev/null 2>&1 && command -v pdflatex >/dev/null 2>&1; then
|
|
39
|
+
host_latex="complete"
|
|
40
|
+
elif command -v latexmk >/dev/null 2>&1 || command -v pdflatex >/dev/null 2>&1; then
|
|
41
|
+
host_latex="partial"
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
run_preflight() {
|
|
45
|
+
docker run --rm \
|
|
46
|
+
--network none \
|
|
47
|
+
--cap-drop ALL \
|
|
48
|
+
--security-opt no-new-privileges \
|
|
49
|
+
--pids-limit 256 \
|
|
50
|
+
--memory 2g \
|
|
51
|
+
--cpus 2 \
|
|
52
|
+
--tmpfs /tmp:rw,nosuid,nodev,size=512m \
|
|
53
|
+
-e HOME=/aginti-home \
|
|
54
|
+
-e XDG_CACHE_HOME=/aginti-cache \
|
|
55
|
+
-e PIP_CACHE_DIR=/aginti-cache/pip \
|
|
56
|
+
-e MPLCONFIGDIR=/tmp/matplotlib \
|
|
57
|
+
-e NPM_CONFIG_CACHE=/aginti-cache/npm \
|
|
58
|
+
-e CONDA_PKGS_DIRS=/aginti-cache/conda-pkgs \
|
|
59
|
+
-v "${WORKSPACE}:/workspace:rw" \
|
|
60
|
+
-v "${STATE_DIR}/home:/aginti-home:rw" \
|
|
61
|
+
-v "${STATE_DIR}/cache:/aginti-cache:rw" \
|
|
62
|
+
-v "${STATE_DIR}/env:/aginti-env:rw" \
|
|
63
|
+
-w /workspace \
|
|
64
|
+
"${IMAGE}" \
|
|
65
|
+
bash -lc 'set -Eeuo pipefail
|
|
66
|
+
mkdir -p /tmp/matplotlib
|
|
67
|
+
if [ ! -x /aginti-env/python/bin/python ]; then python3 -m venv --system-site-packages /aginti-env/python; fi
|
|
68
|
+
. /aginti-env/python/bin/activate
|
|
69
|
+
node -v
|
|
70
|
+
npm -v
|
|
71
|
+
python3 --version
|
|
72
|
+
python3 - <<PY
|
|
73
|
+
import matplotlib, numpy
|
|
74
|
+
print("matplotlib", matplotlib.__version__)
|
|
75
|
+
print("numpy", numpy.__version__)
|
|
76
|
+
PY
|
|
77
|
+
latexmk -version | head -1
|
|
78
|
+
pdflatex --version | head -1
|
|
79
|
+
'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
echo "AgInTiFlow toolchain sandbox:"
|
|
20
83
|
echo " image=${IMAGE}"
|
|
21
84
|
echo " dockerfile=${DOCKERFILE}"
|
|
22
85
|
echo " context=${REPO_ROOT}"
|
|
23
86
|
echo " persistent=${STATE_DIR}"
|
|
87
|
+
echo " platform=${os_name}"
|
|
88
|
+
echo " hostLatex=${host_latex}"
|
|
89
|
+
echo " note=${platform_hint}"
|
|
90
|
+
|
|
91
|
+
if [ "${FORCE_REBUILD}" != "true" ] && docker image inspect "${IMAGE}" >/dev/null 2>&1; then
|
|
92
|
+
echo
|
|
93
|
+
echo "Existing image found. Running preflight before rebuilding:"
|
|
94
|
+
if run_preflight; then
|
|
95
|
+
echo
|
|
96
|
+
echo "Toolchain sandbox is already ready; skipped Docker rebuild and TeX Live redownload."
|
|
97
|
+
exit 0
|
|
98
|
+
fi
|
|
99
|
+
echo
|
|
100
|
+
echo "Existing image failed preflight; rebuilding ${IMAGE}."
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
echo
|
|
104
|
+
echo "Building AgInTiFlow toolchain sandbox image:"
|
|
24
105
|
docker build -t "${IMAGE}" -f "${DOCKERFILE}" "${REPO_ROOT}"
|
|
25
106
|
|
|
26
107
|
echo
|
|
27
108
|
echo "Running toolchain preflight inside Docker:"
|
|
28
|
-
|
|
29
|
-
--network none \
|
|
30
|
-
--cap-drop ALL \
|
|
31
|
-
--security-opt no-new-privileges \
|
|
32
|
-
--pids-limit 256 \
|
|
33
|
-
--memory 2g \
|
|
34
|
-
--cpus 2 \
|
|
35
|
-
--tmpfs /tmp:rw,nosuid,nodev,size=512m \
|
|
36
|
-
-e HOME=/aginti-home \
|
|
37
|
-
-e XDG_CACHE_HOME=/aginti-cache \
|
|
38
|
-
-e PIP_CACHE_DIR=/aginti-cache/pip \
|
|
39
|
-
-e NPM_CONFIG_CACHE=/aginti-cache/npm \
|
|
40
|
-
-e CONDA_PKGS_DIRS=/aginti-cache/conda-pkgs \
|
|
41
|
-
-v "${WORKSPACE}:/workspace:rw" \
|
|
42
|
-
-v "${STATE_DIR}/home:/aginti-home:rw" \
|
|
43
|
-
-v "${STATE_DIR}/cache:/aginti-cache:rw" \
|
|
44
|
-
-v "${STATE_DIR}/env:/aginti-env:rw" \
|
|
45
|
-
-w /workspace \
|
|
46
|
-
"${IMAGE}" \
|
|
47
|
-
bash -lc 'set -Eeuo pipefail
|
|
48
|
-
if [ ! -x /aginti-env/python/bin/python ]; then python3 -m venv --system-site-packages /aginti-env/python; fi
|
|
49
|
-
. /aginti-env/python/bin/activate
|
|
50
|
-
node -v
|
|
51
|
-
npm -v
|
|
52
|
-
python3 --version
|
|
53
|
-
python3 - <<PY
|
|
54
|
-
import matplotlib, numpy
|
|
55
|
-
print("matplotlib", matplotlib.__version__)
|
|
56
|
-
print("numpy", numpy.__version__)
|
|
57
|
-
PY
|
|
58
|
-
latexmk -version | head -1
|
|
59
|
-
pdflatex --version | head -1
|
|
60
|
-
'
|
|
109
|
+
run_preflight
|
|
61
110
|
|
|
62
111
|
echo
|
|
63
112
|
echo "Toolchain sandbox is ready."
|
|
@@ -36,6 +36,8 @@ try {
|
|
|
36
36
|
assert(capabilities.project.commandCwd === tempRoot, "capabilities did not default commandCwd to project root");
|
|
37
37
|
assert(capabilities.project.instructionsPresent, "capabilities did not report AGINTI.md");
|
|
38
38
|
assert(capabilities.project.sharedSessionFolder, "capabilities did not report shared session folder");
|
|
39
|
+
assert(capabilities.platform?.platform, "capabilities did not report platform");
|
|
40
|
+
assert(Array.isArray(capabilities.platform?.setupHints), "capabilities did not report platform setup hints");
|
|
39
41
|
assert(capabilities.keys?.mock === true, "capabilities did not report mock availability");
|
|
40
42
|
assert(typeof capabilities.keys?.qwen === "boolean", "capabilities did not report qwen key status");
|
|
41
43
|
assert(
|
|
@@ -87,6 +87,12 @@ try {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
const promptLayout = buildPromptLayout(`${"x".repeat(180)}\nsecond line`, 95, 80, 24);
|
|
90
|
+
const promptText = promptLayout.renderedRows
|
|
91
|
+
.map((line) => line.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, ""))
|
|
92
|
+
.join("\n");
|
|
93
|
+
if (!promptText.includes("user>")) {
|
|
94
|
+
throw new Error("terminal prompt layout did not render the user> label");
|
|
95
|
+
}
|
|
90
96
|
const visibleLengths = promptLayout.renderedRows.map((line) =>
|
|
91
97
|
line.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "").length
|
|
92
98
|
);
|
|
@@ -172,7 +178,7 @@ try {
|
|
|
172
178
|
{
|
|
173
179
|
ok: true,
|
|
174
180
|
projectRoot: tempRoot,
|
|
175
|
-
checks: ["markdown-render", "markdown-table-no-duplicate", "patch-diff-render", "prompt-layout", "escape-policy", "live-input-status-layout", "agent-response-gutter", "aginti-md", "instructions-command", "skills-command", "instructions-chat-edit", "interactive-chat", "mock-file-write", "run-status", "resume-latest", "resume-history-full"],
|
|
181
|
+
checks: ["markdown-render", "markdown-table-no-duplicate", "patch-diff-render", "prompt-layout", "user-prompt-label", "escape-policy", "live-input-status-layout", "agent-response-gutter", "aginti-md", "instructions-command", "skills-command", "instructions-chat-edit", "interactive-chat", "mock-file-write", "run-status", "resume-latest", "resume-history-full"],
|
|
176
182
|
},
|
|
177
183
|
null,
|
|
178
184
|
2
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { platformInfo, platformLabel, platformSetupHints, hostShellOption } from "../src/platform.js";
|
|
4
|
+
import { buildCapabilityReport } from "../src/capabilities.js";
|
|
5
|
+
import { resolveRuntimeConfig } from "../src/config.js";
|
|
6
|
+
|
|
7
|
+
const info = platformInfo();
|
|
8
|
+
const label = platformLabel(info);
|
|
9
|
+
const hints = platformSetupHints(info);
|
|
10
|
+
|
|
11
|
+
assert(["linux", "darwin", "win32", "freebsd", "openbsd", "aix", "sunos"].includes(info.platform), "unknown platform shape");
|
|
12
|
+
assert(label.includes(info.arch), "platform label should include architecture");
|
|
13
|
+
assert(hints.length > 0, "platform hints should not be empty");
|
|
14
|
+
assert(hostShellOption(), "host shell option should be defined");
|
|
15
|
+
|
|
16
|
+
if (info.isMac) {
|
|
17
|
+
assert(hints.some((hint) => /Docker Desktop|Colima/i.test(hint)), "macOS hints should mention Docker Desktop or Colima");
|
|
18
|
+
assert(hints.some((hint) => /MacTeX|BasicTeX/i.test(hint)), "macOS hints should mention MacTeX or BasicTeX");
|
|
19
|
+
}
|
|
20
|
+
if (info.isWindows) {
|
|
21
|
+
assert(hints.some((hint) => /WSL/i.test(hint)), "Windows hints should recommend WSL");
|
|
22
|
+
}
|
|
23
|
+
if (info.isWsl) {
|
|
24
|
+
assert(label.includes("WSL"), "WSL platform label should mention WSL");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const config = resolveRuntimeConfig({ goal: "platform smoke", provider: "mock" }, { useDockerSandbox: false });
|
|
28
|
+
const report = await buildCapabilityReport(process.cwd(), "0.0.0-smoke", config);
|
|
29
|
+
assert.equal(report.platform.platform, info.platform, "capability report should include platform");
|
|
30
|
+
assert(Array.isArray(report.platform.setupHints), "capability report should include setup hints");
|
|
31
|
+
assert(typeof report.platform.hostLatexAvailable === "boolean", "capability report should include host LaTeX status");
|
|
32
|
+
|
|
33
|
+
console.log(
|
|
34
|
+
JSON.stringify(
|
|
35
|
+
{
|
|
36
|
+
ok: true,
|
|
37
|
+
platform: report.platform.label,
|
|
38
|
+
linuxFamily: report.platform.linuxFamily,
|
|
39
|
+
hostLatexAvailable: report.platform.hostLatexAvailable,
|
|
40
|
+
hints: report.platform.setupHints.length,
|
|
41
|
+
},
|
|
42
|
+
null,
|
|
43
|
+
2
|
|
44
|
+
)
|
|
45
|
+
);
|
package/src/agent-runner.js
CHANGED
|
@@ -23,6 +23,7 @@ import { searchWeb } from "./web-search.js";
|
|
|
23
23
|
import { runParallelScouts, shouldRunParallelScouts } from "./parallel-scouts.js";
|
|
24
24
|
import { readProjectInstructions } from "./project.js";
|
|
25
25
|
import { formatSkillsForPrompt, selectSkillsForGoal } from "./skill-library.js";
|
|
26
|
+
import { hostShellOption, platformInfo, platformLabel } from "./platform.js";
|
|
26
27
|
|
|
27
28
|
const exec = promisify(execCallback);
|
|
28
29
|
const BROWSER_TOOLS = new Set(["open_url", "open_workspace_file", "preview_workspace", "click", "type", "scroll", "press", "back"]);
|
|
@@ -257,6 +258,7 @@ async function createInitialState(config, sessionId) {
|
|
|
257
258
|
const skillContext = formatSkillsForPrompt(selectedSkills);
|
|
258
259
|
const projectInstructions = await readProjectInstructions(config.baseDir || config.commandCwd || process.cwd());
|
|
259
260
|
const projectInstructionContext = formatProjectInstructions(projectInstructions);
|
|
261
|
+
const platform = platformInfo();
|
|
260
262
|
return {
|
|
261
263
|
sessionId,
|
|
262
264
|
createdAt: now,
|
|
@@ -302,7 +304,7 @@ async function createInitialState(config, sessionId) {
|
|
|
302
304
|
config.allowShellTool
|
|
303
305
|
? config.useDockerSandbox
|
|
304
306
|
? `A shell command tool is available inside Docker sandbox mode ${config.sandboxMode}. Docker workspace mode with approved package installs supports broader setup and network commands. The project is mounted at /workspace and the persistent agent toolchain is mounted at /aginti-env with caches under /aginti-cache.`
|
|
305
|
-
:
|
|
307
|
+
: `A host shell command tool is available under the configured trust policy on ${platformLabel(platform)}. On native Windows, prefer PowerShell/cmd-compatible commands or switch to WSL/Docker for bash-like toolchains.`
|
|
306
308
|
: "No shell command tool is available.",
|
|
307
309
|
config.allowFileTools
|
|
308
310
|
? `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.`
|
|
@@ -329,6 +331,7 @@ async function createInitialState(config, sessionId) {
|
|
|
329
331
|
"Work like a practical coding agent: orient with inspect_project/search/read, patch code with apply_patch, run safe checks when they add confidence, iterate on failures, and keep outputs inside the workspace.",
|
|
330
332
|
"For large projects, decompose into useful files and milestones, identify entry points/tests/contracts first, implement a coherent minimal version, then iterate with checks rather than only describing what you would do.",
|
|
331
333
|
"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.",
|
|
334
|
+
"For LaTeX/PDF tasks, check existing latexmk/pdflatex first and compile with the available host or Docker TeX toolchain before installing packages or rebuilding the sandbox.",
|
|
332
335
|
"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.",
|
|
333
336
|
"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.",
|
|
334
337
|
"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.",
|
|
@@ -476,6 +479,7 @@ async function applyContinuationPrompt(state, config, observers) {
|
|
|
476
479
|
state.plan = "";
|
|
477
480
|
state.stepsCompleted = 0;
|
|
478
481
|
state.updatedAt = new Date().toISOString();
|
|
482
|
+
const platform = platformInfo();
|
|
479
483
|
state.messages.push({
|
|
480
484
|
role: "user",
|
|
481
485
|
content: [
|
|
@@ -485,7 +489,7 @@ async function applyContinuationPrompt(state, config, observers) {
|
|
|
485
489
|
config.allowShellTool
|
|
486
490
|
? config.useDockerSandbox
|
|
487
491
|
? `Shell working directory mounted into Docker as /workspace from ${config.commandCwd}. Use relative paths or /workspace paths. Persistent Docker env: /aginti-env, caches: /aginti-cache. Sandbox mode: ${config.sandboxMode}. Package install policy: ${config.packageInstallPolicy}.`
|
|
488
|
-
: `Shell working directory: ${config.commandCwd}
|
|
492
|
+
: `Shell working directory: ${config.commandCwd}. Host platform: ${platformLabel(platform)}. Use OS-compatible commands; prefer WSL/Docker for bash-heavy workflows on Windows.`
|
|
489
493
|
: "",
|
|
490
494
|
config.allowFileTools
|
|
491
495
|
? `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.`
|
|
@@ -658,7 +662,7 @@ async function runShellCommand(command, config, policy = evaluateCommandPolicy(c
|
|
|
658
662
|
cwd: config.commandCwd,
|
|
659
663
|
timeout: 30000,
|
|
660
664
|
maxBuffer: 200 * 1024,
|
|
661
|
-
shell:
|
|
665
|
+
shell: hostShellOption(),
|
|
662
666
|
env: safeExecutionEnv(),
|
|
663
667
|
signal: config.abortSignal,
|
|
664
668
|
});
|
|
@@ -681,6 +685,7 @@ async function runShellCommand(command, config, policy = evaluateCommandPolicy(c
|
|
|
681
685
|
}
|
|
682
686
|
|
|
683
687
|
async function captureSyntheticSnapshot(store, step, config) {
|
|
688
|
+
const platform = platformInfo();
|
|
684
689
|
const snapshot = {
|
|
685
690
|
title: "No browser page open",
|
|
686
691
|
url: "",
|
|
@@ -690,7 +695,7 @@ async function captureSyntheticSnapshot(store, step, config) {
|
|
|
690
695
|
config.allowShellTool
|
|
691
696
|
? config.useDockerSandbox
|
|
692
697
|
? `Shell tool available in Docker with mounted workspace /workspace from ${config.commandCwd}. Use relative paths or /workspace paths. Persistent Docker env: /aginti-env, caches: /aginti-cache. Sandbox mode: ${config.sandboxMode}. Package install policy: ${config.packageInstallPolicy}.`
|
|
693
|
-
: `Shell tool available in: ${config.commandCwd}
|
|
698
|
+
: `Shell tool available in: ${config.commandCwd} on ${platformLabel(platform)}. Use OS-compatible commands; prefer WSL/Docker for bash-heavy workflows on Windows.`
|
|
694
699
|
: "Shell tool disabled.",
|
|
695
700
|
config.allowFileTools
|
|
696
701
|
? `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.`
|
|
@@ -700,7 +705,7 @@ async function captureSyntheticSnapshot(store, step, config) {
|
|
|
700
705
|
: "Agent wrappers disabled.",
|
|
701
706
|
"Canvas/artifacts tunnel available through send_to_canvas.",
|
|
702
707
|
"For draw/plot/graph/chart/diagram/figure requests, publish a canvas artifact proactively.",
|
|
703
|
-
"For LaTeX/PDF requests, publish the source and compiled PDF artifacts when available
|
|
708
|
+
"For LaTeX/PDF requests, check latexmk/pdflatex first, publish the source and compiled PDF artifacts when available, and avoid reinstalling TeX when an existing toolchain works.",
|
|
704
709
|
"Use open_url only if the task actually needs the web.",
|
|
705
710
|
"For generated local HTML/SVG/PDF/site output, use open_workspace_file or preview_workspace instead of shelling a transient local server.",
|
|
706
711
|
]
|
package/src/capabilities.js
CHANGED
|
@@ -10,6 +10,7 @@ import { listAgentWrappers } from "./tool-wrappers.js";
|
|
|
10
10
|
import { listAuxiliarySkills } from "./auxiliary-tools.js";
|
|
11
11
|
import { readCodebaseMap } from "./codebase-map.js";
|
|
12
12
|
import { listSkills } from "./skill-library.js";
|
|
13
|
+
import { platformInfo, platformLabel, platformSetupHints } from "./platform.js";
|
|
13
14
|
|
|
14
15
|
const execFileAsync = promisify(execFile);
|
|
15
16
|
|
|
@@ -112,19 +113,28 @@ function trustedDockerPolicyChecks(config) {
|
|
|
112
113
|
export async function buildCapabilityReport(projectRoot, packageVersion, config) {
|
|
113
114
|
const paths = projectPaths(projectRoot);
|
|
114
115
|
const keyStatus = providerKeyStatus(projectRoot);
|
|
115
|
-
const
|
|
116
|
+
const platform = platformInfo();
|
|
117
|
+
const commandChecks = [
|
|
116
118
|
commandAvailable("node", ["--version"]),
|
|
117
119
|
commandAvailable("npm", ["--version"]),
|
|
118
|
-
commandAvailable("python3", ["--version"]),
|
|
120
|
+
commandAvailable(platform.isWindows ? "python" : "python3", ["--version"]),
|
|
119
121
|
commandAvailable("conda", ["--version"]),
|
|
120
122
|
commandAvailable("R", ["--version"]),
|
|
121
123
|
commandAvailable("pdflatex", ["--version"]),
|
|
122
124
|
commandAvailable("latexmk", ["--version"]),
|
|
125
|
+
];
|
|
126
|
+
if (platform.isMac) commandChecks.push(commandAvailable("brew", ["--version"]));
|
|
127
|
+
if (platform.isWindows) commandChecks.push(commandAvailable("wsl", ["--status"]));
|
|
128
|
+
const [commands, dockerStatus, sessions, instructions, codebaseMap] = await Promise.all([
|
|
129
|
+
Promise.all(commandChecks),
|
|
123
130
|
getDockerSandboxStatus(config).catch((error) => ({ ok: false, error: error.message })),
|
|
124
131
|
listProjectSessions(projectRoot, 12),
|
|
125
132
|
readProjectInstructions(projectRoot, { maxBytes: 1 }),
|
|
126
133
|
readCodebaseMap(projectRoot),
|
|
127
134
|
]);
|
|
135
|
+
const [node, npm, python, conda, r, pdflatex, latexmk] = commands;
|
|
136
|
+
const homebrew = platform.isMac ? commands[7] : undefined;
|
|
137
|
+
const wsl = platform.isWindows ? commands[7] : undefined;
|
|
128
138
|
|
|
129
139
|
const npmPrefixPolicy = evaluateCommandPolicy("npm --prefix round9-node-app test", config);
|
|
130
140
|
const cdNpmTestPolicy = evaluateCommandPolicy("cd round9-node-app && npm test", config);
|
|
@@ -144,6 +154,8 @@ export async function buildCapabilityReport(projectRoot, packageVersion, config)
|
|
|
144
154
|
capability("R", r.available, r.available ? r : { ...r, setup: "Optional. Generate a project-local R setup plan; do not install globally from the agent." }),
|
|
145
155
|
capability("pdflatex", pdflatex.available, pdflatex.available ? pdflatex : { ...pdflatex, setup: "LaTeX tasks should create .tex source and an honest setup report when TeX is unavailable." }),
|
|
146
156
|
capability("latexmk", latexmk.available, latexmk.available ? latexmk : { ...latexmk, setup: "latexmk is optional if pdflatex is available." }),
|
|
157
|
+
...(platform.isMac ? [capability("homebrew", homebrew?.available, homebrew?.available ? homebrew : { ...homebrew, setup: "Optional on macOS for host-mode tool installs." })] : []),
|
|
158
|
+
...(platform.isWindows ? [capability("wsl", wsl?.available, wsl?.available ? wsl : { ...wsl, setup: "Recommended for Windows. Install WSL2 for the most compatible shell and Docker workflow." })] : []),
|
|
147
159
|
capability("docker", Boolean(dockerStatus?.dockerAvailable), dockerStatus || {}),
|
|
148
160
|
capability("deepseek-key", keyStatus.deepseek, { envVars: keyStatus.envVars.deepseek }),
|
|
149
161
|
capability("openai-key", keyStatus.openai, { envVars: keyStatus.envVars.openai }),
|
|
@@ -191,6 +203,13 @@ export async function buildCapabilityReport(projectRoot, packageVersion, config)
|
|
|
191
203
|
summary: codebaseMap.ok ? codebaseMap.map.inspection?.summary || "" : "",
|
|
192
204
|
},
|
|
193
205
|
},
|
|
206
|
+
platform: {
|
|
207
|
+
...platform,
|
|
208
|
+
label: platformLabel(platform),
|
|
209
|
+
setupHints: platformSetupHints(platform),
|
|
210
|
+
hostLatexAvailable: Boolean(pdflatex.available || latexmk.available),
|
|
211
|
+
hostLatexComplete: Boolean(pdflatex.available && latexmk.available),
|
|
212
|
+
},
|
|
194
213
|
routing: {
|
|
195
214
|
active: {
|
|
196
215
|
provider: config.provider,
|
|
@@ -256,6 +275,7 @@ export async function buildCapabilityReport(projectRoot, packageVersion, config)
|
|
|
256
275
|
|
|
257
276
|
export function printCapabilityReport(report) {
|
|
258
277
|
console.log(`AgInTiFlow capabilities ${report.package.version}`);
|
|
278
|
+
console.log(`platform=${report.platform.label} family=${report.platform.linuxFamily || report.platform.platform}`);
|
|
259
279
|
console.log(`project=${report.project.root}`);
|
|
260
280
|
console.log(`cwd=${report.project.commandCwd}`);
|
|
261
281
|
console.log(`instructions=${report.project.instructionsPath} present=${report.project.instructionsPresent}`);
|
|
@@ -286,4 +306,8 @@ export function printCapabilityReport(report) {
|
|
|
286
306
|
console.log(`- ${item.capability}: ${item.setup}`);
|
|
287
307
|
}
|
|
288
308
|
}
|
|
309
|
+
if (report.platform?.setupHints?.length) {
|
|
310
|
+
console.log("platform setup hints:");
|
|
311
|
+
for (const hint of report.platform.setupHints) console.log(`- ${hint}`);
|
|
312
|
+
}
|
|
289
313
|
}
|
package/src/cli.js
CHANGED
|
@@ -349,6 +349,7 @@ function printInitResult(result) {
|
|
|
349
349
|
function printDoctorReport(report) {
|
|
350
350
|
console.log(`AgInTiFlow ${report.package.version} (npm latest: ${report.package.npmLatest})`);
|
|
351
351
|
console.log(`node=${report.node.version} ok=${report.node.ok}`);
|
|
352
|
+
console.log(`platform=${report.platform.label} family=${report.platform.linuxFamily || report.platform.platform}`);
|
|
352
353
|
console.log(`project=${report.project.root}`);
|
|
353
354
|
console.log(`instructions=${report.project.instructionsPath} present=${report.project.instructionsPresent}`);
|
|
354
355
|
console.log(`sessions=${report.project.sessionsDir}`);
|
|
@@ -369,6 +370,10 @@ function printDoctorReport(report) {
|
|
|
369
370
|
`wrappers=${report.wrappers.map((wrapper) => `${wrapper.name}:${wrapper.available ? "ok" : "missing"}`).join(" ")}`
|
|
370
371
|
);
|
|
371
372
|
console.log(`sessions=${report.sessions.length}`);
|
|
373
|
+
if (report.platform.setupHints.length > 0) {
|
|
374
|
+
console.log("platform setup hints:");
|
|
375
|
+
for (const hint of report.platform.setupHints) console.log(`- ${hint}`);
|
|
376
|
+
}
|
|
372
377
|
}
|
|
373
378
|
|
|
374
379
|
async function readStdin() {
|
package/src/command-policy.js
CHANGED
|
@@ -84,7 +84,12 @@ const PACKAGE_INSTALL_PATTERNS = [
|
|
|
84
84
|
const SYSTEM_PACKAGE_INSTALL_PATTERNS = [
|
|
85
85
|
/^(?:sudo\s+)?apt(?:-get)?\s+update$/,
|
|
86
86
|
/^(?:sudo\s+)?apt(?:-get)?\s+install(?:\s+-y)?(?:\s+[-@\w.+:=]+)+$/,
|
|
87
|
+
/^(?:sudo\s+)?(?:dnf|yum)\s+(?:makecache|check-update)(?:\s+[-\w]+)*$/,
|
|
88
|
+
/^(?:sudo\s+)?(?:dnf|yum)\s+install(?:\s+-y)?(?:\s+[-@\w.+:=]+)+$/,
|
|
87
89
|
/^apk\s+add(?:\s+--no-cache)?(?:\s+[-@\w.+:=]+)+$/,
|
|
90
|
+
/^brew\s+install(?:\s+[-@\w.+:=/]+)+$/,
|
|
91
|
+
/^winget\s+install(?:\s+[-@\w.+:=/]+)+$/,
|
|
92
|
+
/^choco\s+install(?:\s+[-@\w.+:=/]+)+$/,
|
|
88
93
|
];
|
|
89
94
|
|
|
90
95
|
const ENV_SETUP_PATTERNS = [
|
package/src/docker-sandbox.js
CHANGED
|
@@ -214,6 +214,7 @@ function dockerCommand(command, policy) {
|
|
|
214
214
|
`export HOME=${DOCKER_HOME}`,
|
|
215
215
|
`export XDG_CACHE_HOME=${DOCKER_CACHE}`,
|
|
216
216
|
`export PIP_CACHE_DIR=${DOCKER_CACHE}/pip`,
|
|
217
|
+
`export MPLCONFIGDIR=/tmp/matplotlib`,
|
|
217
218
|
`export UV_CACHE_DIR=${DOCKER_CACHE}/uv`,
|
|
218
219
|
`export NPM_CONFIG_CACHE=${DOCKER_CACHE}/npm`,
|
|
219
220
|
`export CONDA_PKGS_DIRS=${DOCKER_CACHE}/conda-pkgs`,
|
|
@@ -225,6 +226,7 @@ function dockerCommand(command, policy) {
|
|
|
225
226
|
|
|
226
227
|
if (!policy.requiresDockerRoot) {
|
|
227
228
|
envLines.push(
|
|
229
|
+
`mkdir -p /tmp/matplotlib >/dev/null 2>&1 || true`,
|
|
228
230
|
`if command -v python3 >/dev/null 2>&1 && [ ! -x ${DOCKER_ENV}/python/bin/python ]; then python3 -m venv --system-site-packages ${DOCKER_ENV}/python >/dev/null 2>&1 || true; fi`,
|
|
229
231
|
`if [ -f ${DOCKER_ENV}/python/bin/activate ]; then . ${DOCKER_ENV}/python/bin/activate; fi`
|
|
230
232
|
);
|
package/src/interactive-cli.js
CHANGED
|
@@ -542,7 +542,7 @@ export function buildPromptLayout(buffer = "", cursor = 0, width = terminalWidth
|
|
|
542
542
|
const safeBuffer = String(buffer || "");
|
|
543
543
|
const safeCursor = clamp(Number(cursor) || 0, 0, safeBuffer.length);
|
|
544
544
|
const lineWidth = editorWidth(width);
|
|
545
|
-
const firstPrefix = " user ";
|
|
545
|
+
const firstPrefix = " user> ";
|
|
546
546
|
const nextPrefix = " ... ";
|
|
547
547
|
const firstInnerWidth = Math.max(lineWidth - firstPrefix.length, 8);
|
|
548
548
|
const nextInnerWidth = Math.max(lineWidth - nextPrefix.length, 8);
|
|
@@ -705,43 +705,16 @@ function clearRenderedPrompt(previous) {
|
|
|
705
705
|
}
|
|
706
706
|
}
|
|
707
707
|
|
|
708
|
-
function moveFromPromptCursorToTop(previous) {
|
|
709
|
-
if (!previous.lineCount) return;
|
|
710
|
-
const below = previous.lineCount - 1 - previous.cursorRow;
|
|
711
|
-
if (below > 0) output.write(`\x1b[${below}B`);
|
|
712
|
-
const up = previous.lineCount - 1;
|
|
713
|
-
if (up > 0) output.write(`\x1b[${up}A`);
|
|
714
|
-
output.write("\r");
|
|
715
|
-
}
|
|
716
|
-
|
|
717
708
|
function renderPromptBuffer(buffer, cursor, previous = { lineCount: 0, cursorRow: 0, renderedRows: [] }, options = {}) {
|
|
718
709
|
const layout = buildPromptLayout(buffer, cursor, terminalWidth(), terminalHeight(), options);
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
.map((row, index) => (row === previous.renderedRows[index] ? -1 : index))
|
|
723
|
-
.filter((index) => index >= 0)
|
|
724
|
-
: layout.renderedRows.map((_row, index) => index);
|
|
725
|
-
|
|
726
|
-
if (!sameShape) {
|
|
727
|
-
output.write(ansi.cursorHide);
|
|
728
|
-
clearRenderedPrompt(previous);
|
|
729
|
-
output.write(layout.renderedRows.join("\n"));
|
|
730
|
-
} else if (changedRows.length > 0) {
|
|
731
|
-
output.write(ansi.cursorHide);
|
|
732
|
-
moveFromPromptCursorToTop(previous);
|
|
733
|
-
for (let index = 0; index < layout.renderedRows.length; index += 1) {
|
|
734
|
-
if (changedRows.includes(index)) output.write(`\r${layout.renderedRows[index]}`);
|
|
735
|
-
if (index < layout.renderedRows.length - 1) output.write("\x1b[1B\r");
|
|
736
|
-
}
|
|
737
|
-
} else {
|
|
738
|
-
moveFromPromptCursorToTop(previous);
|
|
739
|
-
}
|
|
710
|
+
output.write(ansi.cursorHide);
|
|
711
|
+
clearRenderedPrompt(previous);
|
|
712
|
+
output.write(layout.renderedRows.join("\n"));
|
|
740
713
|
|
|
741
714
|
const below = layout.renderedRows.length - 1 - layout.cursorRow;
|
|
742
715
|
if (below > 0) output.write(`\x1b[${below}A`);
|
|
743
716
|
output.write(`\r\x1b[${layout.cursorColumn + 1}G`);
|
|
744
|
-
|
|
717
|
+
output.write(ansi.cursorShow);
|
|
745
718
|
return {
|
|
746
719
|
lineCount: layout.renderedRows.length,
|
|
747
720
|
cursorRow: layout.cursorRow,
|
package/src/model-client.js
CHANGED
|
@@ -4,6 +4,7 @@ import { getTaskProfile } from "./task-profiles.js";
|
|
|
4
4
|
import { listAuxiliarySkills } from "./auxiliary-tools.js";
|
|
5
5
|
import { engineeringGuidanceForTask } from "./engineering-guidance.js";
|
|
6
6
|
import { formatSkillsForPrompt, selectSkillsForGoal } from "./skill-library.js";
|
|
7
|
+
import { platformInfo, platformLabel } from "./platform.js";
|
|
7
8
|
|
|
8
9
|
export function createClient(config) {
|
|
9
10
|
if (config.provider === "mock") {
|
|
@@ -197,6 +198,7 @@ export async function createPlan(client, config, state) {
|
|
|
197
198
|
const selectedSkills = selectSkillsForGoal(state.goal, { taskProfile: config.taskProfile, limit: 5 });
|
|
198
199
|
const skillContext = formatSkillsForPrompt(selectedSkills);
|
|
199
200
|
const projectInstructions = state.meta?.projectInstructions;
|
|
201
|
+
const platform = platformInfo();
|
|
200
202
|
if (client.mock) {
|
|
201
203
|
return [
|
|
202
204
|
"1. Inspect the request and prefer the local shell when available.",
|
|
@@ -222,7 +224,7 @@ export async function createPlan(client, config, state) {
|
|
|
222
224
|
state.startUrl ? `Suggested start URL: ${state.startUrl}` : "",
|
|
223
225
|
config.allowedDomains.length > 0 ? `Allowed domains: ${config.allowedDomains.join(", ")}` : "",
|
|
224
226
|
config.allowShellTool
|
|
225
|
-
? `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.`
|
|
227
|
+
? `Shell tool is enabled in ${config.commandCwd}. Host platform: ${platformLabel(platform)}. 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. On native Windows host mode, prefer PowerShell/cmd-compatible commands or WSL/Docker for bash-like toolchains.`
|
|
226
228
|
: "",
|
|
227
229
|
config.allowFileTools
|
|
228
230
|
? `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.`
|
|
@@ -250,6 +252,7 @@ export async function createPlan(client, config, state) {
|
|
|
250
252
|
"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.",
|
|
251
253
|
"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.",
|
|
252
254
|
"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.",
|
|
255
|
+
"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.",
|
|
253
256
|
"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.",
|
|
254
257
|
"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.",
|
|
255
258
|
"For environment or system-maintenance work, prefer project-local dry-run plans/scripts unless the configured policy explicitly allows stronger actions.",
|
package/src/platform.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
|
|
4
|
+
function parseOsRelease() {
|
|
5
|
+
if (process.platform !== "linux") return {};
|
|
6
|
+
try {
|
|
7
|
+
const content = fs.readFileSync("/etc/os-release", "utf8");
|
|
8
|
+
const data = {};
|
|
9
|
+
for (const line of content.split(/\r?\n/)) {
|
|
10
|
+
const match = line.match(/^([A-Z0-9_]+)=(.*)$/);
|
|
11
|
+
if (!match) continue;
|
|
12
|
+
data[match[1].toLowerCase()] = match[2].replace(/^"|"$/g, "");
|
|
13
|
+
}
|
|
14
|
+
return data;
|
|
15
|
+
} catch {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function platformInfo() {
|
|
21
|
+
const osRelease = parseOsRelease();
|
|
22
|
+
const release = os.release();
|
|
23
|
+
const isWsl = Boolean(process.env.WSL_DISTRO_NAME) || /microsoft|wsl/i.test(release);
|
|
24
|
+
const linuxId = osRelease.id || "";
|
|
25
|
+
const linuxLike = osRelease.id_like || "";
|
|
26
|
+
const linuxFamily = /ubuntu|debian/i.test(`${linuxId} ${linuxLike}`)
|
|
27
|
+
? "debian"
|
|
28
|
+
: /rhel|fedora|centos|rocky|almalinux|redhat/i.test(`${linuxId} ${linuxLike}`)
|
|
29
|
+
? "redhat"
|
|
30
|
+
: process.platform === "linux"
|
|
31
|
+
? "linux"
|
|
32
|
+
: "";
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
platform: process.platform,
|
|
36
|
+
arch: process.arch,
|
|
37
|
+
release,
|
|
38
|
+
type: os.type(),
|
|
39
|
+
distro: osRelease.pretty_name || osRelease.name || "",
|
|
40
|
+
linuxId,
|
|
41
|
+
linuxFamily,
|
|
42
|
+
isMac: process.platform === "darwin",
|
|
43
|
+
isLinux: process.platform === "linux",
|
|
44
|
+
isWindows: process.platform === "win32",
|
|
45
|
+
isWsl,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function platformLabel(info = platformInfo()) {
|
|
50
|
+
if (info.isWsl) return `WSL ${info.distro || info.linuxId || "Linux"} (${info.arch})`;
|
|
51
|
+
if (info.isMac) return `macOS (${info.arch})`;
|
|
52
|
+
if (info.isWindows) return `Windows (${info.arch})`;
|
|
53
|
+
if (info.isLinux) return `${info.distro || "Linux"} (${info.arch})`;
|
|
54
|
+
return `${info.platform} (${info.arch})`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function platformSetupHints(info = platformInfo()) {
|
|
58
|
+
if (info.isWsl) {
|
|
59
|
+
return [
|
|
60
|
+
"Use the Linux/WSL shell as the primary AgInTiFlow environment; keep projects under the WSL filesystem for best file and Docker performance.",
|
|
61
|
+
"Use Docker Desktop with WSL integration or Docker Engine inside WSL for docker-workspace mode.",
|
|
62
|
+
"For LaTeX, reuse WSL latexmk/pdflatex when installed, or use the companion Docker sandbox image.",
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
if (info.isMac) {
|
|
66
|
+
return [
|
|
67
|
+
"Use Node.js 22+ from Homebrew, nvm, fnm, or the official installer.",
|
|
68
|
+
"Use Docker Desktop or Colima for docker-workspace mode; the Ubuntu Docker installer is intentionally not used on macOS.",
|
|
69
|
+
"For LaTeX, reuse MacTeX/BasicTeX when latexmk and pdflatex are on PATH; otherwise use the companion Docker sandbox image.",
|
|
70
|
+
"Use Homebrew for optional host tools such as ripgrep, git, python, and latexmk when you choose host mode.",
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
if (info.isWindows) {
|
|
74
|
+
return [
|
|
75
|
+
"Best supported Windows path: run AgInTiFlow inside WSL2 with Node.js 22+ and Docker Desktop WSL integration.",
|
|
76
|
+
"Native Windows host shell is best effort; prefer docker-workspace or WSL for bash-like coding, LaTeX, and package-manager tasks.",
|
|
77
|
+
"For LaTeX, use Docker/WSL, or install MiKTeX/TeX Live and ensure pdflatex/latexmk are on PATH.",
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
if (info.linuxFamily === "debian") {
|
|
81
|
+
return [
|
|
82
|
+
"Use Node.js 22+ from nvm/fnm, NodeSource, or distro packages.",
|
|
83
|
+
"Docker can be installed with scripts/install-docker-ubuntu.sh on Ubuntu/Debian-like hosts.",
|
|
84
|
+
"For LaTeX, reuse host latexmk/pdflatex when available; otherwise use the companion Docker sandbox image.",
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
if (info.linuxFamily === "redhat") {
|
|
88
|
+
return [
|
|
89
|
+
"Use Node.js 22+ from nvm/fnm or the Red Hat/Fedora package stream.",
|
|
90
|
+
"Install Docker/Podman-compatible Docker CLI with dnf/yum or vendor docs; do not use the Ubuntu Docker installer.",
|
|
91
|
+
"For LaTeX, reuse host latexmk/pdflatex from texlive packages when available; otherwise use the companion Docker sandbox image.",
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
return [
|
|
95
|
+
"Use Node.js 22+ and prefer docker-workspace for portable shell/toolchain work.",
|
|
96
|
+
"For LaTeX, reuse host latexmk/pdflatex when available; otherwise use the companion Docker sandbox image.",
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function hostShellOption() {
|
|
101
|
+
if (process.platform === "win32") return process.env.ComSpec || true;
|
|
102
|
+
return process.env.SHELL || "/bin/bash";
|
|
103
|
+
}
|
package/src/project.js
CHANGED
|
@@ -5,6 +5,7 @@ import { execFile } from "node:child_process";
|
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import { listAgentWrappers } from "./tool-wrappers.js";
|
|
7
7
|
import { getDockerSandboxStatus } from "./docker-sandbox.js";
|
|
8
|
+
import { platformInfo, platformLabel, platformSetupHints } from "./platform.js";
|
|
8
9
|
|
|
9
10
|
const execFileAsync = promisify(execFile);
|
|
10
11
|
const LOCAL_ENV_KEYS = new Set([
|
|
@@ -390,6 +391,7 @@ export async function npmLatestVersion(packageName = "@lazyingart/agintiflow") {
|
|
|
390
391
|
export async function doctorReport(projectRoot, packageVersion, config) {
|
|
391
392
|
const paths = projectPaths(projectRoot);
|
|
392
393
|
const keyStatus = providerKeyStatus(projectRoot);
|
|
394
|
+
const platform = platformInfo();
|
|
393
395
|
const [sessions, dockerStatus, latestVersion, instructions] = await Promise.all([
|
|
394
396
|
listProjectSessions(projectRoot, 8),
|
|
395
397
|
getDockerSandboxStatus(config).catch((error) => ({ ok: false, error: error.message })),
|
|
@@ -408,6 +410,11 @@ export async function doctorReport(projectRoot, packageVersion, config) {
|
|
|
408
410
|
version: process.version,
|
|
409
411
|
ok: Number(process.versions.node.split(".")[0]) >= 22,
|
|
410
412
|
},
|
|
413
|
+
platform: {
|
|
414
|
+
...platform,
|
|
415
|
+
label: platformLabel(platform),
|
|
416
|
+
setupHints: platformSetupHints(platform),
|
|
417
|
+
},
|
|
411
418
|
project: {
|
|
412
419
|
root: paths.root,
|
|
413
420
|
instructionsPath: paths.agintiInstructionsPath,
|
package/src/task-profiles.js
CHANGED
|
@@ -80,7 +80,7 @@ export const TASK_PROFILES = {
|
|
|
80
80
|
id: "latex",
|
|
81
81
|
label: "LaTeX",
|
|
82
82
|
prompt:
|
|
83
|
-
"Bias toward LaTeX/PDF production while still using writing, plotting, code, and web research when needed. 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 outputs and /aginti-env for persistent tools when setup is needed.",
|
|
83
|
+
"Bias toward LaTeX/PDF production while still using writing, plotting, code, and web research when needed. Locate or create source and figures in a subfolder, check existing latexmk/pdflatex before installing or rebuilding toolchains, 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 outputs and /aginti-env for persistent tools only when setup is actually needed.",
|
|
84
84
|
tools: ["files", "shell", "canvas", "sandbox"],
|
|
85
85
|
},
|
|
86
86
|
maintenance: {
|
package/src/tool-wrappers.js
CHANGED
|
@@ -16,7 +16,11 @@ const BASE_ADVISORY_PROMPT = [
|
|
|
16
16
|
|
|
17
17
|
function commandExists(command) {
|
|
18
18
|
try {
|
|
19
|
-
|
|
19
|
+
if (process.platform === "win32") {
|
|
20
|
+
execFileSync("where", [command], { stdio: "ignore" });
|
|
21
|
+
} else {
|
|
22
|
+
execFileSync("sh", ["-lc", `command -v ${command}`], { stdio: "ignore" });
|
|
23
|
+
}
|
|
20
24
|
return true;
|
|
21
25
|
} catch {
|
|
22
26
|
return false;
|
package/web.js
CHANGED
|
@@ -15,6 +15,7 @@ import { listTaskProfiles, normalizeTaskProfile } from "./src/task-profiles.js";
|
|
|
15
15
|
import { loadProjectEnv, projectPaths, providerKeyStatus, setProviderKey } from "./src/project.js";
|
|
16
16
|
import { buildCapabilityReport } from "./src/capabilities.js";
|
|
17
17
|
import { listSkills } from "./src/skill-library.js";
|
|
18
|
+
import { platformInfo, platformLabel, platformSetupHints } from "./src/platform.js";
|
|
18
19
|
import {
|
|
19
20
|
buildArtifacts,
|
|
20
21
|
countUnreadArtifacts,
|
|
@@ -559,6 +560,7 @@ app.get("/api/config", async (_req, res) => {
|
|
|
559
560
|
const config = buildRunConfig({ ...preferences, goal: "" });
|
|
560
561
|
const paths = projectPaths(baseDir);
|
|
561
562
|
const keyStatus = publicKeyStatus(baseDir);
|
|
563
|
+
const platform = platformInfo();
|
|
562
564
|
res.json({
|
|
563
565
|
project: {
|
|
564
566
|
root: paths.root,
|
|
@@ -567,6 +569,11 @@ app.get("/api/config", async (_req, res) => {
|
|
|
567
569
|
sessionDbPath: paths.sessionDbPath,
|
|
568
570
|
sharedSessionFolder: path.resolve(config.sessionsDir) === path.resolve(sessionsDir),
|
|
569
571
|
localEnvPresent: keyStatus.localEnv,
|
|
572
|
+
platform: {
|
|
573
|
+
...platform,
|
|
574
|
+
label: platformLabel(platform),
|
|
575
|
+
setupHints: platformSetupHints(platform),
|
|
576
|
+
},
|
|
570
577
|
},
|
|
571
578
|
defaults: {
|
|
572
579
|
openai: publicProviderDefault("openai"),
|