pi-landstrip 0.18.34 → 0.18.35
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 +42 -163
- package/dist/index.ts +361 -276
- package/package.json +2 -2
- package/LICENSE +0 -201
package/README.md
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# pi-landstrip
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
later.
|
|
3
|
+
Pi extension for sandboxed Bash, primary agents, and process-backed subagents.
|
|
4
|
+
Pi 0.82.0 and Node.js 22.19.0 or newer are required.
|
|
6
5
|
|
|
7
6
|
## Install
|
|
8
7
|
|
|
@@ -10,189 +9,69 @@ later.
|
|
|
10
9
|
pi install npm:pi-landstrip
|
|
11
10
|
```
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
```json
|
|
17
|
-
{
|
|
18
|
-
"packages": ["npm:pi-landstrip"]
|
|
19
|
-
}
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
The package includes Landstrip binaries for Linux, macOS, and Windows on x64 and
|
|
23
|
-
Arm64. If sandboxing is enabled but the binary or platform is unusable, Bash,
|
|
24
|
-
shell commands, and sandboxed workers fail closed. Only `--no-sandbox` or
|
|
25
|
-
`"enabled": false` permits execution without OS isolation.
|
|
12
|
+
The package includes Landstrip binaries for Linux, macOS, and Windows on x64
|
|
13
|
+
and Arm64. When sandboxing is enabled, an unusable binary or platform fails
|
|
14
|
+
closed. Use `--no-sandbox` or set `enabled` to `false` for unsandboxed execution.
|
|
26
15
|
|
|
27
16
|
## Permissions
|
|
28
17
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
| Category | Checked | Controls |
|
|
32
|
-
| -------- | --------------------------------------- | -------------------------------- |
|
|
33
|
-
| Agent | Before dispatch | Whether an agent may call a tool |
|
|
34
|
-
| Sandbox | Before file access and during processes | Filesystem and network resources |
|
|
35
|
-
|
|
36
|
-
Agent rules return `deny`, `ask`, or `allow`. An agent approval authorizes only
|
|
37
|
-
tool dispatch and cannot bypass a sandbox hard denial.
|
|
38
|
-
|
|
39
|
-
By default, primary `read`, `write`, `edit`, and `apply_patch` tools run in the
|
|
40
|
-
trusted Pi process and only agent permissions apply. Set
|
|
41
|
-
`toolFilesystemPolicy` to `"sandbox"` to preflight their paths against
|
|
42
|
-
`sandbox.json`. `denyWrite` remains a hard denial. If both agent and sandbox
|
|
43
|
-
approval are needed, Pi presents one prompt for the call.
|
|
44
|
-
|
|
45
|
-
AI Bash, `!`, `!!`, and subagent processes use OS isolation. Subagents rely on
|
|
46
|
-
that isolation instead of repeating file-tool preflight. Pi presents all agent
|
|
47
|
-
and sandbox requests through one FIFO prompt queue.
|
|
48
|
-
|
|
49
|
-
Sandbox approvals may apply once, for the session, to the project, or globally.
|
|
50
|
-
Persistent approvals update `.pi/sandbox.json` or
|
|
51
|
-
`~/.pi/agent/sandbox.json`. Headless requests that require approval are denied.
|
|
52
|
-
|
|
53
|
-
## Sandbox configuration
|
|
18
|
+
Agent permissions control tool dispatch. Sandbox permissions control filesystem
|
|
19
|
+
and network access. Agent approval never bypasses a sandbox hard denial.
|
|
54
20
|
|
|
55
|
-
|
|
21
|
+
AI Bash, `!`, `!!`, and subagents use OS isolation. Primary file tools use the
|
|
22
|
+
trusted Pi process unless `toolFilesystemPolicy` is `"sandbox"`. Persistent
|
|
23
|
+
sandbox approvals update `.pi/sandbox.json` or `~/.pi/agent/sandbox.json`.
|
|
56
24
|
|
|
57
|
-
|
|
58
|
-
2. `~/.pi/agent/sandbox.json`;
|
|
59
|
-
3. `.pi/sandbox.json` for a trusted project.
|
|
25
|
+
## Sandbox settings
|
|
60
26
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
27
|
+
Policy merges from bundled `sandbox.json`, global
|
|
28
|
+
`~/.pi/agent/sandbox.json`, then trusted-project `.pi/sandbox.json`.
|
|
29
|
+
`/settings` displays or toggles the effective policy.
|
|
64
30
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
| `network.allowUnixSockets` | `[]` |
|
|
77
|
-
| `network.allowedDomains` | `[]` |
|
|
78
|
-
| `network.deniedDomains` | `[]` |
|
|
79
|
-
| `windows.appContainerMode` | `"standard"` |
|
|
80
|
-
| `windows.allowLoopback` | `false` |
|
|
81
|
-
|
|
82
|
-
Filesystem and core network semantics follow the main
|
|
83
|
-
[Landstrip policy](https://github.com/landstrip/landstrip#policy). `allowedDomains`
|
|
84
|
-
and `deniedDomains` are plugin fields enforced by the local HTTP/HTTPS proxy.
|
|
85
|
-
|
|
86
|
-
`shell.readAccess: "host"` gives primary Bash and `!`/`!!` the trusted host read
|
|
87
|
-
view on Linux and macOS while retaining sandboxed writes. `"policy"` applies
|
|
88
|
-
`denyRead` and `allowRead`. Workers always use policy reads. Windows always uses
|
|
89
|
-
policy reads because it requires an explicit read allowlist.
|
|
90
|
-
|
|
91
|
-
With `enabled: false`, subagents remain separate processes but lose Landstrip OS
|
|
92
|
-
isolation; Pi warns once per session.
|
|
93
|
-
|
|
94
|
-
## Agent configuration
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"enabled": true,
|
|
34
|
+
"shell": { "readAccess": "host" },
|
|
35
|
+
"filesystem": {
|
|
36
|
+
"allowWrite": ["."],
|
|
37
|
+
"denyWrite": ["**/.env", "**/*.pem"]
|
|
38
|
+
},
|
|
39
|
+
"network": { "allowNetwork": false }
|
|
40
|
+
}
|
|
41
|
+
```
|
|
95
42
|
|
|
96
|
-
|
|
97
|
-
|
|
43
|
+
See [`sandbox.json`](sandbox.json) for exact defaults. Workers always use policy
|
|
44
|
+
reads. Windows requires an explicit read allowlist.
|
|
98
45
|
|
|
99
|
-
|
|
100
|
-
- `.pi/landstrip.json`
|
|
46
|
+
## Agents
|
|
101
47
|
|
|
102
|
-
|
|
103
|
-
|
|
48
|
+
Configure agents in `~/.pi/agent/landstrip.json`, `.pi/landstrip.json`, or a
|
|
49
|
+
`landstrip` object in the matching Pi settings file:
|
|
104
50
|
|
|
105
51
|
```json
|
|
106
52
|
{
|
|
107
53
|
"maxSubagents": 2,
|
|
108
54
|
"toolFilesystemPolicy": "sandbox",
|
|
109
|
-
"agent": {
|
|
110
|
-
"review": {
|
|
111
|
-
"description": "Review without modifying files",
|
|
112
|
-
"mode": "subagent",
|
|
113
|
-
"prompt": "Report concrete findings.",
|
|
114
|
-
"permission": {
|
|
115
|
-
"edit": "deny",
|
|
116
|
-
"bash": "ask"
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
55
|
"permission": {
|
|
121
|
-
"task": {
|
|
122
|
-
"*": "deny",
|
|
123
|
-
"review": "allow"
|
|
124
|
-
}
|
|
56
|
+
"task": { "*": "deny", "review": "allow" }
|
|
125
57
|
}
|
|
126
58
|
}
|
|
127
59
|
```
|
|
128
60
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
| `toolFilesystemPolicy` | `"host"` | `"host"` or `"sandbox"` |
|
|
133
|
-
| `agent` | built-in agents | named agent definitions |
|
|
134
|
-
| `permission` | built-in rules | global tool/resource rules |
|
|
135
|
-
|
|
136
|
-
Agent definitions accept `name`, `description`, `prompt`, `mode`, `model`,
|
|
137
|
-
`variant`, `temperature`, `top_p`, `steps`, `color`, `hidden`, `disable`,
|
|
138
|
-
`options`, and `permission`. `mode` is `primary`, `subagent`, or `all` and
|
|
139
|
-
defaults to `all`. `/agents` Primary lists only `mode: primary` agents. The
|
|
140
|
-
Subagent tab lists `mode: subagent` and `mode: all` agents, including JSON and
|
|
141
|
-
Markdown definitions.
|
|
142
|
-
|
|
143
|
-
The built-in primary agents are `build` and `plan`; built-in subagents are
|
|
144
|
-
`general`, `scout`, and `explore`. Pi also loads Markdown agents from
|
|
145
|
-
`~/.pi/agent/agents/` and `.pi/agents/`. Project definitions overlay global
|
|
146
|
-
definitions. Configured fields overlay Markdown agents with the same name, so a
|
|
147
|
-
model-only `landstrip.json` entry keeps the Markdown agent's mode, prompt, and
|
|
148
|
-
permissions.
|
|
61
|
+
`maxSubagents` accepts 0 through 16. Agent definitions can override model,
|
|
62
|
+
prompt, mode, options, and permissions. Global definitions load before trusted
|
|
63
|
+
project definitions.
|
|
149
64
|
|
|
150
65
|
## Commands and tasks
|
|
151
66
|
|
|
152
|
-
- `/
|
|
153
|
-
- `/
|
|
154
|
-
- `
|
|
155
|
-
|
|
156
|
-
The `task` tool requires `description`, `prompt`, and `subagent_type`. Optional
|
|
157
|
-
`task_id` continues a saved task, `command` records the originating command, and
|
|
158
|
-
`background: true` returns immediately. `/agents <task-id>` inspects a task.
|
|
159
|
-
Each invocation starts a fresh Pi RPC process; continuation restores its saved
|
|
160
|
-
session in a new process.
|
|
161
|
-
|
|
162
|
-
Workers use normal Pi resource and plugin discovery. Their model requests can
|
|
163
|
-
read Pi authentication and inherited credential environment variables. Install
|
|
164
|
-
only trusted worker plugins and use credentials appropriate for the sandboxed
|
|
165
|
-
task.
|
|
166
|
-
|
|
167
|
-
## Permission ask providers
|
|
168
|
-
|
|
169
|
-
A separate Pi extension can resolve agent permission rules that return `ask` by
|
|
170
|
-
registering one `LandstripPermissionAskProvider`:
|
|
171
|
-
|
|
172
|
-
```ts
|
|
173
|
-
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
174
|
-
import { provideLandstripPermissionAsk } from 'pi-landstrip/api';
|
|
175
|
-
|
|
176
|
-
export default function permissionReviewer(pi: ExtensionAPI): void {
|
|
177
|
-
provideLandstripPermissionAsk(pi, {
|
|
178
|
-
id: 'example-reviewer',
|
|
179
|
-
async decide(request) {
|
|
180
|
-
if (request.toolName === 'bash' && request.input.command === 'git status') {
|
|
181
|
-
return { decision: 'allow' };
|
|
182
|
-
}
|
|
183
|
-
return { decision: 'abstain' };
|
|
184
|
-
},
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
```
|
|
67
|
+
- `/settings` opens a screen-wide pane with sandbox and agent settings.
|
|
68
|
+
- `/sandbox` opens the same pane on its sandbox section.
|
|
69
|
+
- `/agents` selects agents and lists task sessions.
|
|
70
|
+
- `Ctrl+Shift+A` cycles primary agents while Pi is idle.
|
|
188
71
|
|
|
189
|
-
The
|
|
190
|
-
|
|
191
|
-
`deny` blocks it, and `abstain` uses the normal UI prompt or headless denial.
|
|
192
|
-
Provider errors also fall back safely. Only one provider may be registered at a
|
|
193
|
-
time; the helper works regardless of extension load order and returns a disposer.
|
|
72
|
+
The `task` tool starts isolated workers. Set `background: true` to return
|
|
73
|
+
immediately and use `task_id` to continue a saved task.
|
|
194
74
|
|
|
195
|
-
##
|
|
75
|
+
## Licensing
|
|
196
76
|
|
|
197
|
-
|
|
198
|
-
licensed separately as `Apache-2.0 AND LGPL-2.1-or-later`.
|
|
77
|
+
[Apache 2.0](../../LICENSE-APACHE-2.0).
|
package/dist/index.ts
CHANGED
|
@@ -31,7 +31,6 @@ import {
|
|
|
31
31
|
SettingsManager,
|
|
32
32
|
withFileMutationQueue as withFileMutationQueue3
|
|
33
33
|
} from "@earendil-works/pi-coding-agent";
|
|
34
|
-
import { matchesKey as matchesKey2, truncateToWidth as truncateToWidth3 } from "@earendil-works/pi-tui";
|
|
35
34
|
import {
|
|
36
35
|
binaryPath
|
|
37
36
|
} from "@landstrip/landstrip";
|
|
@@ -599,6 +598,23 @@ async function setMaxSubagentsConfigForScope(cwd, maxSubagents, scope, agentDir
|
|
|
599
598
|
async function clearMaxSubagentsConfigForScope(cwd, scope, agentDir = getAgentDir()) {
|
|
600
599
|
await writeMaxSubagentsConfigForScope(cwd, undefined, scope, agentDir);
|
|
601
600
|
}
|
|
601
|
+
async function writeToolFilesystemPolicyConfigForScope(cwd, policy, scope, agentDir) {
|
|
602
|
+
await updateLandstripConfigForScope(cwd, scope, agentDir, (landstrip) => {
|
|
603
|
+
if (policy === undefined)
|
|
604
|
+
delete landstrip.toolFilesystemPolicy;
|
|
605
|
+
else
|
|
606
|
+
landstrip.toolFilesystemPolicy = policy;
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
async function setToolFilesystemPolicyConfigForScope(cwd, policy, scope, agentDir = getAgentDir()) {
|
|
610
|
+
if (policy !== "host" && policy !== "sandbox") {
|
|
611
|
+
throw new Error("toolFilesystemPolicy must be host or sandbox");
|
|
612
|
+
}
|
|
613
|
+
await writeToolFilesystemPolicyConfigForScope(cwd, policy, scope, agentDir);
|
|
614
|
+
}
|
|
615
|
+
async function clearToolFilesystemPolicyConfigForScope(cwd, scope, agentDir = getAgentDir()) {
|
|
616
|
+
await writeToolFilesystemPolicyConfigForScope(cwd, undefined, scope, agentDir);
|
|
617
|
+
}
|
|
602
618
|
async function setAgentDisabledForScope(cwd, name, disabled, scope, agentDir = getAgentDir()) {
|
|
603
619
|
await updateLandstripConfigForScope(cwd, scope, agentDir, (landstrip) => {
|
|
604
620
|
if (landstrip.agent !== undefined && !isRecord2(landstrip.agent)) {
|
|
@@ -696,6 +712,16 @@ function loadMaxSubagentsSettings(cwd, includeProject = true, agentDir = getAgen
|
|
|
696
712
|
}
|
|
697
713
|
return { global, project };
|
|
698
714
|
}
|
|
715
|
+
function loadToolFilesystemPolicySettings(cwd, includeProject = true, agentDir = getAgentDir()) {
|
|
716
|
+
const global = loadLandstripConfig(cwd, false, agentDir).toolFilesystemPolicy;
|
|
717
|
+
if (!includeProject)
|
|
718
|
+
return { global };
|
|
719
|
+
const project = readLandstripConfigSource(cwd, "project", agentDir).config.toolFilesystemPolicy;
|
|
720
|
+
if (project !== undefined && project !== "host" && project !== "sandbox") {
|
|
721
|
+
throw new Error("toolFilesystemPolicy must be host or sandbox");
|
|
722
|
+
}
|
|
723
|
+
return { global, project };
|
|
724
|
+
}
|
|
699
725
|
|
|
700
726
|
// opencode-agents.ts
|
|
701
727
|
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3, statSync } from "node:fs";
|
|
@@ -1755,19 +1781,17 @@ var PI_THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xh
|
|
|
1755
1781
|
var AGENTS_HELP_ROWS = [
|
|
1756
1782
|
["Tab", "Next tab"],
|
|
1757
1783
|
["↑ / ↓", "Select item or scroll when follow is off"],
|
|
1784
|
+
["Enter", "Activate: set primary, open log, confirm"],
|
|
1785
|
+
["Esc", "Back one level, or close from a top-level tab"],
|
|
1786
|
+
["Ctrl+C", "Close"],
|
|
1787
|
+
["Space", "Toggle agent enabled or select task"],
|
|
1788
|
+
["X", "Delete selected agent or task sessions"],
|
|
1789
|
+
["E", "Edit project agent"],
|
|
1790
|
+
["I", "Inherit global agent state"],
|
|
1758
1791
|
["F", "Toggle task log follow"],
|
|
1759
1792
|
["Page Up / Down", "Scroll task output by page"],
|
|
1760
1793
|
["Home / End", "Jump to task output boundary"],
|
|
1761
|
-
["
|
|
1762
|
-
["Esc", "Cancel edit, go back, or close"],
|
|
1763
|
-
["Ctrl+C", "Close"],
|
|
1764
|
-
["Ctrl+D", "Delete selected task sessions"],
|
|
1765
|
-
["Space", "Enable agent or select task"],
|
|
1766
|
-
["E", "Edit project agent"],
|
|
1767
|
-
["X", "Delete project agent"],
|
|
1768
|
-
["Y / N", "Confirm or cancel deletion"],
|
|
1769
|
-
["Empty + Enter", "Inherit global setting"],
|
|
1770
|
-
["Backspace", "Open parent task or edit setting"]
|
|
1794
|
+
["Backspace", "Open parent task"]
|
|
1771
1795
|
];
|
|
1772
1796
|
var MIN_SUPPORTED_PI_VERSION = [0, 82, 0];
|
|
1773
1797
|
var cachedPiPackage;
|
|
@@ -2622,6 +2646,14 @@ class SubagentRuntime {
|
|
|
2622
2646
|
description: "Manage agents, tasks, and concurrency",
|
|
2623
2647
|
handler: async (args, ctx) => this.openAgents(args, ctx)
|
|
2624
2648
|
});
|
|
2649
|
+
this.pi.registerCommand("settings", {
|
|
2650
|
+
description: "Manage sandbox and agent settings",
|
|
2651
|
+
handler: async (_args, ctx) => this.openSettings(ctx)
|
|
2652
|
+
});
|
|
2653
|
+
this.pi.registerCommand("sandbox", {
|
|
2654
|
+
description: "Inspect and toggle the sandbox",
|
|
2655
|
+
handler: async (_args, ctx) => this.openSettings(ctx)
|
|
2656
|
+
});
|
|
2625
2657
|
this.pi.registerShortcut("ctrl+shift+a", {
|
|
2626
2658
|
description: "Cycle to the next primary agent",
|
|
2627
2659
|
handler: async (ctx) => this.cyclePrimaryAgent(ctx)
|
|
@@ -2749,7 +2781,6 @@ ${this.primaryAgent.prompt}` };
|
|
|
2749
2781
|
ctx.ui.notify(diagnostic, "warning");
|
|
2750
2782
|
let agents = [...catalog.agents.values()].filter((agent) => !agent.hidden).sort((left, right) => left.name.localeCompare(right.name));
|
|
2751
2783
|
let disabledOverrides = loadAgentDisabledOverrides(ctx.cwd, projectTrusted);
|
|
2752
|
-
let maxSubagentsSettings = loadMaxSubagentsSettings(ctx.cwd, projectTrusted);
|
|
2753
2784
|
let tab = requested ? "log" : "primary";
|
|
2754
2785
|
const agentsForTab = () => agents.filter((agent) => tab === "subagent" ? agentSupportsMode(agent, "subagent") : isPrimaryAgent(agent));
|
|
2755
2786
|
let selectedAgent = Math.max(0, agentsForTab().findIndex((agent) => agent.name === this.primaryAgent?.name));
|
|
@@ -2758,8 +2789,6 @@ ${this.primaryAgent.prompt}` };
|
|
|
2758
2789
|
ctx.ui.notify(`Unknown task session: ${requested}`, "error");
|
|
2759
2790
|
return;
|
|
2760
2791
|
}
|
|
2761
|
-
let selectedSetting = 0;
|
|
2762
|
-
let settingEditor;
|
|
2763
2792
|
let saving = false;
|
|
2764
2793
|
let confirmingDeleteAgent;
|
|
2765
2794
|
let pendingTaskAction;
|
|
@@ -2775,9 +2804,6 @@ ${this.primaryAgent.prompt}` };
|
|
|
2775
2804
|
const nextSelected = selectedName ? listedAgents.findIndex((agent) => agent.name === selectedName) : selectedAgent;
|
|
2776
2805
|
selectedAgent = Math.max(0, Math.min(nextSelected < 0 ? 0 : nextSelected, listedAgents.length - 1));
|
|
2777
2806
|
};
|
|
2778
|
-
const reloadSettings = () => {
|
|
2779
|
-
maxSubagentsSettings = loadMaxSubagentsSettings(ctx.cwd, projectTrusted);
|
|
2780
|
-
};
|
|
2781
2807
|
const refreshTasks = () => {
|
|
2782
2808
|
const selectedId = tasks[selectedTask]?.id;
|
|
2783
2809
|
tasks = [...this.tasks.values()];
|
|
@@ -2804,7 +2830,6 @@ ${this.primaryAgent.prompt}` };
|
|
|
2804
2830
|
this.pi.registerTool(this.createTaskTool(undefined, this.primaryRules, ctx));
|
|
2805
2831
|
}
|
|
2806
2832
|
};
|
|
2807
|
-
const selectedMaxSubagents = () => maxSubagentsSettings.project ?? maxSubagentsSettings.global;
|
|
2808
2833
|
await ctx.ui.custom((tui, theme, _keybindings, done) => ({
|
|
2809
2834
|
render: (width) => {
|
|
2810
2835
|
const contentWidth = Math.max(1, width - 4);
|
|
@@ -2818,20 +2843,34 @@ ${this.primaryAgent.prompt}` };
|
|
|
2818
2843
|
return `${clipped}${" ".repeat(Math.max(0, cellWidth - visibleWidth2(clipped)))}`;
|
|
2819
2844
|
};
|
|
2820
2845
|
refreshTasks();
|
|
2821
|
-
const tabs = dialogTabs(theme, ["Primary", "Subagent", "Tasks", "Log", "
|
|
2846
|
+
const tabs = dialogTabs(theme, ["Primary", "Subagent", "Tasks", "Log", "Help"], `${tab[0]?.toUpperCase()}${tab.slice(1)}`);
|
|
2822
2847
|
if (tab === "primary" || tab === "subagent") {
|
|
2823
2848
|
const listedAgents = agentsForTab();
|
|
2824
2849
|
const start = Math.max(0, Math.min(selectedAgent - 3, listedAgents.length - 7));
|
|
2825
2850
|
const shown2 = listedAgents.slice(start, start + 7);
|
|
2826
2851
|
const nameWidth = Math.min(20, Math.max(5, ...listedAgents.map((agent2) => visibleWidth2(`@${agent2.name}`))));
|
|
2827
|
-
const
|
|
2828
|
-
const
|
|
2829
|
-
const
|
|
2830
|
-
const
|
|
2852
|
+
const modelWidth = Math.max(10, Math.min(24, Math.floor((contentWidth - 2) / 3)));
|
|
2853
|
+
const enabledWidth = 13;
|
|
2854
|
+
const runtimeWidth = Math.max(12, contentWidth - 2 - nameWidth - 1 - modelWidth - 1 - enabledWidth - 1);
|
|
2855
|
+
const runtimeFor = (agent2) => {
|
|
2856
|
+
const parts = [];
|
|
2857
|
+
if (agent2.name === this.primaryAgent?.name)
|
|
2858
|
+
parts.push("★ primary");
|
|
2859
|
+
const byState = (state) => [...this.tasks.values()].filter((task2) => task2.agent === agent2.name && task2.state === state).length;
|
|
2860
|
+
const running = byState("running");
|
|
2861
|
+
const queued = byState("queued");
|
|
2862
|
+
if (running > 0)
|
|
2863
|
+
parts.push(`${running} running`);
|
|
2864
|
+
if (queued > 0)
|
|
2865
|
+
parts.push(`${queued} queued`);
|
|
2866
|
+
const rules = [...catalog.permissions, ...agent2.permissions].length;
|
|
2867
|
+
parts.push(rules > 0 ? `${rules} rules` : "ask by default");
|
|
2868
|
+
return parts.length > 0 ? parts.join(" · ") : "—";
|
|
2869
|
+
};
|
|
2831
2870
|
const lines2 = [
|
|
2832
2871
|
tabs,
|
|
2833
2872
|
"",
|
|
2834
|
-
theme.fg("dim", ` ${pad("Agent", nameWidth)} ${pad("
|
|
2873
|
+
theme.fg("dim", ` ${pad("Agent", nameWidth)} ${pad("Model", modelWidth)} ${pad("Enabled", enabledWidth)} ${pad("Runtime", runtimeWidth)}`)
|
|
2835
2874
|
];
|
|
2836
2875
|
for (const [offset, agent2] of shown2.entries()) {
|
|
2837
2876
|
const index = start + offset;
|
|
@@ -2840,11 +2879,12 @@ ${this.primaryAgent.prompt}` };
|
|
|
2840
2879
|
const disabled = agent2.disabled;
|
|
2841
2880
|
const deleting = confirmingDeleteAgent === agent2.name;
|
|
2842
2881
|
const inherited = !disabledOverrides.project.has(agent2.name);
|
|
2843
|
-
const
|
|
2882
|
+
const enabled = inherited ? `inherited ${disabled ? "off" : "on"}` : disabled ? "disabled" : "enabled";
|
|
2844
2883
|
const color = deleting ? "error" : disabled ? "muted" : "text";
|
|
2845
2884
|
const plainName = pad(`@${agent2.name}`, nameWidth);
|
|
2846
2885
|
const name = deleting ? theme.fg("error", plainName) : disabled ? theme.fg("muted", plainName) : colorizeAgentText(agent2.color, plainName, (agentColor, text) => theme.fg(agentColor, text));
|
|
2847
|
-
const
|
|
2886
|
+
const runtime = runtimeFor(agent2);
|
|
2887
|
+
const line = `${cursor} ${name} ${theme.fg(color, pad(agent2.model ?? "current model", modelWidth))} ${theme.fg(deleting ? "error" : disabled ? "warning" : color, pad(enabled, enabledWidth))} ${theme.fg(disabled ? "muted" : "dim", pad(runtime, runtimeWidth))}`;
|
|
2848
2888
|
lines2.push(agent2.name === this.primaryAgent?.name ? theme.bold(line) : line);
|
|
2849
2889
|
}
|
|
2850
2890
|
if (listedAgents.length === 0) {
|
|
@@ -2854,6 +2894,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
2854
2894
|
if (agent) {
|
|
2855
2895
|
lines2.push("", theme.fg(agent.disabled ? "muted" : "text", agent.description ?? "No description"));
|
|
2856
2896
|
const details = [
|
|
2897
|
+
agent.source,
|
|
2857
2898
|
agent.variant ? `variant ${agent.variant}` : undefined,
|
|
2858
2899
|
agent.steps ? `${agent.steps} steps` : undefined
|
|
2859
2900
|
].filter(Boolean);
|
|
@@ -2886,44 +2927,14 @@ ${this.primaryAgent.prompt}` };
|
|
|
2886
2927
|
}
|
|
2887
2928
|
if (confirmingDeleteAgent) {
|
|
2888
2929
|
lines2.push("", theme.fg("error", `Delete @${confirmingDeleteAgent}? This removes its project file.`), dialogKeys(theme, [
|
|
2889
|
-
["
|
|
2890
|
-
["
|
|
2930
|
+
["Enter", "confirm"],
|
|
2931
|
+
["Esc", "cancel"]
|
|
2891
2932
|
]));
|
|
2892
2933
|
} else if (saving) {
|
|
2893
2934
|
lines2.push("", theme.fg("dim", "Saving…"));
|
|
2894
2935
|
}
|
|
2895
2936
|
return box(lines2);
|
|
2896
2937
|
}
|
|
2897
|
-
if (tab === "settings") {
|
|
2898
|
-
const maxSubagentsInherited = maxSubagentsSettings.project === undefined;
|
|
2899
|
-
const rows = [
|
|
2900
|
-
{
|
|
2901
|
-
label: "maxSubagents",
|
|
2902
|
-
value: String(selectedMaxSubagents()),
|
|
2903
|
-
inherited: maxSubagentsInherited,
|
|
2904
|
-
unavailable: false
|
|
2905
|
-
}
|
|
2906
|
-
];
|
|
2907
|
-
const lines2 = [tabs, ""];
|
|
2908
|
-
if (settingEditor) {
|
|
2909
|
-
const current = String(selectedMaxSubagents());
|
|
2910
|
-
lines2.push(theme.fg("dim", ` Project maxSubagents · current ${current}`), "", ` ${theme.fg("accent", "›")} [ ${settingEditor.value}${theme.fg("accent", "█")} ]`, "", theme.fg("dim", " empty inherit · enter submit · esc cancel"));
|
|
2911
|
-
if (saving)
|
|
2912
|
-
lines2.push("", theme.fg("dim", "Saving…"));
|
|
2913
|
-
return box(lines2);
|
|
2914
|
-
}
|
|
2915
|
-
for (const [index, row] of rows.entries()) {
|
|
2916
|
-
const selected = index === selectedSetting;
|
|
2917
|
-
const cursor = selected ? theme.fg("accent", "›") : " ";
|
|
2918
|
-
const text = `[ ${row.value} ]`;
|
|
2919
|
-
const value = row.inherited ? theme.fg("dim", text) : selected ? theme.fg("accent", text) : theme.fg(row.unavailable ? "muted" : "text", text);
|
|
2920
|
-
const label = theme.fg(row.unavailable ? "muted" : "text", row.label);
|
|
2921
|
-
lines2.push(`${cursor} ${value} ${label}`);
|
|
2922
|
-
}
|
|
2923
|
-
if (saving)
|
|
2924
|
-
lines2.push("", theme.fg("dim", "Saving…"));
|
|
2925
|
-
return box(lines2);
|
|
2926
|
-
}
|
|
2927
2938
|
if (tab === "help") {
|
|
2928
2939
|
const shortcutWidth = Math.max(visibleWidth2("Shortcut"), ...AGENTS_HELP_ROWS.map(([shortcut]) => visibleWidth2(shortcut)));
|
|
2929
2940
|
const descriptionWidth = Math.max(1, contentWidth - shortcutWidth - 4);
|
|
@@ -2960,7 +2971,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
2960
2971
|
const label = count === 1 ? `Delete ${pendingTaskAction.taskIds[0]?.slice(0, 8) ?? "task"}?` : `Delete ${count} selected task sessions?`;
|
|
2961
2972
|
listLines.push(`${theme.fg("warning", label)} ${theme.fg("dim", "enter confirm · esc cancel")}`);
|
|
2962
2973
|
} else {
|
|
2963
|
-
listLines.push(theme.fg("dim", "↑↓ move · space select · enter inspect ·
|
|
2974
|
+
listLines.push(theme.fg("dim", "↑↓ move · space select · enter inspect · x delete · esc close"));
|
|
2964
2975
|
}
|
|
2965
2976
|
return box([tabs, "", ...listLines]);
|
|
2966
2977
|
}
|
|
@@ -2996,12 +3007,16 @@ ${this.primaryAgent.prompt}` };
|
|
|
2996
3007
|
},
|
|
2997
3008
|
handleInput: (data) => {
|
|
2998
3009
|
if (confirmingDeleteAgent !== undefined) {
|
|
2999
|
-
if (matchesKey(data, "
|
|
3010
|
+
if (matchesKey(data, "ctrl+c")) {
|
|
3011
|
+
done();
|
|
3012
|
+
return;
|
|
3013
|
+
}
|
|
3014
|
+
if (matchesKey(data, "escape")) {
|
|
3000
3015
|
confirmingDeleteAgent = undefined;
|
|
3001
3016
|
tui.requestRender();
|
|
3002
3017
|
return;
|
|
3003
3018
|
}
|
|
3004
|
-
if (data
|
|
3019
|
+
if (matchesKey(data, "return")) {
|
|
3005
3020
|
const name = confirmingDeleteAgent;
|
|
3006
3021
|
const agent = agents.find((candidate) => candidate.name === name);
|
|
3007
3022
|
confirmingDeleteAgent = undefined;
|
|
@@ -3024,10 +3039,8 @@ ${this.primaryAgent.prompt}` };
|
|
|
3024
3039
|
return;
|
|
3025
3040
|
}
|
|
3026
3041
|
if (matchesKey(data, "tab")) {
|
|
3027
|
-
if (settingEditor !== undefined)
|
|
3028
|
-
return;
|
|
3029
3042
|
const selectedName = tab === "primary" || tab === "subagent" ? agentsForTab()[selectedAgent]?.name : undefined;
|
|
3030
|
-
const tabs = ["primary", "subagent", "tasks", "log", "
|
|
3043
|
+
const tabs = ["primary", "subagent", "tasks", "log", "help"];
|
|
3031
3044
|
tab = tabs[(tabs.indexOf(tab) + 1) % tabs.length] ?? "primary";
|
|
3032
3045
|
if (tab === "primary" || tab === "subagent") {
|
|
3033
3046
|
const listedAgents = agentsForTab();
|
|
@@ -3123,98 +3136,6 @@ ${this.primaryAgent.prompt}` };
|
|
|
3123
3136
|
tui.requestRender();
|
|
3124
3137
|
return;
|
|
3125
3138
|
}
|
|
3126
|
-
if (tab === "settings") {
|
|
3127
|
-
if (saving)
|
|
3128
|
-
return;
|
|
3129
|
-
if (matchesKey(data, "ctrl+c")) {
|
|
3130
|
-
done();
|
|
3131
|
-
return;
|
|
3132
|
-
}
|
|
3133
|
-
if (settingEditor) {
|
|
3134
|
-
if (matchesKey(data, "escape")) {
|
|
3135
|
-
settingEditor = undefined;
|
|
3136
|
-
tui.requestRender();
|
|
3137
|
-
return;
|
|
3138
|
-
}
|
|
3139
|
-
if (matchesKey(data, "backspace")) {
|
|
3140
|
-
settingEditor.value = settingEditor.value.slice(0, -1);
|
|
3141
|
-
tui.requestRender();
|
|
3142
|
-
return;
|
|
3143
|
-
}
|
|
3144
|
-
if (matchesKey(data, "return")) {
|
|
3145
|
-
const input = settingEditor.value.trim();
|
|
3146
|
-
let save;
|
|
3147
|
-
let notifyMessage;
|
|
3148
|
-
if (input === "") {
|
|
3149
|
-
if (!projectTrusted) {
|
|
3150
|
-
ctx.ui.notify("Project settings require a trusted project", "warning");
|
|
3151
|
-
return;
|
|
3152
|
-
}
|
|
3153
|
-
save = clearMaxSubagentsConfigForScope(ctx.cwd, "project");
|
|
3154
|
-
notifyMessage = "Project maxSubagents inherits Global";
|
|
3155
|
-
} else {
|
|
3156
|
-
if (!projectTrusted) {
|
|
3157
|
-
ctx.ui.notify("Project settings require a trusted project", "warning");
|
|
3158
|
-
return;
|
|
3159
|
-
}
|
|
3160
|
-
if (!/^\d+$/.test(input)) {
|
|
3161
|
-
ctx.ui.notify(`Enter a whole number between 0 and ${MAX_SUBAGENTS}`, "error");
|
|
3162
|
-
return;
|
|
3163
|
-
}
|
|
3164
|
-
const parsed = Number(input);
|
|
3165
|
-
if (!Number.isSafeInteger(parsed) || parsed < 0 || parsed > MAX_SUBAGENTS) {
|
|
3166
|
-
ctx.ui.notify(`Enter a whole number between 0 and ${MAX_SUBAGENTS}`, "error");
|
|
3167
|
-
return;
|
|
3168
|
-
}
|
|
3169
|
-
save = setMaxSubagentsConfigForScope(ctx.cwd, parsed, "project");
|
|
3170
|
-
notifyMessage = `Project maxSubagents = ${parsed}`;
|
|
3171
|
-
}
|
|
3172
|
-
saving = true;
|
|
3173
|
-
settingEditor = undefined;
|
|
3174
|
-
tui.requestRender();
|
|
3175
|
-
save.then(() => {
|
|
3176
|
-
reloadSettings();
|
|
3177
|
-
const loadedMaxSubagents = loadMaxSubagentsSettings(ctx.cwd, projectTrusted);
|
|
3178
|
-
this.setMaxSubagents(loadedMaxSubagents.project ?? loadedMaxSubagents.global);
|
|
3179
|
-
maxSubagentsSettings = loadedMaxSubagents;
|
|
3180
|
-
ctx.ui.notify(notifyMessage ?? "Setting updated", "info");
|
|
3181
|
-
}).catch((error) => ctx.ui.notify(`Could not save setting: ${formatError(error)}`, "error")).finally(() => {
|
|
3182
|
-
saving = false;
|
|
3183
|
-
tui.requestRender();
|
|
3184
|
-
});
|
|
3185
|
-
return;
|
|
3186
|
-
}
|
|
3187
|
-
const kittyChar = decodeKittyPrintable(data);
|
|
3188
|
-
const printable = kittyChar ?? (data.length === 1 && !isControlChar(data) ? data : undefined);
|
|
3189
|
-
if (printable === undefined)
|
|
3190
|
-
return;
|
|
3191
|
-
if (!/^\d$/.test(printable))
|
|
3192
|
-
return;
|
|
3193
|
-
settingEditor.value += printable;
|
|
3194
|
-
tui.requestRender();
|
|
3195
|
-
return;
|
|
3196
|
-
}
|
|
3197
|
-
if (matchesKey(data, "escape")) {
|
|
3198
|
-
done();
|
|
3199
|
-
return;
|
|
3200
|
-
}
|
|
3201
|
-
if (matchesKey(data, "up"))
|
|
3202
|
-
selectedSetting = 0;
|
|
3203
|
-
else if (matchesKey(data, "down"))
|
|
3204
|
-
selectedSetting = 0;
|
|
3205
|
-
else if (matchesKey(data, "return")) {
|
|
3206
|
-
if (!projectTrusted) {
|
|
3207
|
-
ctx.ui.notify("Project settings require a trusted project", "warning");
|
|
3208
|
-
return;
|
|
3209
|
-
}
|
|
3210
|
-
settingEditor = {
|
|
3211
|
-
value: maxSubagentsSettings.project !== undefined ? String(maxSubagentsSettings.project) : ""
|
|
3212
|
-
};
|
|
3213
|
-
} else
|
|
3214
|
-
return;
|
|
3215
|
-
tui.requestRender();
|
|
3216
|
-
return;
|
|
3217
|
-
}
|
|
3218
3139
|
if (tab === "help") {
|
|
3219
3140
|
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c"))
|
|
3220
3141
|
done();
|
|
@@ -3235,7 +3156,11 @@ ${this.primaryAgent.prompt}` };
|
|
|
3235
3156
|
tui.requestRender();
|
|
3236
3157
|
return;
|
|
3237
3158
|
}
|
|
3238
|
-
if (matchesKey(data, "
|
|
3159
|
+
if (matchesKey(data, "ctrl+c")) {
|
|
3160
|
+
done();
|
|
3161
|
+
return;
|
|
3162
|
+
}
|
|
3163
|
+
if (matchesKey(data, "escape")) {
|
|
3239
3164
|
pendingTaskAction = undefined;
|
|
3240
3165
|
tui.requestRender();
|
|
3241
3166
|
}
|
|
@@ -3253,7 +3178,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
3253
3178
|
return;
|
|
3254
3179
|
if (!selectedTaskIds.delete(task2.id))
|
|
3255
3180
|
selectedTaskIds.add(task2.id);
|
|
3256
|
-
} else if (
|
|
3181
|
+
} else if (data.toLowerCase() === "x") {
|
|
3257
3182
|
const targets = taskTargets();
|
|
3258
3183
|
if (targets.length === 0)
|
|
3259
3184
|
return;
|
|
@@ -3315,6 +3240,262 @@ ${this.primaryAgent.prompt}` };
|
|
|
3315
3240
|
invalidate() {}
|
|
3316
3241
|
}), { overlay: true, overlayOptions: { anchor: "center", width: 96, margin: 2 } });
|
|
3317
3242
|
}
|
|
3243
|
+
async openSettings(ctx) {
|
|
3244
|
+
if (ctx.mode !== "tui") {
|
|
3245
|
+
ctx.ui.notify("Settings are available in TUI mode", "warning");
|
|
3246
|
+
return;
|
|
3247
|
+
}
|
|
3248
|
+
const projectTrusted = isProjectTrusted(ctx);
|
|
3249
|
+
let tab = "sandbox";
|
|
3250
|
+
let overview = this.integration.sandboxCallbacks?.overview?.(ctx.cwd, projectTrusted);
|
|
3251
|
+
let maxSubagentsSettings = loadMaxSubagentsSettings(ctx.cwd, projectTrusted);
|
|
3252
|
+
let policySettings = loadToolFilesystemPolicySettings(ctx.cwd, projectTrusted);
|
|
3253
|
+
let selectedSetting = 0;
|
|
3254
|
+
let settingEditor;
|
|
3255
|
+
let saving = false;
|
|
3256
|
+
await ctx.ui.custom((tui, theme, _keybindings, done) => ({
|
|
3257
|
+
render: (width) => {
|
|
3258
|
+
const contentWidth = Math.max(1, width - 4);
|
|
3259
|
+
const box = (lines2) => [
|
|
3260
|
+
boxTop(theme, width, "Settings"),
|
|
3261
|
+
...lines2.map((line) => boxRow(theme, width, line)),
|
|
3262
|
+
boxBottom(theme, width)
|
|
3263
|
+
];
|
|
3264
|
+
const tabs = dialogTabs(theme, ["Sandbox", "Agents"], tab === "sandbox" ? "Sandbox" : "Agents");
|
|
3265
|
+
const listValue = (values) => {
|
|
3266
|
+
const value = values.join(", ") || "none";
|
|
3267
|
+
return truncateToWidth2(value, Math.max(10, contentWidth - 20));
|
|
3268
|
+
};
|
|
3269
|
+
if (tab === "sandbox") {
|
|
3270
|
+
const lines2 = [tabs, ""];
|
|
3271
|
+
if (!overview) {
|
|
3272
|
+
lines2.push(theme.fg("muted", " Sandbox settings are unavailable."));
|
|
3273
|
+
lines2.push("", dialogKeys(theme, [["Esc", "close"]]));
|
|
3274
|
+
return box(lines2);
|
|
3275
|
+
}
|
|
3276
|
+
const status = overview.noSandboxFlag ? { color: "warning", label: "Disabled by --no-sandbox" } : !overview.enabled ? { color: "warning", label: "Disabled by configuration" } : !overview.running ? { color: "warning", label: "Inactive" } : { color: "success", label: "Active" };
|
|
3277
|
+
const networkMode = overview.networkMode;
|
|
3278
|
+
const shellReadMode = overview.windowsMode !== undefined ? "Policy (required)" : overview.shellReadMode === "host" ? "Host-aligned" : "Policy";
|
|
3279
|
+
const changePath = overview.changeScope === "project" ? overview.paths.project : overview.paths.global;
|
|
3280
|
+
const item = (label, value) => ` ${theme.fg("dim", label.padEnd(16))} ${value}`;
|
|
3281
|
+
lines2.push(`${theme.fg(status.color, "●")} ${theme.fg("text", status.label)}`, item("Configured", overview.enabled ? theme.fg("text", "Enabled") : theme.fg("warning", "Disabled")), item("Runtime", overview.running ? theme.fg("success", "Running") : theme.fg("dim", "Not running")), "", theme.fg("accent", "Protection"), item("Network", networkMode), item("Shell reads", shellReadMode), item("Read rules", `${overview.config.filesystem.denyRead.length + overview.config.filesystem.allowRead.length} · ${overview.readRuleScope}`), item("Write rules", String(overview.config.filesystem.allowWrite.length + overview.config.filesystem.denyWrite.length)), item("Session grants", String(overview.sessionDomains.length + overview.sessionReadPaths.length + overview.sessionWritePaths.length)), "", theme.fg("accent", "Configuration"), item("Change scope", overview.changeScope === "project" ? "Project" : "Global"), item("Change file", truncateToWidth2(changePath, Math.max(10, contentWidth - 20))), item("Project file", truncateToWidth2(overview.paths.project, Math.max(10, contentWidth - 20))), item("Global file", truncateToWidth2(overview.paths.global, Math.max(10, contentWidth - 20))), item("Landstrip binary", truncateToWidth2(overview.paths.binary, Math.max(10, contentWidth - 20))));
|
|
3282
|
+
if (overview.windowsMode !== undefined)
|
|
3283
|
+
lines2.push(item("Windows mode", overview.windowsMode));
|
|
3284
|
+
lines2.push("", theme.fg("accent", "Network"), item("Allowed domains", listValue(overview.config.network.allowedDomains)), item("Denied domains", listValue(overview.config.network.deniedDomains)), item("Unix sockets", overview.config.network.allowAllUnixSockets ? "all" : listValue(overview.config.network.allowUnixSockets)), "", theme.fg("accent", "Filesystem"), item("Shell reads", shellReadMode), item("Read rules for", overview.readRuleScope), item("Denied reads", listValue(overview.config.filesystem.denyRead)), item("Allowed reads", listValue(overview.config.filesystem.allowRead)), item("Allowed writes", listValue(overview.config.filesystem.allowWrite)), item("Denied writes", listValue(overview.config.filesystem.denyWrite)), "", theme.fg("accent", "Session grants"), item("Domains", listValue(overview.sessionDomains)), item("Read paths", listValue(overview.sessionReadPaths)), item("Write paths", listValue(overview.sessionWritePaths)));
|
|
3285
|
+
if (saving)
|
|
3286
|
+
lines2.push("", theme.fg("dim", "Saving…"));
|
|
3287
|
+
lines2.push("", dialogKeys(theme, [
|
|
3288
|
+
["Tab", "next tab"],
|
|
3289
|
+
["Enter", `${overview.enabled ? "disable" : "enable"} in ${overview.changeScope}`],
|
|
3290
|
+
["Esc", "close"]
|
|
3291
|
+
]));
|
|
3292
|
+
return box(lines2);
|
|
3293
|
+
}
|
|
3294
|
+
const rows = [
|
|
3295
|
+
{
|
|
3296
|
+
kind: "maxSubagents",
|
|
3297
|
+
label: "maxSubagents",
|
|
3298
|
+
value: String(maxSubagentsSettings.project ?? maxSubagentsSettings.global),
|
|
3299
|
+
inherited: maxSubagentsSettings.project === undefined
|
|
3300
|
+
},
|
|
3301
|
+
{
|
|
3302
|
+
kind: "toolFilesystemPolicy",
|
|
3303
|
+
label: "toolFilesystemPolicy",
|
|
3304
|
+
value: policySettings.project ?? policySettings.global,
|
|
3305
|
+
inherited: policySettings.project === undefined
|
|
3306
|
+
}
|
|
3307
|
+
];
|
|
3308
|
+
const lines = [tabs, ""];
|
|
3309
|
+
if (settingEditor) {
|
|
3310
|
+
const kindLabel = settingEditor.kind === "maxSubagents" ? "Project maxSubagents" : "Project toolFilesystemPolicy";
|
|
3311
|
+
const current = settingEditor.kind === "maxSubagents" ? String(maxSubagentsSettings.project ?? maxSubagentsSettings.global) : policySettings.project ?? policySettings.global;
|
|
3312
|
+
const hint = settingEditor.kind === "maxSubagents" ? "empty inherit · enter submit · esc cancel" : "h host · s sandbox · backspace inherit · enter submit · esc cancel";
|
|
3313
|
+
lines.push(theme.fg("dim", ` ${kindLabel} · current ${current}`), "", ` ${theme.fg("accent", "›")} [ ${settingEditor.value}${theme.fg("accent", "█")} ]`, "", theme.fg("dim", ` ${hint}`));
|
|
3314
|
+
if (saving)
|
|
3315
|
+
lines.push("", theme.fg("dim", "Saving…"));
|
|
3316
|
+
return box(lines);
|
|
3317
|
+
}
|
|
3318
|
+
for (const [index, row] of rows.entries()) {
|
|
3319
|
+
const selected = index === selectedSetting;
|
|
3320
|
+
const cursor = selected ? theme.fg("accent", "›") : " ";
|
|
3321
|
+
const value = row.inherited ? theme.fg("dim", `[ ${row.value} ]`) : selected ? theme.fg("accent", `[ ${row.value} ]`) : theme.fg("text", `[ ${row.value} ]`);
|
|
3322
|
+
lines.push(`${cursor} ${value} ${theme.fg("text", row.label)}`);
|
|
3323
|
+
}
|
|
3324
|
+
lines.push("", theme.fg("dim", " enter edit · ↑↓ select · esc close"));
|
|
3325
|
+
if (saving)
|
|
3326
|
+
lines.push("", theme.fg("dim", "Saving…"));
|
|
3327
|
+
return box(lines);
|
|
3328
|
+
},
|
|
3329
|
+
handleInput: (data) => {
|
|
3330
|
+
if (saving)
|
|
3331
|
+
return;
|
|
3332
|
+
if (matchesKey(data, "ctrl+c")) {
|
|
3333
|
+
done();
|
|
3334
|
+
return;
|
|
3335
|
+
}
|
|
3336
|
+
if (matchesKey(data, "escape")) {
|
|
3337
|
+
if (settingEditor) {
|
|
3338
|
+
settingEditor = undefined;
|
|
3339
|
+
tui.requestRender();
|
|
3340
|
+
return;
|
|
3341
|
+
}
|
|
3342
|
+
done();
|
|
3343
|
+
return;
|
|
3344
|
+
}
|
|
3345
|
+
if (settingEditor) {
|
|
3346
|
+
if (matchesKey(data, "backspace")) {
|
|
3347
|
+
settingEditor.value = settingEditor.kind === "maxSubagents" ? settingEditor.value.slice(0, -1) : "";
|
|
3348
|
+
tui.requestRender();
|
|
3349
|
+
return;
|
|
3350
|
+
}
|
|
3351
|
+
if (matchesKey(data, "return")) {
|
|
3352
|
+
const input = settingEditor.value.trim();
|
|
3353
|
+
if (!projectTrusted) {
|
|
3354
|
+
ctx.ui.notify("Project settings require a trusted project", "warning");
|
|
3355
|
+
return;
|
|
3356
|
+
}
|
|
3357
|
+
let save;
|
|
3358
|
+
let notifyMessage;
|
|
3359
|
+
if (settingEditor.kind === "maxSubagents") {
|
|
3360
|
+
if (input === "") {
|
|
3361
|
+
save = clearMaxSubagentsConfigForScope(ctx.cwd, "project");
|
|
3362
|
+
notifyMessage = "Project maxSubagents inherits Global";
|
|
3363
|
+
} else {
|
|
3364
|
+
if (!/^\d+$/.test(input)) {
|
|
3365
|
+
ctx.ui.notify(`Enter a whole number between 0 and ${MAX_SUBAGENTS}`, "error");
|
|
3366
|
+
return;
|
|
3367
|
+
}
|
|
3368
|
+
const parsed = Number(input);
|
|
3369
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0 || parsed > MAX_SUBAGENTS) {
|
|
3370
|
+
ctx.ui.notify(`Enter a whole number between 0 and ${MAX_SUBAGENTS}`, "error");
|
|
3371
|
+
return;
|
|
3372
|
+
}
|
|
3373
|
+
save = setMaxSubagentsConfigForScope(ctx.cwd, parsed, "project");
|
|
3374
|
+
notifyMessage = `Project maxSubagents = ${parsed}`;
|
|
3375
|
+
}
|
|
3376
|
+
} else {
|
|
3377
|
+
if (input === "") {
|
|
3378
|
+
save = clearToolFilesystemPolicyConfigForScope(ctx.cwd, "project");
|
|
3379
|
+
notifyMessage = "Project toolFilesystemPolicy inherits Global";
|
|
3380
|
+
} else {
|
|
3381
|
+
if (input !== "host" && input !== "sandbox") {
|
|
3382
|
+
ctx.ui.notify("Enter host or sandbox", "error");
|
|
3383
|
+
return;
|
|
3384
|
+
}
|
|
3385
|
+
const policy = input;
|
|
3386
|
+
save = setToolFilesystemPolicyConfigForScope(ctx.cwd, policy, "project");
|
|
3387
|
+
notifyMessage = `Project toolFilesystemPolicy = ${policy}`;
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
const kind = settingEditor.kind;
|
|
3391
|
+
saving = true;
|
|
3392
|
+
settingEditor = undefined;
|
|
3393
|
+
tui.requestRender();
|
|
3394
|
+
save.then(() => {
|
|
3395
|
+
if (kind === "maxSubagents") {
|
|
3396
|
+
maxSubagentsSettings = loadMaxSubagentsSettings(ctx.cwd, projectTrusted);
|
|
3397
|
+
this.setMaxSubagents(maxSubagentsSettings.project ?? maxSubagentsSettings.global);
|
|
3398
|
+
} else {
|
|
3399
|
+
policySettings = loadToolFilesystemPolicySettings(ctx.cwd, projectTrusted);
|
|
3400
|
+
this.toolFilesystemPolicy = policySettings.project ?? policySettings.global;
|
|
3401
|
+
}
|
|
3402
|
+
ctx.ui.notify(notifyMessage, "info");
|
|
3403
|
+
}).catch((error) => ctx.ui.notify(`Could not save setting: ${formatError(error)}`, "error")).finally(() => {
|
|
3404
|
+
saving = false;
|
|
3405
|
+
tui.requestRender();
|
|
3406
|
+
});
|
|
3407
|
+
return;
|
|
3408
|
+
}
|
|
3409
|
+
const kittyChar = decodeKittyPrintable(data);
|
|
3410
|
+
const printable = kittyChar ?? (data.length === 1 && !isControlChar(data) ? data : undefined);
|
|
3411
|
+
if (printable === undefined)
|
|
3412
|
+
return;
|
|
3413
|
+
if (settingEditor.kind === "maxSubagents") {
|
|
3414
|
+
if (!/^\d$/.test(printable))
|
|
3415
|
+
return;
|
|
3416
|
+
settingEditor.value += printable;
|
|
3417
|
+
} else {
|
|
3418
|
+
const lower = printable.toLowerCase();
|
|
3419
|
+
if (lower !== "h" && lower !== "s")
|
|
3420
|
+
return;
|
|
3421
|
+
settingEditor.value = lower === "h" ? "host" : "sandbox";
|
|
3422
|
+
}
|
|
3423
|
+
tui.requestRender();
|
|
3424
|
+
return;
|
|
3425
|
+
}
|
|
3426
|
+
if (matchesKey(data, "tab")) {
|
|
3427
|
+
tab = tab === "sandbox" ? "agents" : "sandbox";
|
|
3428
|
+
tui.requestRender();
|
|
3429
|
+
return;
|
|
3430
|
+
}
|
|
3431
|
+
if (tab === "sandbox") {
|
|
3432
|
+
if (!matchesKey(data, "return"))
|
|
3433
|
+
return;
|
|
3434
|
+
const callbacks = this.integration.sandboxCallbacks;
|
|
3435
|
+
const current = overview;
|
|
3436
|
+
if (!callbacks || !current) {
|
|
3437
|
+
ctx.ui.notify("Sandbox settings are unavailable", "warning");
|
|
3438
|
+
return;
|
|
3439
|
+
}
|
|
3440
|
+
saving = true;
|
|
3441
|
+
tui.requestRender();
|
|
3442
|
+
(async () => {
|
|
3443
|
+
try {
|
|
3444
|
+
const enabled = !current.enabled;
|
|
3445
|
+
if (!enabled) {
|
|
3446
|
+
const confirmed = await ctx.ui.confirm("Disable sandbox?", `Commands will run without OS isolation.
|
|
3447
|
+
|
|
3448
|
+
Change: ${current.changeScope} configuration
|
|
3449
|
+
${current.changeScope === "project" ? current.paths.project : current.paths.global}`);
|
|
3450
|
+
if (!confirmed)
|
|
3451
|
+
return;
|
|
3452
|
+
}
|
|
3453
|
+
await callbacks.setEnabled(ctx, enabled, current.changeScope);
|
|
3454
|
+
overview = callbacks.overview(ctx.cwd, projectTrusted);
|
|
3455
|
+
if (enabled && overview.noSandboxFlag) {
|
|
3456
|
+
ctx.ui.notify("Sandbox remains disabled via --no-sandbox", "warning");
|
|
3457
|
+
} else {
|
|
3458
|
+
ctx.ui.notify(`Sandbox ${enabled ? "enabled" : "disabled"} in ${current.changeScope} config`, "info");
|
|
3459
|
+
}
|
|
3460
|
+
} catch (error) {
|
|
3461
|
+
ctx.ui.notify(`Could not update config: ${formatError(error)}`, "error");
|
|
3462
|
+
} finally {
|
|
3463
|
+
saving = false;
|
|
3464
|
+
tui.requestRender();
|
|
3465
|
+
}
|
|
3466
|
+
})();
|
|
3467
|
+
return;
|
|
3468
|
+
}
|
|
3469
|
+
if (matchesKey(data, "up"))
|
|
3470
|
+
selectedSetting = Math.max(0, selectedSetting - 1);
|
|
3471
|
+
else if (matchesKey(data, "down"))
|
|
3472
|
+
selectedSetting = Math.min(1, selectedSetting + 1);
|
|
3473
|
+
else if (matchesKey(data, "return")) {
|
|
3474
|
+
if (!projectTrusted) {
|
|
3475
|
+
ctx.ui.notify("Project settings require a trusted project", "warning");
|
|
3476
|
+
return;
|
|
3477
|
+
}
|
|
3478
|
+
if (selectedSetting === 0) {
|
|
3479
|
+
settingEditor = {
|
|
3480
|
+
kind: "maxSubagents",
|
|
3481
|
+
value: maxSubagentsSettings.project !== undefined ? String(maxSubagentsSettings.project) : ""
|
|
3482
|
+
};
|
|
3483
|
+
} else {
|
|
3484
|
+
settingEditor = {
|
|
3485
|
+
kind: "toolFilesystemPolicy",
|
|
3486
|
+
value: policySettings.project ?? policySettings.global
|
|
3487
|
+
};
|
|
3488
|
+
}
|
|
3489
|
+
} else
|
|
3490
|
+
return;
|
|
3491
|
+
tui.requestRender();
|
|
3492
|
+
},
|
|
3493
|
+
invalidate() {}
|
|
3494
|
+
}), {
|
|
3495
|
+
overlay: true,
|
|
3496
|
+
overlayOptions: { anchor: "bottom-center", width: "100%", maxHeight: "60%" }
|
|
3497
|
+
});
|
|
3498
|
+
}
|
|
3318
3499
|
async selectPrimaryAgent(name, ctx) {
|
|
3319
3500
|
const catalog = this.getAgentCatalog(ctx);
|
|
3320
3501
|
for (const diagnostic of catalog.diagnostics)
|
|
@@ -4582,11 +4763,6 @@ async function writeSandboxConfigEnabled(cwd, enabled, scope) {
|
|
|
4582
4763
|
writeConfigFile(path, config);
|
|
4583
4764
|
});
|
|
4584
4765
|
}
|
|
4585
|
-
async function setSandboxConfigEnabled(cwd, enabled, includeProject = true) {
|
|
4586
|
-
const { scope } = getSandboxConfigWriteTarget(cwd, includeProject);
|
|
4587
|
-
await writeSandboxConfigEnabled(cwd, enabled, scope);
|
|
4588
|
-
return scope;
|
|
4589
|
-
}
|
|
4590
4766
|
function loadSandboxEnabledSettings(cwd, includeProject) {
|
|
4591
4767
|
const { projectPath } = getConfigPaths(cwd);
|
|
4592
4768
|
const global = loadSandboxConfig(cwd, false).enabled;
|
|
@@ -4778,14 +4954,6 @@ function setTuiStatus(ctx, key, value) {
|
|
|
4778
4954
|
return;
|
|
4779
4955
|
ctx.ui.setStatus(key, value);
|
|
4780
4956
|
}
|
|
4781
|
-
function themeColors(theme) {
|
|
4782
|
-
return {
|
|
4783
|
-
dim: (value) => theme.fg("dim", value),
|
|
4784
|
-
muted: (value) => theme.fg("muted", value),
|
|
4785
|
-
accent: (value) => theme.fg("accent", value),
|
|
4786
|
-
text: (value) => theme.fg("text", value)
|
|
4787
|
-
};
|
|
4788
|
-
}
|
|
4789
4957
|
function patchProcessGroupKill(child) {
|
|
4790
4958
|
const kill = child.kill.bind(child);
|
|
4791
4959
|
child.kill = (signal) => {
|
|
@@ -6437,110 +6605,6 @@ ${message}
|
|
|
6437
6605
|
unpublishRuntime?.();
|
|
6438
6606
|
unpublishRuntime = undefined;
|
|
6439
6607
|
});
|
|
6440
|
-
maybePi.registerCommand?.("sandbox", {
|
|
6441
|
-
description: "Inspect and toggle the sandbox",
|
|
6442
|
-
handler: async (_args, ctx) => {
|
|
6443
|
-
if (!ctx.hasUI)
|
|
6444
|
-
return;
|
|
6445
|
-
const trustContext = ctx;
|
|
6446
|
-
const projectTrusted = trustContext.isProjectTrusted?.() ?? projectConfigTrusted;
|
|
6447
|
-
const config = loadSandboxConfig(ctx.cwd, projectTrusted);
|
|
6448
|
-
const { globalPath, projectPath } = getConfigPaths(ctx.cwd);
|
|
6449
|
-
const windowsImplementation = process.platform === "win32" ? activeWindowsImplementation() : undefined;
|
|
6450
|
-
const target = getSandboxConfigWriteTarget(ctx.cwd, projectTrusted);
|
|
6451
|
-
let tab = "Overview";
|
|
6452
|
-
const shouldToggle = await ctx.ui.custom((tui, theme, _kb, done) => {
|
|
6453
|
-
const { muted, accent, text } = themeColors(theme);
|
|
6454
|
-
const sandboxStatus = () => {
|
|
6455
|
-
if (getNoSandboxFlag())
|
|
6456
|
-
return { color: "warning", label: "Disabled by --no-sandbox" };
|
|
6457
|
-
if (!config.enabled)
|
|
6458
|
-
return { color: "warning", label: "Disabled by configuration" };
|
|
6459
|
-
if (!sandboxEnabled || !sandboxReady)
|
|
6460
|
-
return { color: "warning", label: "Inactive" };
|
|
6461
|
-
return { color: "success", label: "Active" };
|
|
6462
|
-
};
|
|
6463
|
-
const networkMode = config.network.allowNetwork ? "Unrestricted" : windowsImplementation === "appContainer" && !config.windows.allowLoopback ? "Blocked" : "Proxied";
|
|
6464
|
-
const shellReadMode = process.platform === "win32" ? "Policy (required)" : config.shell.readAccess === "host" ? "Host-aligned" : "Policy";
|
|
6465
|
-
const readRuleScope = enforcesShellReadPolicy(config.shell.readAccess) ? "Workers and shell" : "Workers only";
|
|
6466
|
-
return {
|
|
6467
|
-
render(width) {
|
|
6468
|
-
const innerWidth = Math.max(1, width - 4);
|
|
6469
|
-
const status = sandboxStatus();
|
|
6470
|
-
const tabs = dialogTabs(theme, ["Overview", "Policy"], tab);
|
|
6471
|
-
const listValue = (values) => {
|
|
6472
|
-
const value = values.join(", ") || "none";
|
|
6473
|
-
return text(truncateToWidth3(value, Math.max(10, innerWidth - 20)));
|
|
6474
|
-
};
|
|
6475
|
-
const item = (label, value) => ` ${muted(label.padEnd(16))} ${value}`;
|
|
6476
|
-
const footer = dialogKeys(theme, [
|
|
6477
|
-
["Tab", "next tab"],
|
|
6478
|
-
["Enter", `${config.enabled ? "disable" : "enable"} in ${target.scope}`],
|
|
6479
|
-
["Esc", "close"]
|
|
6480
|
-
]);
|
|
6481
|
-
const content = [tabs, ""];
|
|
6482
|
-
if (tab === "Overview") {
|
|
6483
|
-
content.push(`${theme.fg(status.color, "●")} ${text(status.label)}`, item("Configured", config.enabled ? text("Enabled") : theme.fg("warning", "Disabled")), item("Runtime", sandboxEnabled && sandboxReady ? theme.fg("success", "Running") : muted("Not running")), "", accent("Protection"), item("Network", text(networkMode)), item("Shell reads", text(shellReadMode)), item("Read rules", text(`${config.filesystem.denyRead.length + config.filesystem.allowRead.length} · ${readRuleScope}`)), item("Write rules", text(String(config.filesystem.allowWrite.length + config.filesystem.denyWrite.length))), item("Session grants", text(String(sessionAllowedDomains.length + sessionAllowedReadPaths.length + sessionAllowedWritePaths.length))), "", accent("Configuration"), item("Change scope", text(target.scope === "project" ? "Project" : "Global")), item("Change file", text(truncateToWidth3(target.path, Math.max(10, innerWidth - 20)))), item("Project file", text(truncateToWidth3(projectPath, Math.max(10, innerWidth - 20)))), item("Global file", text(truncateToWidth3(globalPath, Math.max(10, innerWidth - 20)))), item("Landstrip binary", text(truncateToWidth3(binaryPath(), Math.max(10, innerWidth - 20)))));
|
|
6484
|
-
if (windowsImplementation !== undefined) {
|
|
6485
|
-
content.push(item("Windows mode", text(windowsImplementation === "restrictedUser" ? "Restricted user" : `AppContainer (${config.windows.appContainerMode})`)));
|
|
6486
|
-
}
|
|
6487
|
-
} else {
|
|
6488
|
-
content.push(accent(`Network · ${networkMode}`), item("Allowed domains", listValue(config.network.allowedDomains)), item("Denied domains", listValue(config.network.deniedDomains)), item("Unix sockets", config.network.allowAllUnixSockets ? text("all") : listValue(config.network.allowUnixSockets)), "", accent("Filesystem"), item("Shell reads", text(shellReadMode)), item("Read rules for", text(readRuleScope)), item("Denied reads", listValue(config.filesystem.denyRead)), item("Allowed reads", listValue(config.filesystem.allowRead)), item("Allowed writes", listValue(config.filesystem.allowWrite)), item("Denied writes", listValue(config.filesystem.denyWrite)), "", accent("Session grants"), item("Domains", listValue(sessionAllowedDomains)), item("Read paths", listValue(sessionAllowedReadPaths)), item("Write paths", listValue(sessionAllowedWritePaths)));
|
|
6489
|
-
}
|
|
6490
|
-
content.push("", footer);
|
|
6491
|
-
return [
|
|
6492
|
-
boxTop(theme, width, "Sandbox"),
|
|
6493
|
-
...content.map((line) => boxRow(theme, width, line)),
|
|
6494
|
-
boxBottom(theme, width)
|
|
6495
|
-
];
|
|
6496
|
-
},
|
|
6497
|
-
handleInput(data) {
|
|
6498
|
-
if (matchesKey2(data, "tab")) {
|
|
6499
|
-
tab = tab === "Overview" ? "Policy" : "Overview";
|
|
6500
|
-
tui.requestRender();
|
|
6501
|
-
return;
|
|
6502
|
-
}
|
|
6503
|
-
if (matchesKey2(data, "return")) {
|
|
6504
|
-
done(true);
|
|
6505
|
-
return;
|
|
6506
|
-
}
|
|
6507
|
-
if (matchesKey2(data, "escape") || matchesKey2(data, "ctrl+c"))
|
|
6508
|
-
done(false);
|
|
6509
|
-
},
|
|
6510
|
-
invalidate() {}
|
|
6511
|
-
};
|
|
6512
|
-
}, {
|
|
6513
|
-
overlay: true,
|
|
6514
|
-
overlayOptions: { anchor: "center", width: 78, margin: 2 }
|
|
6515
|
-
});
|
|
6516
|
-
if (!shouldToggle)
|
|
6517
|
-
return;
|
|
6518
|
-
const enabled = !config.enabled;
|
|
6519
|
-
if (!enabled) {
|
|
6520
|
-
const confirmed = await ctx.ui.confirm("Disable sandbox?", `Commands will run without OS isolation.
|
|
6521
|
-
|
|
6522
|
-
Change: ${target.scope} configuration
|
|
6523
|
-
${target.path}`);
|
|
6524
|
-
if (!confirmed)
|
|
6525
|
-
return;
|
|
6526
|
-
}
|
|
6527
|
-
try {
|
|
6528
|
-
const scope = await setSandboxConfigEnabled(ctx.cwd, enabled, projectTrusted);
|
|
6529
|
-
if (!enabled) {
|
|
6530
|
-
disableSandbox(ctx);
|
|
6531
|
-
} else if (!getNoSandboxFlag()) {
|
|
6532
|
-
enableSandbox(ctx);
|
|
6533
|
-
}
|
|
6534
|
-
if (enabled && getNoSandboxFlag()) {
|
|
6535
|
-
notify(ctx, "Sandbox remains disabled via --no-sandbox", "warning");
|
|
6536
|
-
} else {
|
|
6537
|
-
notify(ctx, `Sandbox ${enabled ? "enabled" : "disabled"} in ${scope} config`, "info");
|
|
6538
|
-
}
|
|
6539
|
-
} catch (error) {
|
|
6540
|
-
notify(ctx, `Could not update config: ${error}`, "error");
|
|
6541
|
-
}
|
|
6542
|
-
}
|
|
6543
|
-
});
|
|
6544
6608
|
}
|
|
6545
6609
|
async function applySandboxSetting(ctx, enabled, scope) {
|
|
6546
6610
|
await writeSandboxConfigEnabled(ctx.cwd, enabled, scope);
|
|
@@ -6551,10 +6615,31 @@ ${target.path}`);
|
|
|
6551
6615
|
enableSandbox(ctx);
|
|
6552
6616
|
}
|
|
6553
6617
|
}
|
|
6618
|
+
function sandboxOverview(cwd, includeProject) {
|
|
6619
|
+
const config = loadSandboxConfig(cwd, includeProject);
|
|
6620
|
+
const { globalPath, projectPath } = getConfigPaths(cwd);
|
|
6621
|
+
const windowsImplementation = process.platform === "win32" ? activeWindowsImplementation() : undefined;
|
|
6622
|
+
return {
|
|
6623
|
+
enabled: config.enabled,
|
|
6624
|
+
running: sandboxEnabled && sandboxReady,
|
|
6625
|
+
noSandboxFlag: getNoSandboxFlag(),
|
|
6626
|
+
networkMode: config.network.allowNetwork ? "unrestricted" : windowsImplementation === "appContainer" && !config.windows.allowLoopback ? "blocked" : "proxied",
|
|
6627
|
+
shellReadMode: process.platform === "win32" ? "policy" : config.shell.readAccess,
|
|
6628
|
+
readRuleScope: enforcesShellReadPolicy(config.shell.readAccess) ? "Workers and shell" : "Workers only",
|
|
6629
|
+
windowsMode: windowsImplementation === undefined ? undefined : windowsImplementation === "restrictedUser" ? "Restricted user" : `AppContainer (${config.windows.appContainerMode})`,
|
|
6630
|
+
changeScope: getSandboxConfigWriteTarget(cwd, includeProject).scope,
|
|
6631
|
+
paths: { global: globalPath, project: projectPath, binary: binaryPath() },
|
|
6632
|
+
config,
|
|
6633
|
+
sessionDomains: [...sessionAllowedDomains],
|
|
6634
|
+
sessionReadPaths: [...sessionAllowedReadPaths],
|
|
6635
|
+
sessionWritePaths: [...sessionAllowedWritePaths]
|
|
6636
|
+
};
|
|
6637
|
+
}
|
|
6554
6638
|
const sandboxCallbacks = {
|
|
6555
6639
|
load: loadSandboxEnabledSettings,
|
|
6556
6640
|
setEnabled: (ctx, enabled, scope) => applySandboxSetting(ctx, enabled, scope),
|
|
6557
|
-
clearProject: (ctx) => applySandboxSetting(ctx, undefined, "project")
|
|
6641
|
+
clearProject: (ctx) => applySandboxSetting(ctx, undefined, "project"),
|
|
6642
|
+
overview: sandboxOverview
|
|
6558
6643
|
};
|
|
6559
6644
|
const integration = {
|
|
6560
6645
|
version: LANDSTRIP_RUNTIME_VERSION,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-landstrip",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.35",
|
|
4
4
|
"description": "Sandboxed Bash and process-backed agents for Pi",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"landstrip",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"ci:test": "npm run test"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@landstrip/landstrip": "^0.18.
|
|
42
|
+
"@landstrip/landstrip": "^0.18.35",
|
|
43
43
|
"jsonc-parser": "^3.3.1",
|
|
44
44
|
"minimatch": "^10.2.5",
|
|
45
45
|
"yaml": "^2.9.0"
|
package/LICENSE
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
Apache License
|
|
2
|
-
Version 2.0, January 2004
|
|
3
|
-
http://www.apache.org/licenses/
|
|
4
|
-
|
|
5
|
-
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
-
|
|
7
|
-
1. Definitions.
|
|
8
|
-
|
|
9
|
-
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
-
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
-
|
|
12
|
-
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
-
the copyright owner that is granting the License.
|
|
14
|
-
|
|
15
|
-
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
-
other entities that control, are controlled by, or are under common
|
|
17
|
-
control with that entity. For the purposes of this definition,
|
|
18
|
-
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
-
direction or management of such entity, whether by contract or
|
|
20
|
-
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
-
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
-
|
|
23
|
-
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
-
exercising permissions granted by this License.
|
|
25
|
-
|
|
26
|
-
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
-
including but not limited to software source code, documentation
|
|
28
|
-
source, and configuration files.
|
|
29
|
-
|
|
30
|
-
"Object" form shall mean any form resulting from mechanical
|
|
31
|
-
transformation or translation of a Source form, including but
|
|
32
|
-
not limited to compiled object code, generated documentation,
|
|
33
|
-
and conversions to other media types.
|
|
34
|
-
|
|
35
|
-
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
-
Object form, made available under the License, as indicated by a
|
|
37
|
-
copyright notice that is included in or attached to the work
|
|
38
|
-
(an example is provided in the Appendix below).
|
|
39
|
-
|
|
40
|
-
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
-
form, that is based on (or derived from) the Work and for which the
|
|
42
|
-
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
-
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
-
of this License, Derivative Works shall not include works that remain
|
|
45
|
-
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
-
the Work and Derivative Works thereof.
|
|
47
|
-
|
|
48
|
-
"Contribution" shall mean any work of authorship, including
|
|
49
|
-
the original version of the Work and any modifications or additions
|
|
50
|
-
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
-
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
-
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
-
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
-
means any form of electronic, verbal, or written communication sent
|
|
55
|
-
to the Licensor or its representatives, including but not limited to
|
|
56
|
-
communication on electronic mailing lists, source code control systems,
|
|
57
|
-
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
-
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
-
excluding communication that is conspicuously marked or otherwise
|
|
60
|
-
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
-
|
|
62
|
-
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
-
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
-
subsequently incorporated within the Work.
|
|
65
|
-
|
|
66
|
-
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
-
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
-
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
-
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
-
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
-
Work and such Derivative Works in Source or Object form.
|
|
72
|
-
|
|
73
|
-
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
-
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
-
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
-
(except as stated in this section) patent license to make, have made,
|
|
77
|
-
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
-
where such license applies only to those patent claims licensable
|
|
79
|
-
by such Contributor that are necessarily infringed by their
|
|
80
|
-
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
-
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
-
institute patent litigation against any entity (including a
|
|
83
|
-
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
-
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
-
or contributory patent infringement, then any patent licenses
|
|
86
|
-
granted to You under this License for that Work shall terminate
|
|
87
|
-
as of the date such litigation is filed.
|
|
88
|
-
|
|
89
|
-
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
-
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
-
modifications, and in Source or Object form, provided that You
|
|
92
|
-
meet the following conditions:
|
|
93
|
-
|
|
94
|
-
(a) You must give any other recipients of the Work or
|
|
95
|
-
Derivative Works a copy of this License; and
|
|
96
|
-
|
|
97
|
-
(b) You must cause any modified files to carry prominent notices
|
|
98
|
-
stating that You changed the files; and
|
|
99
|
-
|
|
100
|
-
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
-
that You distribute, all copyright, patent, trademark, and
|
|
102
|
-
attribution notices from the Source form of the Work,
|
|
103
|
-
excluding those notices that do not pertain to any part of
|
|
104
|
-
the Derivative Works; and
|
|
105
|
-
|
|
106
|
-
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
-
distribution, then any Derivative Works that You distribute must
|
|
108
|
-
include a readable copy of the attribution notices contained
|
|
109
|
-
within such NOTICE file, excluding those notices that do not
|
|
110
|
-
pertain to any part of the Derivative Works, in at least one
|
|
111
|
-
of the following places: within a NOTICE text file distributed
|
|
112
|
-
as part of the Derivative Works; within the Source form or
|
|
113
|
-
documentation, if provided along with the Derivative Works; or,
|
|
114
|
-
within a display generated by the Derivative Works, if and
|
|
115
|
-
wherever such third-party notices normally appear. The contents
|
|
116
|
-
of the NOTICE file are for informational purposes only and
|
|
117
|
-
do not modify the License. You may add Your own attribution
|
|
118
|
-
notices within Derivative Works that You distribute, alongside
|
|
119
|
-
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
-
that such additional attribution notices cannot be construed
|
|
121
|
-
as modifying the License.
|
|
122
|
-
|
|
123
|
-
You may add Your own copyright statement to Your modifications and
|
|
124
|
-
may provide additional or different license terms and conditions
|
|
125
|
-
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
-
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
-
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
-
the conditions stated in this License.
|
|
129
|
-
|
|
130
|
-
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
-
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
-
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
-
this License, without any additional terms or conditions.
|
|
134
|
-
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
-
the terms of any separate license agreement you may have executed
|
|
136
|
-
with Licensor regarding such Contributions.
|
|
137
|
-
|
|
138
|
-
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
-
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
-
except as required for reasonable and customary use in describing the
|
|
141
|
-
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
-
|
|
143
|
-
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
-
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
-
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
-
implied, including, without limitation, any warranties or conditions
|
|
148
|
-
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
-
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
-
appropriateness of using or redistributing the Work and assume any
|
|
151
|
-
risks associated with Your exercise of permissions under this License.
|
|
152
|
-
|
|
153
|
-
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
-
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
-
unless required by applicable law (such as deliberate and grossly
|
|
156
|
-
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
-
liable to You for damages, including any direct, indirect, special,
|
|
158
|
-
incidental, or consequential damages of any character arising as a
|
|
159
|
-
result of this License or out of the use or inability to use the
|
|
160
|
-
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
-
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
-
other commercial damages or losses), even if such Contributor
|
|
163
|
-
has been advised of the possibility of such damages.
|
|
164
|
-
|
|
165
|
-
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
-
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
-
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
-
or other liability obligations and/or rights consistent with this
|
|
169
|
-
License. However, in accepting such obligations, You may act only
|
|
170
|
-
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
-
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
-
defend, and hold each Contributor harmless for any liability
|
|
173
|
-
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
-
of your accepting any such warranty or additional liability.
|
|
175
|
-
|
|
176
|
-
END OF TERMS AND CONDITIONS
|
|
177
|
-
|
|
178
|
-
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
-
|
|
180
|
-
To apply the Apache License to your work, attach the following
|
|
181
|
-
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
-
replaced with your own identifying information. (Don't include
|
|
183
|
-
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
-
comment syntax for the file format. We also recommend that a
|
|
185
|
-
file or class name and description of purpose be included on the
|
|
186
|
-
same "printed page" as the copyright notice for easier
|
|
187
|
-
identification within third-party archives.
|
|
188
|
-
|
|
189
|
-
Copyright [yyyy] [name of copyright owner]
|
|
190
|
-
|
|
191
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
-
you may not use this file except in compliance with the License.
|
|
193
|
-
You may obtain a copy of the License at
|
|
194
|
-
|
|
195
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
-
|
|
197
|
-
Unless required by applicable law or agreed to in writing, software
|
|
198
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
-
See the License for the specific language governing permissions and
|
|
201
|
-
limitations under the License.
|