@lazyingart/agintiflow 0.20.124 → 0.20.126

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 CHANGED
@@ -114,6 +114,16 @@ aginti web --port 3210
114
114
  # opens http://127.0.0.1:3210, or the next available port
115
115
  ```
116
116
 
117
+ Check or prepare the Docker/LaTeX sandbox explicitly:
118
+
119
+ ```bash
120
+ aginti docker status
121
+ aginti docker setup
122
+ aginti docker install-host --yes # Ubuntu host only; explicit opt-in
123
+ ```
124
+
125
+ AgInTiFlow does not silently install Docker during npm postinstall. Docker changes host services and permissions, so host installation is explicit. Normal mode can install packages and TeX/Python dependencies inside the Docker sandbox. Danger mode is trusted host mode for tasks that really need host package managers or system services.
126
+
117
127
  Run without live model credentials for smoke tests:
118
128
 
119
129
  ```bash
@@ -43,6 +43,15 @@ Docker package installs are safe when they match the sandbox contract.
43
43
  - OS package installs such as `apt-get install htop` affect only the current short-lived command container unless the Docker image is rebuilt.
44
44
  - To make OS packages portable, add them to `docker/sandbox.Dockerfile` and rebuild with `scripts/setup-agent-toolchain-docker.sh`.
45
45
 
46
+ Use the built-in Docker setup command for the normal sandbox/toolchain path:
47
+
48
+ ```bash
49
+ aginti docker status
50
+ aginti docker setup
51
+ ```
52
+
53
+ `aginti docker setup` builds or verifies the companion sandbox image and checks Node, npm, Python, matplotlib/numpy, `latexmk`, `pdflatex`, git, and ripgrep inside Docker. If host Docker itself is missing, AgInTiFlow prints platform-specific install guidance. `aginti docker install-host --yes` is only an explicit opt-in helper for supported Ubuntu hosts; npm postinstall does not silently install Docker or mutate host services.
54
+
46
55
  This is why a task can safely install Python packages in Docker, but cannot keep an interactive tmux server alive inside a one-shot Docker command.
47
56
 
48
57
  ## Permission Contract
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.124",
3
+ "version": "0.20.126",
4
4
  "type": "module",
5
5
  "description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
6
6
  "license": "Apache-2.0",
@@ -57,6 +57,7 @@
57
57
  "scripts/smoke-canvas-artifacts.js",
58
58
  "scripts/smoke-cli-chat.js",
59
59
  "scripts/smoke-coding-tools.js",
60
+ "scripts/smoke-docker-command.js",
60
61
  "scripts/smoke-dynamic-step-budget.js",
61
62
  "scripts/smoke-capabilities.js",
62
63
  "scripts/smoke-auto-update.js",
@@ -94,6 +95,7 @@
94
95
  "smoke:auth": "node scripts/smoke-auth.js",
95
96
  "smoke:canvas-artifacts": "node scripts/smoke-canvas-artifacts.js",
96
97
  "smoke:cli-chat": "node scripts/smoke-cli-chat.js",
98
+ "smoke:docker-command": "node scripts/smoke-docker-command.js",
97
99
  "smoke:skills": "node scripts/smoke-skills.js",
98
100
  "smoke:skillmesh": "node scripts/smoke-skillmesh.js",
99
101
  "smoke:toolchain-docker": "node scripts/smoke-toolchain-docker.js",
@@ -112,7 +114,7 @@
112
114
  "postinstall": "node scripts/postinstall-webapp.js",
113
115
  "supervision:seed": "node scripts/seed-supervised-homework.js",
114
116
  "storage:migrate": "node bin/aginti-cli.js storage migrate",
115
- "test": "npm run check && npm run smoke:autoupdate && npm run smoke:web-api && npm run smoke:web-autostart && npm run smoke:webapp-command && npm run smoke:web-port-fallback && npm run smoke:coding-tools && npm run smoke:dynamic-step-budget && npm run smoke:aaps-adapter && npm run smoke:auxiliary-tools && npm run smoke:perception-research && npm run smoke:auth && npm run smoke:canvas-artifacts && npm run smoke:capabilities && npm run smoke:model-roles && npm run smoke:platform && npm run smoke:permission-modes && npm run smoke:skills && npm run smoke:skillmesh && npm run smoke:tmux-tools && npm run smoke:cli-chat && npm run smoke:inbox",
117
+ "test": "npm run check && npm run smoke:autoupdate && npm run smoke:web-api && npm run smoke:web-autostart && npm run smoke:webapp-command && npm run smoke:web-port-fallback && npm run smoke:docker-command && npm run smoke:coding-tools && npm run smoke:dynamic-step-budget && npm run smoke:aaps-adapter && npm run smoke:auxiliary-tools && npm run smoke:perception-research && npm run smoke:auth && npm run smoke:canvas-artifacts && npm run smoke:capabilities && npm run smoke:model-roles && npm run smoke:platform && npm run smoke:permission-modes && npm run smoke:skills && npm run smoke:skillmesh && npm run smoke:tmux-tools && npm run smoke:cli-chat && npm run smoke:inbox",
116
118
  "pack:dry-run": "npm pack --dry-run",
