@lazyingart/agintiflow 0.20.94 → 0.20.110
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 +14 -12
- package/docker/sandbox.Dockerfile +1 -0
- package/docs/runtime-modes-and-autonomy.md +14 -30
- package/package.json +4 -2
- package/public/app.js +147 -0
- package/public/index.html +10 -0
- package/public/styles.css +20 -0
- package/scripts/smoke-cli-chat.js +31 -1
- package/scripts/smoke-coding-tools.js +174 -1
- package/scripts/smoke-permission-modes.js +121 -0
- package/scripts/smoke-web-api.js +45 -0
- package/skills/android/SKILL.md +1 -1
- package/skills/r-stan/SKILL.md +2 -0
- package/skills/security-review/SKILL.md +8 -0
- package/src/agent-runner.js +226 -3
- package/src/cli.js +90 -8
- package/src/command-policy.js +113 -15
- package/src/config.js +37 -6
- package/src/i18n.js +1 -0
- package/src/interactive-cli.js +147 -11
- package/src/model-client.js +65 -9
- package/src/parallel-scouts.js +9 -4
- package/src/permission-advice.js +19 -0
- package/src/permission-modes.js +91 -0
- package/src/scs-controller.js +1 -1
- package/src/task-profiles.js +4 -4
- package/src/web-db.js +22 -6
- package/src/workspace-tools.js +16 -0
- package/web.js +199 -6
package/README.md
CHANGED
|
@@ -24,6 +24,14 @@ Use the same agent workspace from Web or CLI, with DeepSeek/Venice/OpenAI routin
|
|
|
24
24
|
|
|
25
25
|
The short version: run `aginti` inside a project, give it a task, inspect what it plans, see every tool call, resume later, and keep the outputs in your workspace.
|
|
26
26
|
|
|
27
|
+
## Visual Preview
|
|
28
|
+
|
|
29
|
+
The two screenshots below match the current website hero: terminal-first launch on the left, browser console visibility on the right.
|
|
30
|
+
|
|
31
|
+
| CLI launch | Web console |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| <img src="https://raw.githubusercontent.com/lazyingart/AgInTiFlow/main/website/assets/screenshots/cli-launch.jpg" alt="AgInTiFlow CLI launch screen" width="480" /> | <img src="https://raw.githubusercontent.com/lazyingart/AgInTiFlow/main/website/assets/screenshots/web-console-conversation-run-output.jpg" alt="AgInTiFlow web console conversation and run output" width="480" /> |
|
|
34
|
+
|
|
27
35
|
**Links**
|
|
28
36
|
|
|
29
37
|
| Resource | URL |
|
|
@@ -35,8 +43,6 @@ The short version: run `aginti` inside a project, give it a task, inspect what i
|
|
|
35
43
|
| Product positioning | [references/agintiflow-product-positioning.md](references/agintiflow-product-positioning.md) |
|
|
36
44
|
| Full archived README reference | [references/notes/readme-full-reference-2026-05-05.md](references/notes/readme-full-reference-2026-05-05.md) |
|
|
37
45
|
|
|
38
|
-
Screenshots are kept out of the README body so the start guide stays readable. View them on the website carousel or in [demos/](demos/) and [website/assets/screenshots/](website/assets/screenshots/).
|
|
39
|
-
|
|
40
46
|
## Why This Exists
|
|
41
47
|
|
|
42
48
|
Most agent tools are either a chat box with hidden state or an expensive one-model loop. AgInTiFlow is built around a different philosophy:
|
|
@@ -128,6 +134,7 @@ aginti --language de
|
|
|
128
134
|
| Use SCS only for complex work | `/scs auto` or `aginti --scs auto "task"` |
|
|
129
135
|
| Work with AAPS workflows | `aginti aaps status`, `/aaps validate` |
|
|
130
136
|
| Choose models | `/route`, `/model`, `/spare`, `/wrapper`, `/auxiliary model` |
|
|
137
|
+
| Switch permissions | `-s safe`, `-s normal`, `-s danger`, or `/safe`, `/normal`, `/danger` |
|
|
131
138
|
| Enable Venice shortcut | `/venice` |
|
|
132
139
|
| Generate images | `/auxiliary image`, then ask for an image |
|
|
133
140
|
| Resume current project | `aginti resume` |
|
|
@@ -155,7 +162,7 @@ aginti --resume <session-id> \
|
|
|
155
162
|
"Take a fresh screenshot of the running app in the emulator, save it with a durable filename in this project, and keep git status clean."
|
|
156
163
|
```
|
|
157
164
|
|
|
158
|
-
Permission behavior is intentionally consistent
|
|
165
|
+
Permission behavior is intentionally consistent. Use `-s safe` for read-first sessions that ask before writes/setup, `-s normal` for current-project writes plus Docker setup, and `-s danger` for trusted host/full-access work. Inside chat, `/safe`, `/normal`, and `/danger` switch the current session. When a blocked action appears, CLI and web can offer `No`, `Yes this time`, or `Yes and always for this session` instead of making the agent retry command variants. Android/Gradle builds can use safe local env assignments such as `ANDROID_HOME=... JAVA_HOME=... ./gradlew assembleDebug` and relative workspace logs without requiring whole-host destructive mode. See [runtime modes and autonomy](docs/runtime-modes-and-autonomy.md) for the full contract.
|
|
159
166
|
|
|
160
167
|
Tmux follows the same rule. In Docker sandbox mode, `tmux_start_session` and `tmux_send_keys` are durable host tools, but their commands must stay workspace-bound. In host mode, tmux startup/send command text follows the same host shell policy as `run_command`; broad host shell work needs explicit `--allow-destructive`. Use `--sandbox-mode host --allow-destructive` only when a tmux task really needs trusted whole-host execution.
|
|
161
168
|
|
|
@@ -165,20 +172,15 @@ Use these when you want explicit control instead of the default interactive poli
|
|
|
165
172
|
|
|
166
173
|
| Mode | Command | What it permits |
|
|
167
174
|
| --- | --- | --- |
|
|
168
|
-
|
|
|
169
|
-
|
|
|
170
|
-
|
|
|
175
|
+
| Safe | `aginti -s safe "inspect this project and ask before edits"` | Docker read-only posture, package/setup approval required, and file-tool writes stop for approval. |
|
|
176
|
+
| Normal | `aginti -s normal "build and test this project"` | Current-project file writes and Docker workspace package/setup are allowed; outside-project writes and host-system changes still stop. |
|
|
177
|
+
| Danger | `aginti -s danger "perform the trusted host maintenance task"` | Trusted host mode with destructive shell, host installs, password typing, and outside-workspace file paths enabled. Hard secret/publish guards still protect obvious credential leaks. |
|
|
171
178
|
|
|
172
179
|
For resume:
|
|
173
180
|
|
|
174
181
|
```bash
|
|
175
182
|
aginti --resume <session-id> \
|
|
176
|
-
|
|
177
|
-
--package-install-policy allow \
|
|
178
|
-
--approve-package-installs \
|
|
179
|
-
--allow-shell \
|
|
180
|
-
--allow-file-tools \
|
|
181
|
-
--allow-destructive \
|
|
183
|
+
-s danger \
|
|
182
184
|
"continue with trusted host access"
|
|
183
185
|
```
|
|
184
186
|
|
|
@@ -27,6 +27,7 @@ Important boundary: host tmux tools are durable, but they are not a permission e
|
|
|
27
27
|
|
|
28
28
|
Codex exposes these concerns as `--sandbox read-only|workspace-write|danger-full-access` and `--ask-for-approval untrusted|on-failure|on-request|never`. AgInTiFlow maps the same philosophy into:
|
|
29
29
|
|
|
30
|
+
- `-s safe|normal|danger` and `/safe`, `/normal`, `/danger`
|
|
30
31
|
- `--sandbox-mode host|docker-readonly|docker-workspace`
|
|
31
32
|
- `--package-install-policy block|prompt|allow`
|
|
32
33
|
- `--allow-shell|--no-shell`
|
|
@@ -50,8 +51,12 @@ The runtime must be consistent when an action is not permitted. A blocked action
|
|
|
50
51
|
|
|
51
52
|
Current contract:
|
|
52
53
|
|
|
54
|
+
- `safe` asks before file-tool writes and package/setup actions. It is the right mode for inspection, review, and cautious first passes.
|
|
55
|
+
- `normal` allows writes inside the current project folder and package/setup inside Docker workspace mode. It still blocks outside-project writes and host-system changes until approved.
|
|
56
|
+
- `danger` is trusted host/full-access mode: host shell, destructive actions, host installs, password typing, and outside-workspace file paths are enabled. Use it only for tasks you trust; obvious secret/publish exfiltration guards remain hard stops.
|
|
53
57
|
- Workspace file tools may read and write inside the configured project folder when file tools are enabled.
|
|
54
58
|
- Narrow workspace-local shell actions such as safe probes, Gradle/TeX builds, and `chmod +x` on a project script may run when the command policy can classify them precisely.
|
|
59
|
+
- Android/JVM toolchain commands may include safe local env assignments such as `ANDROID_HOME`, `ANDROID_SDK_ROOT`, `JAVA_HOME`, or `GRADLE_USER_HOME`, plus small workspace-local status-log redirects. Secret-like env vars and redirects outside the workspace remain blocked.
|
|
55
60
|
- Workspace writes outside that folder, secret paths, `.git` internals, and dependency folders such as `node_modules` are blocked.
|
|
56
61
|
- `git clone`, `git fetch`, `git pull --ff-only`, `git push`, `curl`, and `wget` are classified as network operations.
|
|
57
62
|
- Network/setup commands are allowed in `docker-workspace` only when package installs are approved.
|
|
@@ -59,7 +64,7 @@ Current contract:
|
|
|
59
64
|
- Destructive host shell/git operations need explicit trusted host mode.
|
|
60
65
|
- Android SDK/emulator work commonly needs host mode because `adb`, emulator images, and device state live outside Docker; use trusted host mode for install/launch/screenshot steps when the user approves that host access.
|
|
61
66
|
|
|
62
|
-
When a tool is blocked, the tool result includes `permissionAdvice
|
|
67
|
+
When a tool is blocked, the tool result includes `permissionAdvice`. Interactive CLI and web runs present the same approval language: `No`, `Yes this time`, and `Yes and always for this session`. One-time approval temporarily escalates the active continuation; always switches the session to the suggested shortcut mode. The model prompt requires the agent to present that blocker and avoid retrying variants until the user approves a path.
|
|
63
68
|
|
|
64
69
|
The common controlled rerun shape is:
|
|
65
70
|
|
|
@@ -77,30 +82,14 @@ aginti --resume <session-id> \
|
|
|
77
82
|
Three practical permission recipes:
|
|
78
83
|
|
|
79
84
|
```bash
|
|
80
|
-
#
|
|
81
|
-
aginti
|
|
82
|
-
--package-install-policy block \
|
|
83
|
-
--allow-shell \
|
|
84
|
-
--no-file-tools \
|
|
85
|
-
--no-web-search \
|
|
86
|
-
"inspect this project without edits"
|
|
85
|
+
# Safe: read-first, ask before writes and setup.
|
|
86
|
+
aginti -s safe "inspect this project without edits"
|
|
87
87
|
|
|
88
|
-
#
|
|
89
|
-
aginti
|
|
90
|
-
--package-install-policy allow \
|
|
91
|
-
--approve-package-installs \
|
|
92
|
-
--allow-shell \
|
|
93
|
-
--allow-file-tools \
|
|
94
|
-
"build and test this project"
|
|
88
|
+
# Normal: write in the current project folder, setup isolated in Docker.
|
|
89
|
+
aginti -s normal "build and test this project"
|
|
95
90
|
|
|
96
|
-
#
|
|
97
|
-
aginti
|
|
98
|
-
--package-install-policy allow \
|
|
99
|
-
--approve-package-installs \
|
|
100
|
-
--allow-shell \
|
|
101
|
-
--allow-file-tools \
|
|
102
|
-
--allow-destructive \
|
|
103
|
-
"perform the trusted host maintenance task"
|
|
91
|
+
# Danger: full trusted host access for maintenance or emulator/device work.
|
|
92
|
+
aginti -s danger "perform the trusted host maintenance task"
|
|
104
93
|
```
|
|
105
94
|
|
|
106
95
|
For host-only work, use the stricter trusted form deliberately:
|
|
@@ -108,12 +97,7 @@ For host-only work, use the stricter trusted form deliberately:
|
|
|
108
97
|
```bash
|
|
109
98
|
aginti --resume <session-id> \
|
|
110
99
|
--cwd /path/to/project \
|
|
111
|
-
|
|
112
|
-
--package-install-policy allow \
|
|
113
|
-
--approve-package-installs \
|
|
114
|
-
--allow-shell \
|
|
115
|
-
--allow-file-tools \
|
|
116
|
-
--allow-destructive \
|
|
100
|
+
-s danger \
|
|
117
101
|
"Continue after trusted host approval. Inspect first and keep unrelated files untouched."
|
|
118
102
|
```
|
|
119
103
|
|
|
@@ -130,7 +114,7 @@ This starts interactive chat with Docker workspace mode, file tools, shell tools
|
|
|
130
114
|
Direct trusted host mode:
|
|
131
115
|
|
|
132
116
|
```bash
|
|
133
|
-
aginti
|
|
117
|
+
aginti -s danger "fix this local service"
|
|
134
118
|
```
|
|
135
119
|
|
|
136
120
|
Use this only when the task truly needs host access. The agent should inspect first, show risky commands, stop on ambiguity, and avoid secrets.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.110",
|
|
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",
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"scripts/smoke-inbox.js",
|
|
62
62
|
"scripts/smoke-model-roles.js",
|
|
63
63
|
"scripts/smoke-platform.js",
|
|
64
|
+
"scripts/smoke-permission-modes.js",
|
|
64
65
|
"scripts/smoke-skills.js",
|
|
65
66
|
"scripts/smoke-skillmesh.js",
|
|
66
67
|
"scripts/smoke-tmux-tools.js",
|
|
@@ -92,13 +93,14 @@
|
|
|
92
93
|
"smoke:inbox": "node scripts/smoke-inbox.js",
|
|
93
94
|
"smoke:model-roles": "node scripts/smoke-model-roles.js",
|
|
94
95
|
"smoke:platform": "node scripts/smoke-platform.js",
|
|
96
|
+
"smoke:permission-modes": "node scripts/smoke-permission-modes.js",
|
|
95
97
|
"smoke:tmux-tools": "node scripts/smoke-tmux-tools.js",
|
|
96
98
|
"smoke:web-api": "node scripts/smoke-web-api.js",
|
|
97
99
|
"smoke:autoupdate": "node scripts/smoke-auto-update.js",
|
|
98
100
|
"real:deepseek": "node scripts/real-deepseek-capabilities.js",
|
|
99
101
|
"supervision:seed": "node scripts/seed-supervised-homework.js",
|
|
100
102
|
"storage:migrate": "node bin/aginti-cli.js storage migrate",
|
|
101
|
-
"test": "npm run check && npm run smoke:autoupdate && npm run smoke:web-api && npm run smoke:coding-tools && npm run smoke:aaps-adapter && npm run smoke:auxiliary-tools && 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:skills && npm run smoke:skillmesh && npm run smoke:tmux-tools && npm run smoke:cli-chat && npm run smoke:inbox",
|
|
103
|
+
"test": "npm run check && npm run smoke:autoupdate && npm run smoke:web-api && npm run smoke:coding-tools && npm run smoke:aaps-adapter && npm run smoke:auxiliary-tools && 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",
|
|
102
104
|
"pack:dry-run": "npm pack --dry-run",
|
|
103
105
|
"smoke:capabilities": "node scripts/smoke-capabilities.js"
|
|
104
106
|
},
|
package/public/app.js
CHANGED
|
@@ -57,6 +57,18 @@ const translations = {
|
|
|
57
57
|
commandCwdLabel: "Working directory",
|
|
58
58
|
maxStepsLabel: "Max steps",
|
|
59
59
|
sandboxModeLabel: "Sandbox mode",
|
|
60
|
+
permissionModeLabel: "Permission shortcut",
|
|
61
|
+
permissionSafeOption: "Safe: ask before writes/setup",
|
|
62
|
+
permissionNormalOption: "Normal: project writes + Docker setup",
|
|
63
|
+
permissionDangerOption: "Danger: trusted host/full access",
|
|
64
|
+
permissionHintSafe: "Safe mode asks before workspace writes and package/setup actions.",
|
|
65
|
+
permissionHintNormal: "Normal mode allows current-project writes and package setup inside Docker; outside-project and host-system changes still stop for approval.",
|
|
66
|
+
permissionHintDanger: "Danger mode enables trusted host access, destructive commands, password typing, and outside-workspace file paths. Use only for tasks you trust.",
|
|
67
|
+
permissionApprovalTitle: "Permission approval",
|
|
68
|
+
permissionApprovalNo: "No",
|
|
69
|
+
permissionApprovalOnce: "Yes this time",
|
|
70
|
+
permissionApprovalAlways: "Yes and always",
|
|
71
|
+
permissionApprovalFailed: "Permission approval failed.",
|
|
60
72
|
sandboxHostOption: "Host read-only",
|
|
61
73
|
sandboxDockerReadonlyOption: "Docker read-only",
|
|
62
74
|
sandboxDockerWorkspaceOption: "Docker workspace-write",
|
|
@@ -672,6 +684,8 @@ const setupApiKeyField = document.querySelector("#setup-api-key");
|
|
|
672
684
|
const saveApiKeyButton = document.querySelector("#save-api-key");
|
|
673
685
|
const setupStatusEl = document.querySelector("#setup-status");
|
|
674
686
|
const taskProfileField = document.querySelector("#taskProfile");
|
|
687
|
+
const permissionModeField = document.querySelector("#permissionMode");
|
|
688
|
+
const permissionHintEl = document.querySelector("#permission-hint");
|
|
675
689
|
const sandboxModeField = document.querySelector("#sandboxMode");
|
|
676
690
|
const packageInstallPolicyField = document.querySelector("#packageInstallPolicy");
|
|
677
691
|
const packageWarningEl = document.querySelector("#package-warning");
|
|
@@ -1218,6 +1232,60 @@ function updatePackageWarning() {
|
|
|
1218
1232
|
packageWarningEl.textContent = t(key);
|
|
1219
1233
|
}
|
|
1220
1234
|
|
|
1235
|
+
function permissionDefaults(mode = "normal") {
|
|
1236
|
+
if (mode === "safe") {
|
|
1237
|
+
return {
|
|
1238
|
+
sandboxMode: "docker-readonly",
|
|
1239
|
+
packageInstallPolicy: "prompt",
|
|
1240
|
+
workspaceWritePolicy: "prompt",
|
|
1241
|
+
allowPasswords: false,
|
|
1242
|
+
allowDestructive: false,
|
|
1243
|
+
allowOutsideWorkspaceFileTools: false,
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
if (mode === "danger") {
|
|
1247
|
+
return {
|
|
1248
|
+
sandboxMode: "host",
|
|
1249
|
+
packageInstallPolicy: "allow",
|
|
1250
|
+
workspaceWritePolicy: "allow",
|
|
1251
|
+
allowPasswords: true,
|
|
1252
|
+
allowDestructive: true,
|
|
1253
|
+
allowOutsideWorkspaceFileTools: true,
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
return {
|
|
1257
|
+
sandboxMode: "docker-workspace",
|
|
1258
|
+
packageInstallPolicy: "allow",
|
|
1259
|
+
workspaceWritePolicy: "allow",
|
|
1260
|
+
allowPasswords: false,
|
|
1261
|
+
allowDestructive: false,
|
|
1262
|
+
allowOutsideWorkspaceFileTools: false,
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
function updatePermissionHint() {
|
|
1267
|
+
if (!permissionHintEl || !permissionModeField) return;
|
|
1268
|
+
const key =
|
|
1269
|
+
permissionModeField.value === "safe"
|
|
1270
|
+
? "permissionHintSafe"
|
|
1271
|
+
: permissionModeField.value === "danger"
|
|
1272
|
+
? "permissionHintDanger"
|
|
1273
|
+
: "permissionHintNormal";
|
|
1274
|
+
permissionHintEl.textContent = t(key);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
function applyPermissionModeToForm(mode = permissionModeField?.value || "normal") {
|
|
1278
|
+
const defaults = permissionDefaults(mode);
|
|
1279
|
+
if (sandboxModeField) sandboxModeField.value = defaults.sandboxMode;
|
|
1280
|
+
if (packageInstallPolicyField) packageInstallPolicyField.value = defaults.packageInstallPolicy;
|
|
1281
|
+
const allowPasswordsField = document.querySelector("#allowPasswords");
|
|
1282
|
+
const allowDestructiveField = document.querySelector("#allowDestructive");
|
|
1283
|
+
if (allowPasswordsField) allowPasswordsField.checked = defaults.allowPasswords;
|
|
1284
|
+
if (allowDestructiveField) allowDestructiveField.checked = defaults.allowDestructive;
|
|
1285
|
+
updatePermissionHint();
|
|
1286
|
+
updatePackageWarning();
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1221
1289
|
function applyLanguage(language, { persist = true } = {}) {
|
|
1222
1290
|
currentLanguage = normalizeLanguage(language);
|
|
1223
1291
|
document.documentElement.lang = currentLanguage;
|
|
@@ -1244,6 +1312,7 @@ function applyLanguage(language, { persist = true } = {}) {
|
|
|
1244
1312
|
renderWorkspacePanel();
|
|
1245
1313
|
renderSandboxStatus();
|
|
1246
1314
|
updateRoutingHint();
|
|
1315
|
+
updatePermissionHint();
|
|
1247
1316
|
updatePackageWarning();
|
|
1248
1317
|
renderSessions(lastSessions);
|
|
1249
1318
|
renderSessionManager();
|
|
@@ -1274,8 +1343,10 @@ function formPayload() {
|
|
|
1274
1343
|
allowedDomains: document.querySelector("#allowedDomains").value.trim(),
|
|
1275
1344
|
commandCwd: document.querySelector("#commandCwd").value.trim(),
|
|
1276
1345
|
maxSteps: Number(document.querySelector("#maxSteps").value) || 24,
|
|
1346
|
+
permissionMode: permissionModeField?.value || "normal",
|
|
1277
1347
|
sandboxMode: sandboxModeField.value,
|
|
1278
1348
|
packageInstallPolicy: packageInstallPolicyField.value,
|
|
1349
|
+
workspaceWritePolicy: permissionDefaults(permissionModeField?.value || "normal").workspaceWritePolicy,
|
|
1279
1350
|
headless: document.querySelector("#headless").checked,
|
|
1280
1351
|
allowShellTool: document.querySelector("#allowShellTool").checked,
|
|
1281
1352
|
allowFileTools: document.querySelector("#allowFileTools").checked,
|
|
@@ -1290,6 +1361,7 @@ function formPayload() {
|
|
|
1290
1361
|
dockerSandboxImage: document.querySelector("#dockerSandboxImage").value.trim(),
|
|
1291
1362
|
allowPasswords: document.querySelector("#allowPasswords").checked,
|
|
1292
1363
|
allowDestructive: document.querySelector("#allowDestructive").checked,
|
|
1364
|
+
allowOutsideWorkspaceFileTools: permissionDefaults(permissionModeField?.value || "normal").allowOutsideWorkspaceFileTools,
|
|
1293
1365
|
};
|
|
1294
1366
|
}
|
|
1295
1367
|
|
|
@@ -1326,6 +1398,33 @@ function renderCommandOutputLog(entry) {
|
|
|
1326
1398
|
`;
|
|
1327
1399
|
}
|
|
1328
1400
|
|
|
1401
|
+
function renderPermissionApproval(entry) {
|
|
1402
|
+
const advice = entry.data?.permissionAdvice;
|
|
1403
|
+
if (!advice) return "";
|
|
1404
|
+
const summary = advice.summary || advice.reason || "Permission approval is required.";
|
|
1405
|
+
const category = advice.category || entry.data?.category || "";
|
|
1406
|
+
const suggested = advice.suggestedCommand
|
|
1407
|
+
? `<div class="log-fold-note"><strong>rerun:</strong> <code>${escapeHtml(advice.suggestedCommand)}</code></div>`
|
|
1408
|
+
: "";
|
|
1409
|
+
const trusted = advice.trustedHostCommand
|
|
1410
|
+
? `<div class="log-fold-note"><strong>host:</strong> <code>${escapeHtml(advice.trustedHostCommand)}</code></div>`
|
|
1411
|
+
: "";
|
|
1412
|
+
return `
|
|
1413
|
+
<article class="permission-approval">
|
|
1414
|
+
<strong>${escapeHtml(t("permissionApprovalTitle"))}</strong>
|
|
1415
|
+
<p>${escapeHtml(summary)}</p>
|
|
1416
|
+
<div class="subtle">${escapeHtml(category)}</div>
|
|
1417
|
+
${suggested}
|
|
1418
|
+
${trusted}
|
|
1419
|
+
<div class="permission-actions">
|
|
1420
|
+
<button type="button" class="mini-button danger" data-permission-action="no">${escapeHtml(t("permissionApprovalNo"))}</button>
|
|
1421
|
+
<button type="button" class="mini-button" data-permission-action="once">${escapeHtml(t("permissionApprovalOnce"))}</button>
|
|
1422
|
+
<button type="button" class="mini-button" data-permission-action="always">${escapeHtml(t("permissionApprovalAlways"))}</button>
|
|
1423
|
+
</div>
|
|
1424
|
+
</article>
|
|
1425
|
+
`;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1329
1428
|
function renderLogs(run) {
|
|
1330
1429
|
logsEl.dataset.mode = "active";
|
|
1331
1430
|
const parts = [
|
|
@@ -1340,6 +1439,10 @@ function renderLogs(run) {
|
|
|
1340
1439
|
continue;
|
|
1341
1440
|
}
|
|
1342
1441
|
parts.push(`<div class="log-line">${escapeHtml(`[${entry.at}] ${entry.kind}: ${entry.message}`)}</div>`);
|
|
1442
|
+
if (entry.message === "tool.blocked" && entry.data?.permissionAdvice) {
|
|
1443
|
+
parts.push(renderPermissionApproval(entry));
|
|
1444
|
+
continue;
|
|
1445
|
+
}
|
|
1343
1446
|
if (entry.data && Object.keys(entry.data).length > 0) {
|
|
1344
1447
|
parts.push(`<pre class="log-json">${escapeHtml(JSON.stringify(entry.data, null, 2))}</pre>`);
|
|
1345
1448
|
}
|
|
@@ -2270,6 +2373,36 @@ async function stopCurrentRun() {
|
|
|
2270
2373
|
await refreshRun();
|
|
2271
2374
|
}
|
|
2272
2375
|
|
|
2376
|
+
async function approvePermission(action) {
|
|
2377
|
+
if (!currentSessionId) return;
|
|
2378
|
+
chatStatusEl.textContent = t("sendingStatus");
|
|
2379
|
+
const response = await fetch(`/api/sessions/${encodeURIComponent(currentSessionId)}/approve-permission`, {
|
|
2380
|
+
method: "POST",
|
|
2381
|
+
headers: { "Content-Type": "application/json" },
|
|
2382
|
+
body: JSON.stringify({
|
|
2383
|
+
...formPayload(),
|
|
2384
|
+
action,
|
|
2385
|
+
}),
|
|
2386
|
+
});
|
|
2387
|
+
const data = await response.json().catch(() => ({}));
|
|
2388
|
+
if (!response.ok) {
|
|
2389
|
+
chatStatusEl.textContent = data.error || t("permissionApprovalFailed");
|
|
2390
|
+
return;
|
|
2391
|
+
}
|
|
2392
|
+
if (data.permissionMode && action === "always" && permissionModeField) {
|
|
2393
|
+
permissionModeField.value = data.permissionMode;
|
|
2394
|
+
applyPermissionModeToForm(data.permissionMode);
|
|
2395
|
+
}
|
|
2396
|
+
currentRunStatus = action === "no" ? currentRunStatus : "running";
|
|
2397
|
+
chatStatusEl.textContent = action === "no" ? t("permissionApprovalNo") : t("runningStatus");
|
|
2398
|
+
updateStopRunButton();
|
|
2399
|
+
await refreshRun();
|
|
2400
|
+
if (action !== "no") {
|
|
2401
|
+
if (pollTimer) clearInterval(pollTimer);
|
|
2402
|
+
pollTimer = setInterval(refreshRun, 1500);
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2273
2406
|
async function refreshChat() {
|
|
2274
2407
|
if (!currentSessionId) {
|
|
2275
2408
|
pendingInboxItems = [];
|
|
@@ -2595,6 +2728,10 @@ modelField.addEventListener("change", updateRoutingHint);
|
|
|
2595
2728
|
});
|
|
2596
2729
|
sandboxModeField.addEventListener("change", updatePackageWarning);
|
|
2597
2730
|
packageInstallPolicyField.addEventListener("change", updatePackageWarning);
|
|
2731
|
+
permissionModeField?.addEventListener("change", () => {
|
|
2732
|
+
applyPermissionModeToForm(permissionModeField.value);
|
|
2733
|
+
schedulePreferenceSave();
|
|
2734
|
+
});
|
|
2598
2735
|
allowWrapperToolsField.addEventListener("change", () => renderWrapperStatus());
|
|
2599
2736
|
preferredWrapperField.addEventListener("change", () => renderWrapperStatus());
|
|
2600
2737
|
taskProfileField?.addEventListener("change", () => {
|
|
@@ -2687,6 +2824,14 @@ stopRunButton?.addEventListener("click", () => {
|
|
|
2687
2824
|
});
|
|
2688
2825
|
});
|
|
2689
2826
|
|
|
2827
|
+
logsEl?.addEventListener("click", (event) => {
|
|
2828
|
+
const button = event.target.closest("[data-permission-action]");
|
|
2829
|
+
if (!button) return;
|
|
2830
|
+
approvePermission(button.dataset.permissionAction || "no").catch((error) => {
|
|
2831
|
+
chatStatusEl.textContent = error instanceof Error ? error.message : String(error);
|
|
2832
|
+
});
|
|
2833
|
+
});
|
|
2834
|
+
|
|
2690
2835
|
window.addEventListener("keydown", (event) => {
|
|
2691
2836
|
if (event.key !== "Escape" || currentRunStatus !== "running") return;
|
|
2692
2837
|
const tag = event.target?.tagName?.toLowerCase();
|
|
@@ -2870,6 +3015,7 @@ async function loadConfig() {
|
|
|
2870
3015
|
document.querySelector("#commandCwd").value = prefs.commandCwd || data.project?.root || "";
|
|
2871
3016
|
document.querySelector("#headless").checked = prefs.headless ?? data.defaults.headless;
|
|
2872
3017
|
document.querySelector("#maxSteps").value = prefs.maxSteps ?? data.defaults.maxSteps;
|
|
3018
|
+
if (permissionModeField) permissionModeField.value = prefs.permissionMode || "normal";
|
|
2873
3019
|
sandboxModeField.value = prefs.sandboxMode || (prefs.useDockerSandbox ? "docker-workspace" : "host");
|
|
2874
3020
|
packageInstallPolicyField.value = prefs.packageInstallPolicy || "allow";
|
|
2875
3021
|
document.querySelector("#allowShellTool").checked = prefs.allowShellTool ?? true;
|
|
@@ -2891,6 +3037,7 @@ async function loadConfig() {
|
|
|
2891
3037
|
await refreshWorkspaceChanges();
|
|
2892
3038
|
renderSandboxLogs(data.sandbox?.logs || []);
|
|
2893
3039
|
updateRoutingHint();
|
|
3040
|
+
updatePermissionHint();
|
|
2894
3041
|
updatePackageWarning();
|
|
2895
3042
|
|
|
2896
3043
|
renderSessions(data.sessions || []);
|
package/public/index.html
CHANGED
|
@@ -154,6 +154,16 @@
|
|
|
154
154
|
</div>
|
|
155
155
|
<p id="package-warning" class="subtle package-warning"></p>
|
|
156
156
|
|
|
157
|
+
<label>
|
|
158
|
+
<span data-i18n="permissionModeLabel">Permission shortcut</span>
|
|
159
|
+
<select id="permissionMode" name="permissionMode">
|
|
160
|
+
<option value="safe" data-i18n="permissionSafeOption">Safe: ask before writes/setup</option>
|
|
161
|
+
<option value="normal" data-i18n="permissionNormalOption">Normal: project writes + Docker setup</option>
|
|
162
|
+
<option value="danger" data-i18n="permissionDangerOption">Danger: trusted host/full access</option>
|
|
163
|
+
</select>
|
|
164
|
+
</label>
|
|
165
|
+
<p id="permission-hint" class="subtle route-hint"></p>
|
|
166
|
+
|
|
157
167
|
<div class="grid">
|
|
158
168
|
<label>
|
|
159
169
|
<span data-i18n="sandboxModeLabel">Sandbox mode</span>
|
package/public/styles.css
CHANGED
|
@@ -875,6 +875,26 @@ button.danger {
|
|
|
875
875
|
background: rgba(255, 255, 255, 0.06);
|
|
876
876
|
}
|
|
877
877
|
|
|
878
|
+
.permission-approval {
|
|
879
|
+
margin: 8px 0 14px;
|
|
880
|
+
padding: 12px;
|
|
881
|
+
border: 1px solid rgba(251, 146, 60, 0.42);
|
|
882
|
+
border-radius: 14px;
|
|
883
|
+
background: rgba(124, 45, 18, 0.22);
|
|
884
|
+
color: #ffedd5;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
.permission-approval p {
|
|
888
|
+
margin: 6px 0;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
.permission-actions {
|
|
892
|
+
display: flex;
|
|
893
|
+
flex-wrap: wrap;
|
|
894
|
+
gap: 8px;
|
|
895
|
+
margin-top: 10px;
|
|
896
|
+
}
|
|
897
|
+
|
|
878
898
|
.log-stream-title,
|
|
879
899
|
.log-fold-note {
|
|
880
900
|
padding: 0 12px 8px;
|
|
@@ -64,7 +64,7 @@ function cellWidth(value = "") {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
function runChat(inputText) {
|
|
67
|
-
return runCli(["chat", "--provider", "mock", "--routing", "manual", "--profile", "code"], inputText);
|
|
67
|
+
return runCli(["chat", "--provider", "mock", "--routing", "manual", "--profile", "code", "--headless"], inputText);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
function runCli(args, inputText) {
|
|
@@ -163,6 +163,7 @@ try {
|
|
|
163
163
|
"| Check | Result |",
|
|
164
164
|
"| --- | --- |",
|
|
165
165
|
"| `/.dockerenv` | **Present** |",
|
|
166
|
+
"| `data_helper_report.md` | **Found** |",
|
|
166
167
|
"| Hostname | `abc123` |",
|
|
167
168
|
].join("\n")
|
|
168
169
|
);
|
|
@@ -175,6 +176,9 @@ try {
|
|
|
175
176
|
if ((renderedMarkdown.match(/Present/g) || []).length !== 1) {
|
|
176
177
|
throw new Error("terminal markdown renderer duplicated table rows");
|
|
177
178
|
}
|
|
179
|
+
if (!renderedMarkdown.includes("data_helper_report.md")) {
|
|
180
|
+
throw new Error("terminal markdown renderer corrupted underscores in table cell literals");
|
|
181
|
+
}
|
|
178
182
|
const renderedMarkdownFence = stripMarkdown(
|
|
179
183
|
[
|
|
180
184
|
"```markdown",
|
|
@@ -382,6 +386,14 @@ try {
|
|
|
382
386
|
if (scsAutoArgs.enableScs !== "auto" || scsAutoArgs.goal !== "fix this project") {
|
|
383
387
|
throw new Error("--scs auto should consume auto and preserve the task");
|
|
384
388
|
}
|
|
389
|
+
const unknownOptionArgs = parseArgs(["--provider", "mock", "--allow-web-search", "research this"]);
|
|
390
|
+
if (unknownOptionArgs.unknownOptions[0] !== "--allow-web-search" || unknownOptionArgs.goal !== "research this") {
|
|
391
|
+
throw new Error("unknown option-like arguments before the prompt should be reported, not silently folded into the goal");
|
|
392
|
+
}
|
|
393
|
+
const dashedPromptArgs = parseArgs(["--provider", "mock", "--", "--allow-web-search should be prompt text"]);
|
|
394
|
+
if (dashedPromptArgs.unknownOptions.length || dashedPromptArgs.goal !== "--allow-web-search should be prompt text") {
|
|
395
|
+
throw new Error("explicit -- delimiter should allow prompt text that starts with a dash");
|
|
396
|
+
}
|
|
385
397
|
const resumeAfterOptions = parseResumeCommandArgs([
|
|
386
398
|
"web-agent-smoke",
|
|
387
399
|
"--provider",
|
|
@@ -412,6 +424,10 @@ try {
|
|
|
412
424
|
) {
|
|
413
425
|
throw new Error("resume subcommand should preserve explicit prompt text after --");
|
|
414
426
|
}
|
|
427
|
+
const resumeUnknownOption = parseResumeCommandArgs(["web-agent-smoke", "--allow-web-search", "continue research"]);
|
|
428
|
+
if (resumeUnknownOption.unknownOptions[0] !== "--allow-web-search" || resumeUnknownOption.prompt !== "continue research") {
|
|
429
|
+
throw new Error("resume subcommand should report unknown option-like arguments before prompt text");
|
|
430
|
+
}
|
|
415
431
|
const leadingResume = splitResumeCommandArgv(["--provider", "mock", "--routing", "manual", "resume", "latest"]);
|
|
416
432
|
if (
|
|
417
433
|
!leadingResume ||
|
|
@@ -440,6 +456,7 @@ try {
|
|
|
440
456
|
!helpResult.stdout.includes("/auxiliary") ||
|
|
441
457
|
!helpResult.stdout.includes("/review") ||
|
|
442
458
|
!helpResult.stdout.includes("/scs") ||
|
|
459
|
+
!helpResult.stdout.includes("/safe") ||
|
|
443
460
|
helpResult.stdout.includes("helpReview") ||
|
|
444
461
|
helpResult.stdout.includes("helpRename") ||
|
|
445
462
|
helpResult.stdout.includes("/enable" + "ss") ||
|
|
@@ -480,6 +497,18 @@ try {
|
|
|
480
497
|
if (!scsAutoResult.stdout.includes("scs=auto")) {
|
|
481
498
|
throw new Error("interactive /scs auto did not enable auto mode");
|
|
482
499
|
}
|
|
500
|
+
const safeResult = await runChat("/safe\n");
|
|
501
|
+
if (!safeResult.stdout.includes("permission=safe") || !safeResult.stdout.includes("writePolicy=prompt")) {
|
|
502
|
+
throw new Error("interactive /safe did not switch to safe permission mode");
|
|
503
|
+
}
|
|
504
|
+
const normalResult = await runChat("/normal\n");
|
|
505
|
+
if (!normalResult.stdout.includes("permission=normal") || !normalResult.stdout.includes("sandbox=docker-workspace")) {
|
|
506
|
+
throw new Error("interactive /normal did not switch to normal permission mode");
|
|
507
|
+
}
|
|
508
|
+
const dangerResult = await runChat("/danger\n");
|
|
509
|
+
if (!dangerResult.stdout.includes("permission=danger") || !dangerResult.stdout.includes("sandbox=host")) {
|
|
510
|
+
throw new Error("interactive /danger did not switch to danger permission mode");
|
|
511
|
+
}
|
|
483
512
|
const abbreviatedSkillsResult = await runChat("/sk website\n/ex\n");
|
|
484
513
|
if (abbreviatedSkillsResult.stdout.includes("Unknown command") || !abbreviatedSkillsResult.stdout.includes("website-app")) {
|
|
485
514
|
throw new Error("interactive slash command prefix did not auto-select the first matching command");
|
|
@@ -558,6 +587,7 @@ try {
|
|
|
558
587
|
"skills-command",
|
|
559
588
|
"review-command",
|
|
560
589
|
"scs-command-toggle",
|
|
590
|
+
"permission-mode-slash-commands",
|
|
561
591
|
"resume-command-options",
|
|
562
592
|
"slash-prefix-autoselect",
|
|
563
593
|
"slash-prefix-canonical-history",
|