117
119
  "smoke:capabilities": "node scripts/smoke-capabilities.js"
118
120
  },
@@ -219,6 +219,7 @@ try {
219
219
  "helpScouts",
220
220
  "helpRouting",
221
221
  "helpProvider",
222
+ "helpDocker",
222
223
  "helpDockerOn",
223
224
  "helpDockerOff",
224
225
  "helpLatex",
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ import assert from "node:assert/strict";
3
+ import { execFile } from "node:child_process";
4
+ import fs from "node:fs/promises";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { promisify } from "node:util";
9
+
10
+ const execFileAsync = promisify(execFile);
11
+ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
12
+ const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "agintiflow-docker-command-"));
13
+
14
+ try {
15
+ const { stdout } = await execFileAsync(process.execPath, [path.join(repoRoot, "bin/aginti-cli.js"), "docker", "status", "--json", "--cwd", tempRoot], {
16
+ cwd: tempRoot,
17
+ env: {
18
+ ...process.env,
19
+ AGINTIFLOW_NO_WEB_AUTO_START: "1",
20
+ },
21
+ timeout: 20000,
22
+ maxBuffer: 1024 * 1024,
23
+ });
24
+ const parsed = JSON.parse(stdout);
25
+ assert.equal(parsed.ok, true, "docker status command should return ok envelope");
26
+ assert.equal(parsed.summary.workspace, tempRoot, "docker status should honor --cwd");
27
+ assert.equal(typeof parsed.summary.dockerAvailable, "boolean", "docker status should include docker availability");
28
+ assert(parsed.summary.install?.command, "docker status should include install guidance");
29
+
30
+ const install = await execFileAsync(process.execPath, [path.join(repoRoot, "bin/aginti-cli.js"), "docker", "install-host", "--json"], {
31
+ cwd: tempRoot,
32
+ timeout: 20000,
33
+ maxBuffer: 1024 * 1024,
34
+ });
35
+ const installPlan = JSON.parse(install.stdout);
36
+ assert.equal(typeof installPlan.supported, "boolean", "install-host plan should state support");
37
+ assert(installPlan.command, "install-host plan should include a command or guidance");
38
+
39
+ console.log("docker command smoke ok");
40
+ } finally {
41
+ await fs.rm(tempRoot, { recursive: true, force: true });
42
+ }
package/src/cli.js CHANGED
@@ -38,9 +38,11 @@ import { handleAapsCliCommand } from "./aaps-adapter.js";
38
38
  import { formatInstructionTemplateList, normalizeInstructionTemplate } from "./behavior-contract.js";
39
39
  import { applyPermissionMode, normalizePermissionMode } from "./permission-modes.js";
40
40
  import { ensureAgintiWebApp } from "./web-autostart.js";
41
+ import { dockerHostInstallPlan, formatDockerSetupText, summarizeDockerSetup } from "./docker-setup.js";
41
42
  import fs from "node:fs/promises";
42
43
  import path from "node:path";
43
44
  import { fileURLToPath } from "node:url";
45
+ import { spawn } from "node:child_process";
44
46
  import readline from "node:readline/promises";
45
47
  import * as readlineRaw from "node:readline";
46
48
 
@@ -716,12 +718,104 @@ function exitOnUnknownOptions(parsed) {
716
718
 
717
719
  function printUsage() {
718
720
  console.log(
719
- 'Usage: aginti [chat] OR aginti init [--template minimal|disciplined|coding|research|writing|design|aaps|supervision] OR aginti web [--port 3210] OR aginti update OR aginti models OR aginti aaps [status|init|files|validate|compile|check|run] OR aginti skills [query] OR aginti skillmesh [status|off|record|share|sync|serve|service] OR aginti housekeeping [--json] OR aginti auth [deepseek|openai|qwen|venice|grsai] OR aginti resume [--all-sessions] [latest|<session-id>] ["prompt"] OR aginti --remove-empty-sessions OR aginti --remove-sessions OR aginti queue <session-id> "message" OR aginti [--no-auto-update] [-s safe|normal|danger] [--language en|ja|zh-Hans|zh-Hant|ko|fr|es|ar|vi|de|ru] [--image] [--latex] [--scs|--scs auto|--no-scs] [--dynamic-steps auto|on|off] [--routing smart|fast|complex|manual] [--provider deepseek|openai|qwen|venice|mock] [--model MODEL] [--route-model MODEL] [--main-model MODEL] [--spare-model MODEL --spare-reasoning medium] [--aux-provider grsai|venice --aux-model MODEL] [--sandbox-mode host|docker-readonly|docker-workspace] [--package-install-policy block|prompt|allow] [--approve-package-installs] [--allow-shell|--no-shell] [--allow-file-tools|--no-file-tools] [--web-search|--no-web-search] [--parallel-scouts|--no-parallel-scouts --scout-count 1..10] [--allow-auxiliary-tools|--no-auxiliary-tools] [--allow-wrappers --wrapper codex --wrapper-model gpt-5.5] [--list-models|--list-routes] "your task"'
721
+ 'Usage: aginti [chat] OR aginti init [--template minimal|disciplined|coding|research|writing|design|aaps|supervision] OR aginti web [--port 3210] OR aginti docker [status|setup|install-host] OR aginti update OR aginti models OR aginti aaps [status|init|files|validate|compile|check|run] OR aginti skills [query] OR aginti skillmesh [status|off|record|share|sync|serve|service] OR aginti housekeeping [--json] OR aginti auth [deepseek|openai|qwen|venice|grsai] OR aginti resume [--all-sessions] [latest|<session-id>] ["prompt"] OR aginti --remove-empty-sessions OR aginti --remove-sessions OR aginti queue <session-id> "message" OR aginti [--no-auto-update] [-s safe|normal|danger] [--language en|ja|zh-Hans|zh-Hant|ko|fr|es|ar|vi|de|ru] [--image] [--latex] [--scs|--scs auto|--no-scs] [--dynamic-steps auto|on|off] [--routing smart|fast|complex|manual] [--provider deepseek|openai|qwen|venice|mock] [--model MODEL] [--route-model MODEL] [--main-model MODEL] [--spare-model MODEL --spare-reasoning medium] [--aux-provider grsai|venice --aux-model MODEL] [--sandbox-mode host|docker-readonly|docker-workspace] [--package-install-policy block|prompt|allow] [--approve-package-installs] [--allow-shell|--no-shell] [--allow-file-tools|--no-file-tools] [--web-search|--no-web-search] [--parallel-scouts|--no-parallel-scouts --scout-count 1..10] [--allow-auxiliary-tools|--no-auxiliary-tools] [--allow-wrappers --wrapper codex --wrapper-model gpt-5.5] [--list-models|--list-routes] "your task"'
720
722
  );
721
723
  console.log("Permission shortcuts: -s safe asks before writes/setup; -s normal allows current-project writes and Docker setup; -s danger enables trusted host/full-access mode.");
722
724
  console.log(`Languages: ${["en", "ja", "zh-Hans", "zh-Hant", "ko", "fr", "es", "ar", "vi", "de", "ru"].map((code) => `${code}=${languageLabel(code)}`).join(", ")}`);
723
725
  }
724
726
 
727
+ function parseDockerCommandArgs(argv = [], fallbackCwd = process.cwd()) {
728
+ const result = {
729
+ action: "status",
730
+ json: false,
731
+ yes: false,
732
+ commandCwd: fallbackCwd,
733
+ };
734
+ for (let index = 0; index < argv.length; index += 1) {
735
+ const arg = argv[index];
736
+ if (arg === "--json") {
737
+ result.json = true;
738
+ continue;
739
+ }
740
+ if (arg === "--yes" || arg === "-y") {
741
+ result.yes = true;
742
+ continue;
743
+ }
744
+ if (arg === "--cwd") {
745
+ result.commandCwd = readOption(argv, index) || result.commandCwd;
746
+ index += 1;
747
+ continue;
748
+ }
749
+ if (!arg.startsWith("-") && result.action === "status") {
750
+ result.action = arg;
751
+ continue;
752
+ }
753
+ }
754
+ return result;
755
+ }
756
+
757
+ function runInherited(command, args = [], options = {}) {
758
+ return new Promise((resolve) => {
759
+ const child = spawn(command, args, { stdio: "inherit", ...options });
760
+ child.on("close", (code) => resolve(Number(code || 0)));
761
+ child.on("error", (error) => {
762
+ console.error(error instanceof Error ? error.message : String(error));
763
+ resolve(1);
764
+ });
765
+ });
766
+ }
767
+
768
+ async function handleDockerCommand(argv = [], { commandCwd = process.cwd() } = {}) {
769
+ const parsed = parseDockerCommandArgs(argv, commandCwd);
770
+ const config = loadConfig(
771
+ {
772
+ goal: "docker setup",
773
+ commandCwd: parsed.commandCwd,
774
+ sandboxMode: "docker-workspace",
775
+ packageInstallPolicy: "allow",
776
+ provider: "mock",
777
+ routingMode: "manual",
778
+ },
779
+ { packageDir, baseDir: parsed.commandCwd }
780
+ );
781
+ const action = String(parsed.action || "status").toLowerCase();
782
+
783
+ if (["install", "install-host", "host-install"].includes(action)) {
784
+ const plan = dockerHostInstallPlan(packageDir);
785
+ if (parsed.json) {
786
+ console.log(JSON.stringify(plan, null, 2));
787
+ return;
788
+ }
789
+ if (!plan.supported) {
790
+ console.log(formatDockerSetupText(summarizeDockerSetup({ status: await getDockerSandboxStatus(config), packageDir })));
791
+ console.log("");
792
+ console.log("Host Docker auto-install is intentionally not run on this platform.");
793
+ console.log(`Recommended path: ${plan.command}`);
794
+ return;
795
+ }
796
+ if (!parsed.yes) {
797
+ console.log("Host Docker install requires explicit confirmation because it changes system packages/services.");
798
+ console.log(`Run: aginti docker install-host --yes`);
799
+ console.log(`Script: ${plan.command}`);
800
+ return;
801
+ }
802
+ const exitCode = await runInherited("bash", [plan.command], { cwd: packageDir });
803
+ process.exitCode = exitCode;
804
+ return;
805
+ }
806
+
807
+ const buildImage = ["setup", "build", "preflight", "latex", "toolchain"].includes(action);
808
+ const result = buildImage
809
+ ? await runDockerPreflight(config, { buildImage: true })
810
+ : { ok: true, status: await getDockerSandboxStatus(config), checks: [] };
811
+ const summary = summarizeDockerSetup({ status: result.status, preflight: buildImage ? result : null, packageDir });
812
+ if (parsed.json) {
813
+ console.log(JSON.stringify({ ok: buildImage ? result.ok : true, summary, result }, null, 2));
814
+ return;
815
+ }
816
+ console.log(formatDockerSetupText(summary));
817
+ }
818
+
725
819
  function stripLeadingGlobalOptions(argv = []) {
726
820
  let index = 0;
727
821
  const options = {
@@ -1607,6 +1701,11 @@ export async function main(argv = process.argv.slice(2)) {
1607
1701
  return;
1608
1702
  }
1609
1703
 
1704
+ if (commandArgv[0] === "docker") {
1705
+ await handleDockerCommand(commandArgv.slice(1), { commandCwd });
1706
+ return;
1707
+ }
1708
+
1610
1709
  if (commandArgv.includes("--remove-empty-sessions") || commandArgv[0] === "remove-empty-sessions") {
1611
1710
  await handleRemoveSessionsCommand({ emptyOnly: true });
1612
1711
  return;
@@ -0,0 +1,107 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import { platformInfo, platformLabel, platformSetupHints } from "./platform.js";
4
+
5
+ function latexStatusFromChecks(checks = []) {
6
+ const latexChecks = checks.filter((check) => /latexmk|pdflatex/i.test(check.command || ""));
7
+ if (!latexChecks.length) return "unchecked";
8
+ return latexChecks.every((check) => check.ok) ? "ready" : "missing";
9
+ }
10
+
11
+ export function dockerHostInstallPlan(packageDir = process.cwd(), info = platformInfo()) {
12
+ const hints = platformSetupHints(info);
13
+ const script = path.join(packageDir, "scripts", "install-docker-ubuntu.sh");
14
+ if (info.isMac) {
15
+ return {
16
+ supported: false,
17
+ platform: platformLabel(info),
18
+ command: "Install Docker Desktop or Colima, then run: aginti docker setup",
19
+ hints,
20
+ };
21
+ }
22
+ if (info.isWindows && !info.isWsl) {
23
+ return {
24
+ supported: false,
25
+ platform: platformLabel(info),
26
+ command: "Use WSL2 with Docker Desktop WSL integration, then run: aginti docker setup inside WSL",
27
+ hints,
28
+ };
29
+ }
30
+ if (info.isLinux && info.linuxFamily === "debian" && /ubuntu/i.test(`${info.linuxId} ${info.distro}`)) {
31
+ return {
32
+ supported: true,
33
+ platform: platformLabel(info),
34
+ command: script,
35
+ hints,
36
+ };
37
+ }
38
+ return {
39
+ supported: false,
40
+ platform: platformLabel(info),
41
+ command: "Install a Docker-compatible engine with your OS package manager, then run: aginti docker setup",
42
+ hints,
43
+ };
44
+ }
45
+
46
+ export function summarizeDockerSetup({ status, preflight = null, packageDir = process.cwd() } = {}) {
47
+ const info = platformInfo();
48
+ const install = dockerHostInstallPlan(packageDir, info);
49
+ const checks = preflight?.checks || [];
50
+ const failedChecks = checks.filter((check) => !check.ok);
51
+ const latex = latexStatusFromChecks(checks);
52
+ return {
53
+ platform: platformLabel(info),
54
+ sandboxMode: status?.sandboxMode || "unknown",
55
+ packageInstallPolicy: status?.packageInstallPolicy || "unknown",
56
+ dockerAvailable: Boolean(status?.dockerAvailable),
57
+ image: status?.image || "agintiflow-sandbox:latest",
58
+ imageReady: Boolean(status?.imageReady),
59
+ dockerfileExists: Boolean(status?.dockerfileExists),
60
+ workspace: status?.workspace || process.cwd(),
61
+ persistentDocker: status?.persistentDocker || {},
62
+ latex,
63
+ preflightOk: preflight ? Boolean(preflight.ok && failedChecks.length === 0) : undefined,
64
+ failedChecks: failedChecks.map((check) => check.command || check.error || "unknown"),
65
+ install,
66
+ };
67
+ }
68
+
69
+ export function formatDockerSetupText(summary = {}) {
70
+ const lines = [
71
+ "AgInTiFlow Docker setup",
72
+ `platform=${summary.platform || `${os.type()} (${process.arch})`}`,
73
+ `sandbox=${summary.sandboxMode || "unknown"} installs=${summary.packageInstallPolicy || "unknown"}`,
74
+ `docker=${summary.dockerAvailable ? "available" : "missing"} image=${summary.image || "agintiflow-sandbox:latest"} ${
75
+ summary.imageReady ? "ready" : "not-ready"
76
+ }`,
77
+ `dockerfile=${summary.dockerfileExists ? "present" : "missing"}`,
78
+ `workspace=${summary.workspace || process.cwd()}`,
79
+ ];
80
+
81
+ if (summary.persistentDocker?.root || summary.persistentDocker?.env) {
82
+ lines.push(`persistent=${summary.persistentDocker.root || path.dirname(summary.persistentDocker.env)}`);
83
+ }
84
+ if (summary.latex && summary.latex !== "unchecked") lines.push(`latex=${summary.latex}`);
85
+ if (summary.preflightOk !== undefined) lines.push(`preflight=${summary.preflightOk ? "passed" : "failed"}`);
86
+ if (summary.failedChecks?.length) {
87
+ lines.push(`failedChecks=${summary.failedChecks.join(", ")}`);
88
+ }
89
+
90
+ lines.push("");
91
+ if (!summary.dockerAvailable) {
92
+ lines.push("Docker is not available.");
93
+ lines.push(`Install path: ${summary.install?.command || "Install Docker, then run: aginti docker setup"}`);
94
+ } else if (!summary.imageReady || summary.preflightOk === false) {
95
+ lines.push("Next: aginti docker setup");
96
+ } else {
97
+ lines.push("Docker sandbox is ready for normal-mode package setup and LaTeX/PDF work.");
98
+ }
99
+
100
+ if (summary.install?.hints?.length) {
101
+ lines.push("");
102
+ lines.push("Platform notes:");
103
+ for (const hint of summary.install.hints) lines.push(`- ${hint}`);
104
+ }
105
+
106
+ return lines.join("\n");
107
+ }
package/src/i18n.js CHANGED
@@ -146,6 +146,7 @@ const TRANSLATIONS = {
146
146
  helpRouting: "Set routing: smart, fast, complex, manual.",
147
147
  helpProvider: "Open provider selector, or set deepseek/openai/qwen/venice/mock.",
148
148
  helpPermissionMode: "Switch safe/normal/danger permission posture for this session.",
149
+ helpDocker: "Inspect or prepare the Docker sandbox/toolchain.",
149
150
  helpDockerOn: "Use docker-workspace with approved package installs.",
150
151
  helpDockerOff: "Use host shell policy.",
151
152
  helpLatex: "Use the LaTeX/PDF profile in Docker with a larger step budget.",
@@ -197,6 +198,7 @@ const TRANSLATIONS = {
197
198
  helpScouts: "並列 DeepSeek スカウトを有効化し数を設定します。",
198
199
  helpRouting: "routing を smart、fast、complex、manual に設定します。",
199
200
  helpProvider: "プロバイダ選択を開くか deepseek/openai/qwen/venice/mock を設定します。",
201
+ helpDocker: "Docker サンドボックス/ツールチェーンを確認または準備します。",
200
202
  helpDockerOn: "承認済みパッケージインストール付き docker-workspace を使います。",
201
203
  helpDockerOff: "ホストシェルポリシーを使います。",
202
204
  helpLatex: "Docker で LaTeX/PDF プロファイルと大きめのステップ数を使います。",
@@ -248,6 +250,7 @@ const TRANSLATIONS = {
248
250
  helpScouts: "启用并设置并行 DeepSeek scouts 数量。",
249
251
  helpRouting: "设置 routing: smart、fast、complex、manual。",
250
252
  helpProvider: "打开 provider 选择器,或设置 deepseek/openai/qwen/venice/mock。",
253
+ helpDocker: "检查或准备 Docker 沙箱/工具链。",
251
254
  helpDockerOn: "使用允许安装包的 docker-workspace。",
252
255
  helpDockerOff: "使用主机 shell 策略。",
253
256
  helpLatex: "在 Docker 中使用 LaTeX/PDF profile 和更大步骤数。",
@@ -299,6 +302,7 @@ const TRANSLATIONS = {
299
302
  helpScouts: "啟用並設定並行 DeepSeek scouts 數量。",
300
303
  helpRouting: "設定 routing: smart、fast、complex、manual。",
301
304
  helpProvider: "開啟 provider 選擇器,或設定 deepseek/openai/qwen/venice/mock。",
305
+ helpDocker: "檢查或準備 Docker 沙箱/工具鏈。",
302
306
  helpDockerOn: "使用允許安裝套件的 docker-workspace。",
303
307
  helpDockerOff: "使用主機 shell 策略。",
304
308
  helpLatex: "在 Docker 中使用 LaTeX/PDF profile 和較大步數。",
@@ -353,6 +357,7 @@ const FALLBACKS = {
353
357
  helpScouts: "병렬 DeepSeek 스카우트를 켜고 수를 설정합니다.",
354
358
  helpRouting: "routing 을 smart, fast, complex, manual 로 설정합니다.",
355
359
  helpProvider: "provider 선택기를 열거나 deepseek/openai/qwen/venice/mock 을 설정합니다.",
360
+ helpDocker: "Docker 샌드박스/툴체인을 확인하거나 준비합니다.",
356
361
  helpDockerOn: "승인된 패키지 설치가 가능한 docker-workspace 를 사용합니다.",
357
362
  helpDockerOff: "호스트 shell 정책을 사용합니다.",
358
363
  helpLatex: "Docker 에서 LaTeX/PDF 프로필과 더 큰 단계 예산을 사용합니다.",
@@ -404,6 +409,7 @@ const FALLBACKS = {
404
409
  helpScouts: "Activer les scouts DeepSeek parallèles et régler leur nombre.",
405
410
  helpRouting: "Définir le routage: smart, fast, complex, manual.",
406
411
  helpProvider: "Ouvrir le sélecteur provider, ou définir deepseek/openai/qwen/venice/mock.",
412
+ helpDocker: "Inspecter ou préparer le sandbox/la toolchain Docker.",
407
413
  helpDockerOn: "Utiliser docker-workspace avec installations de paquets approuvées.",
408
414
  helpDockerOff: "Utiliser la politique shell hôte.",
409
415
  helpLatex: "Utiliser le profil LaTeX/PDF dans Docker avec plus d'étapes.",
@@ -455,6 +461,7 @@ const FALLBACKS = {
455
461
  helpScouts: "Activar scouts DeepSeek paralelos y configurar cantidad.",
456
462
  helpRouting: "Configurar routing: smart, fast, complex, manual.",
457
463
  helpProvider: "Abrir selector provider, o configurar deepseek/openai/qwen/venice/mock.",
464
+ helpDocker: "Inspeccionar o preparar el sandbox/toolchain Docker.",
458
465
  helpDockerOn: "Usar docker-workspace con instalaciones aprobadas.",
459
466
  helpDockerOff: "Usar política de shell host.",
460
467
  helpLatex: "Usar perfil LaTeX/PDF en Docker con más pasos.",
@@ -506,6 +513,7 @@ const FALLBACKS = {
506
513
  helpScouts: "تفعيل كشافات DeepSeek المتوازية وتحديد عددها.",
507
514
  helpRouting: "تعيين routing: smart أو fast أو complex أو manual.",
508
515
  helpProvider: "فتح محدد provider أو تعيين deepseek/openai/qwen/venice/mock.",
516
+ helpDocker: "فحص أو تجهيز صندوق Docker والأدوات.",
509
517
  helpDockerOn: "استخدام docker-workspace مع تثبيت حزم معتمد.",
510
518
  helpDockerOff: "استخدام سياسة shell المضيف.",
511
519
  helpLatex: "استخدام ملف LaTeX/PDF في Docker مع ميزانية خطوات أكبر.",
@@ -557,6 +565,7 @@ const FALLBACKS = {
557
565
  helpScouts: "Bật DeepSeek scouts song song và đặt số lượng.",
558
566
  helpRouting: "Đặt routing: smart, fast, complex, manual.",
559
567
  helpProvider: "Mở bộ chọn provider, hoặc đặt deepseek/openai/qwen/venice/mock.",
568
+ helpDocker: "Kiểm tra hoặc chuẩn bị sandbox/toolchain Docker.",
560
569
  helpDockerOn: "Dùng docker-workspace với cài package đã duyệt.",
561
570
  helpDockerOff: "Dùng chính sách shell host.",
562
571
  helpLatex: "Dùng profile LaTeX/PDF trong Docker với ngân sách bước lớn hơn.",
@@ -608,6 +617,7 @@ const FALLBACKS = {
608
617
  helpScouts: "Parallele DeepSeek-Scouts aktivieren und Anzahl setzen.",
609
618
  helpRouting: "Routing setzen: smart, fast, complex, manual.",
610
619
  helpProvider: "Provider-Auswahl öffnen oder deepseek/openai/qwen/venice/mock setzen.",
620
+ helpDocker: "Docker-Sandbox/Toolchain prüfen oder vorbereiten.",
611
621
  helpDockerOn: "docker-workspace mit genehmigten Paketinstallationen verwenden.",
612
622
  helpDockerOff: "Host-Shell-Policy verwenden.",
613
623
  helpLatex: "LaTeX/PDF-Profil in Docker mit größerem Schrittbudget verwenden.",
@@ -659,6 +669,7 @@ const FALLBACKS = {
659
669
  helpScouts: "Включить параллельных DeepSeek scouts и задать количество.",
660
670
  helpRouting: "Задать routing: smart, fast, complex, manual.",
661
671
  helpProvider: "Открыть выбор provider или задать deepseek/openai/qwen/venice/mock.",
672
+ helpDocker: "Проверить или подготовить Docker sandbox/toolchain.",
662
673
  helpDockerOn: "Использовать docker-workspace с одобренными установками пакетов.",
663
674
  helpDockerOff: "Использовать политику host shell.",
664
675
  helpLatex: "Использовать профиль LaTeX/PDF в Docker с большим числом шагов.",
@@ -13,6 +13,8 @@ import {
13
13
  sessionStoreOptions,
14
14
  } from "./project.js";
15
15
  import { normalizePackageInstallPolicy, normalizeSandboxMode } from "./command-policy.js";
16
+ import { getDockerSandboxStatus, runDockerPreflight } from "./docker-sandbox.js";
17
+ import { formatDockerSetupText, summarizeDockerSetup } from "./docker-setup.js";
16
18
  import { defaultMaxStepsForProfile, normalizeTaskProfile } from "./task-profiles.js";
17
19
  import { recommendedMaxStepsForTask } from "./engineering-guidance.js";
18
20
  import { normalizeAuthProvider, runAuthWizard, shouldPromptForDeepSeek } from "./auth-onboarding.js";
@@ -294,7 +296,23 @@ function promptViewportRows(height = terminalHeight()) {
294
296
  }
295
297
 
296
298
  function stripAnsi(value) {
297
- return String(value || "").replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "");
299
+ return String(value || "")
300
+ .replace(/\x1b\][\s\S]*?(?:\x07|\x1b\\)/g, "")
301
+ .replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "");
302
+ }
303
+
304
+ function terminalHyperlink(url = "", text = url) {
305
+ if (!useColor || process.env.AGINTIFLOW_NO_HYPERLINK === "1") return String(text || "");
306
+ const safeUrl = String(url || "").replace(/[\x00-\x1f\x7f]/g, "");
307
+ if (!safeUrl) return String(text || "");
308
+ return `\x1b]8;;${safeUrl}\x07${text}\x1b]8;;\x07`;
309
+ }
310
+
311
+ function linkifyTerminalUrl(line = "", url = "") {
312
+ const visibleLine = String(line || "");
313
+ const visibleUrl = String(url || "");
314
+ if (!visibleLine || !visibleUrl || !visibleLine.includes(visibleUrl)) return visibleLine;
315
+ return visibleLine.replace(visibleUrl, terminalHyperlink(visibleUrl, visibleUrl));
298
316
  }
299
317
 
300
318
  function charCellWidth(char = "") {
@@ -806,7 +824,9 @@ export function buildLaunchHeaderLines({
806
824
  row(centerLine(subtitle, contentWidth), ansi.dim),
807
825
  row(centerLine(credit, contentWidth), ansi.dim),
808
826
  tagline ? mid : "",
809
- tagline ? row(centerLine(compactLine(tagline, contentWidth), contentWidth), webAppUrl ? ansi.cyan : ansi.yellow) : "",
827
+ tagline
828
+ ? row(centerLine(linkifyTerminalUrl(compactLine(tagline, contentWidth), webAppUrl), contentWidth), webAppUrl ? ansi.cyan : ansi.yellow)
829
+ : "",
810
830
  bottom,
811
831
  ].filter(Boolean);
812
832
  const indent = " ".repeat(Math.max(Math.floor((terminalColumns - Math.max(...boxLines.map((line) => visualLength(line)))) / 2), 0));
@@ -871,8 +891,7 @@ function printHelp() {
871
891
  ` ${command("/routing <mode>", "Set routing: smart, fast, complex, manual.", "helpRouting")}`,
872
892
  ` ${command("/provider [name]", "Open provider selector, or set deepseek/openai/qwen/venice/mock.", "helpProvider")}`,
873
893
  ` ${command("/safe | /normal | /danger", "Switch permission posture for this session.", "helpPermissionMode")}`,
874
- ` ${command("/docker on", "Use docker-workspace with approved package installs.", "helpDockerOn")}`,
875
- ` ${command("/docker off", "Use host shell policy.", "helpDockerOff")}`,
894
+ ` ${command("/docker [status|setup|on|off]", "Inspect or prepare the Docker sandbox/toolchain.", "helpDocker")}`,
876
895
  ` ${command("/latex on", "Use the LaTeX/PDF profile in Docker with a larger step budget.", "helpLatex")}`,
877
896
  ` ${command("/installs block|prompt|allow", "Set package install policy.", "helpInstalls")}`,
878
897
  ` ${command("/cwd <path>", "Change command workspace.", "helpCwd")}`,
@@ -3601,8 +3620,35 @@ async function handleCommand(line, state, packageDir) {
3601
3620
  state.sandboxMode = "host";
3602
3621
  state.packageInstallPolicy = "prompt";
3603
3622
  printSystemLine("docker=off sandbox=host installs=prompt");
3623
+ } else if (value === "status" || value === "") {
3624
+ const config = loadConfig(
3625
+ {
3626
+ ...state,
3627
+ goal: "docker status",
3628
+ commandCwd: state.commandCwd || process.cwd(),
3629
+ sandboxMode: state.sandboxMode || "docker-workspace",
3630
+ packageInstallPolicy: state.packageInstallPolicy || "allow",
3631
+ },
3632
+ { packageDir, baseDir: state.commandCwd || process.cwd() }
3633
+ );
3634
+ const status = await getDockerSandboxStatus(config);
3635
+ printAgentMessage(formatDockerSetupText(summarizeDockerSetup({ status, packageDir })));
3636
+ } else if (value === "setup" || value === "preflight" || value === "build" || value === "latex") {
3637
+ const config = loadConfig(
3638
+ {
3639
+ ...state,
3640
+ goal: "docker setup",
3641
+ commandCwd: state.commandCwd || process.cwd(),
3642
+ sandboxMode: "docker-workspace",
3643
+ packageInstallPolicy: "allow",
3644
+ },
3645
+ { packageDir, baseDir: state.commandCwd || process.cwd() }
3646
+ );
3647
+ printSystemLine("docker=setup starting");
3648
+ const result = await runDockerPreflight(config, { buildImage: true });
3649
+ printAgentMessage(formatDockerSetupText(summarizeDockerSetup({ status: result.status, preflight: result, packageDir })));
3604
3650
  } else {
3605
- printAgentMessage("Usage: /docker on OR /docker off");
3651
+ printAgentMessage("Usage: /docker status OR /docker setup OR /docker on OR /docker off");
3606
3652
  }
3607
3653
  return true;
3608
3654
  }