pi-landstrip 0.18.4 → 0.18.6
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 +99 -303
- package/dist/index.ts +65 -31
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,32 +1,19 @@
|
|
|
1
1
|
# pi-landstrip
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
`pi-landstrip` adds sandboxed Bash, primary agents, and sandboxed subagents to
|
|
6
|
-
[Pi](https://pi.dev/). OS isolation is provided by
|
|
3
|
+
`pi-landstrip` adds sandboxed Bash, primary agents, and process-backed subagents
|
|
4
|
+
to [Pi](https://pi.dev/). OS isolation is provided by
|
|
7
5
|
[`landstrip`](https://github.com/landstrip/landstrip).
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
subagent limits, permissions, and OpenCode imports under `landstrip` in Pi
|
|
11
|
-
`settings.json`. Trusted project settings override global settings.
|
|
12
|
-
|
|
13
|
-
Process-backed subagents require Pi >= 0.82.0, and Node.js >= 22.19.0.
|
|
7
|
+
Requires Pi 0.82.0 or later and Node.js 22.19.0 or later.
|
|
14
8
|
|
|
15
9
|
## Installation
|
|
16
10
|
|
|
17
|
-
### Automatic install
|
|
18
|
-
|
|
19
11
|
```sh
|
|
20
12
|
pi install npm:pi-landstrip
|
|
21
13
|
```
|
|
22
14
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
### Manual install
|
|
27
|
-
|
|
28
|
-
Add the extension to `~/.pi/agent/settings.json` (global) or
|
|
29
|
-
`.pi/settings.json` (project):
|
|
15
|
+
For a manual install, add the package to global or trusted project
|
|
16
|
+
`settings.json`:
|
|
30
17
|
|
|
31
18
|
```json
|
|
32
19
|
{
|
|
@@ -34,183 +21,93 @@ Add the extension to `~/.pi/agent/settings.json` (global) or
|
|
|
34
21
|
}
|
|
35
22
|
```
|
|
36
23
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
[extension documentation](https://pi.dev/docs/latest/extensions) for details.
|
|
40
|
-
|
|
41
|
-
On unsupported platforms the extension loads but leaves sandboxing disabled.
|
|
42
|
-
|
|
43
|
-
Pi's Git Bash/MSYS shell cannot start in LPAC, so the default Windows policy uses
|
|
44
|
-
standard AppContainer. `/sandbox` and the status line show the active Windows
|
|
45
|
-
isolation. LPAC launch failures return an error.
|
|
46
|
-
|
|
47
|
-
Restricted-user isolation supports Git Bash with restricted local accounts. See
|
|
48
|
-
[Windows restricted-user installation](#windows-restricted-user-installation).
|
|
49
|
-
|
|
50
|
-
## Disabling
|
|
51
|
-
|
|
52
|
-
Use the `--no-sandbox` flag, or set `enabled` to `false` in `sandbox.json`:
|
|
53
|
-
|
|
54
|
-
```json
|
|
55
|
-
{
|
|
56
|
-
"enabled": false
|
|
57
|
-
}
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
When sandboxing is explicitly disabled, subagents still run as separate Pi RPC
|
|
61
|
-
processes, but without the outer Landstrip OS sandbox. The extension warns once
|
|
62
|
-
per session. Agent tool permissions still apply, but they are not an OS isolation
|
|
63
|
-
boundary.
|
|
64
|
-
|
|
65
|
-
Trusted project config overrides global config. `/sandbox` updates a trusted
|
|
66
|
-
project sandbox file when present, otherwise the global file. Pi versions
|
|
67
|
-
without a project-trust API use only global configuration.
|
|
24
|
+
The package includes native Linux, macOS, and Windows binaries for x64 and
|
|
25
|
+
Arm64. On unsupported platforms, the extension loads with sandboxing disabled.
|
|
68
26
|
|
|
69
27
|
## Behavior
|
|
70
28
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
go to `~/.pi/agent/sandbox.json`.
|
|
29
|
+
AI Bash calls and shell commands entered with `!` or `!!` run through
|
|
30
|
+
Landstrip. Subagents run as separate Pi RPC processes and are sandboxed unless
|
|
31
|
+
the sandbox is explicitly disabled.
|
|
75
32
|
|
|
76
|
-
The
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
direct access is disabled and the platform policy permits loopback; otherwise it is
|
|
80
|
-
blocked. Pi's filesystem tools and plugin callbacks remain trusted code outside the
|
|
81
|
-
Landstrip sandbox.
|
|
33
|
+
The root Pi process is trusted. Pi filesystem tools and plugin callbacks run
|
|
34
|
+
outside the OS sandbox. Agent permissions still control tool dispatch, but they
|
|
35
|
+
are not an OS isolation boundary.
|
|
82
36
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
37
|
+
The default [sandbox policy](./sandbox.json) limits reads and writes to the
|
|
38
|
+
project and a few bootstrap files. Direct network access is blocked. When the
|
|
39
|
+
platform permits loopback, the extension's proxy filters allowed HTTP and HTTPS
|
|
40
|
+
domains.
|
|
86
41
|
|
|
87
|
-
|
|
88
|
-
global
|
|
89
|
-
|
|
90
|
-
normal Pi worker and are not persisted into `sandbox.json`. The worker receives
|
|
91
|
-
write access only to its own session and temporary directories.
|
|
42
|
+
When a command needs more access, Pi can allow it once, for the session, or in
|
|
43
|
+
the project or global policy. Project approvals go to `.pi/sandbox.json` and
|
|
44
|
+
global approvals to `~/.pi/agent/sandbox.json`.
|
|
92
45
|
|
|
93
|
-
|
|
94
|
-
|
|
46
|
+
- `/sandbox` inspects and toggles the sandbox.
|
|
47
|
+
- `/agents` selects agents, edits agent settings, and shows task sessions.
|
|
48
|
+
- `Ctrl+Shift+A` cycles visible primary agents while Pi is idle.
|
|
95
49
|
|
|
96
|
-
|
|
50
|
+
### Disabling the sandbox
|
|
97
51
|
|
|
98
|
-
|
|
52
|
+
Use `--no-sandbox` or set `enabled` to `false` in `sandbox.json`:
|
|
99
53
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
```text
|
|
106
|
-
agent → agent permission → tool → sandbox → OS resource
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"enabled": false
|
|
57
|
+
}
|
|
107
58
|
```
|
|
108
59
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
## Primary agents
|
|
60
|
+
Subagents remain separate processes but lose Landstrip OS isolation. Pi warns
|
|
61
|
+
once per session. Trusted project config overrides global config; `/sandbox`
|
|
62
|
+
updates a project sandbox file when present, otherwise the global file.
|
|
113
63
|
|
|
114
|
-
|
|
115
|
-
visible primary agent. The built-in primary agents are `build` and `plan`. Build has
|
|
116
|
-
normal development access; plan asks before shell commands and file edits.
|
|
64
|
+
### Permission layers
|
|
117
65
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
global-only agents.
|
|
66
|
+
| Layer | Checked | Controls |
|
|
67
|
+
| ---------------- | ------------------ | ------------------------------ |
|
|
68
|
+
| Agent permission | Before a tool runs | Which tools the agent may call |
|
|
69
|
+
| Sandbox policy | During a command | Filesystem and network access |
|
|
123
70
|
|
|
124
|
-
|
|
125
|
-
|
|
71
|
+
An agent approval allows tool dispatch; the sandbox still applies to the
|
|
72
|
+
resulting process.
|
|
126
73
|
|
|
127
|
-
##
|
|
74
|
+
## Agents
|
|
128
75
|
|
|
129
|
-
The
|
|
130
|
-
|
|
131
|
-
|
|
76
|
+
The built-in primary agents are `build` and `plan`. The built-in subagents are
|
|
77
|
+
`explore`, `general`, and `scout`. `/agents` can activate, edit, or disable
|
|
78
|
+
configured agents.
|
|
132
79
|
|
|
133
|
-
|
|
134
|
-
`pi-landstrip` worker extension. Requested tools are composed inside the worker,
|
|
135
|
-
so plugins can add or replace implementations. Each task starts a fresh Pi
|
|
136
|
-
process and plugin instances; continuing a `task_id` restores its persisted Pi
|
|
137
|
-
session in a new process.
|
|
138
|
-
|
|
139
|
-
The tool accepts these fields:
|
|
80
|
+
The `task` tool starts a subagent. It accepts:
|
|
140
81
|
|
|
141
82
|
```json
|
|
142
83
|
{
|
|
143
84
|
"description": "Review sandbox boundary",
|
|
144
85
|
"prompt": "Review the sandbox implementation and report concrete issues.",
|
|
145
|
-
"subagent_type": "
|
|
86
|
+
"subagent_type": "explore",
|
|
146
87
|
"task_id": "optional-session-id",
|
|
147
88
|
"command": "optional-originating-command",
|
|
148
89
|
"background": false
|
|
149
90
|
}
|
|
150
91
|
```
|
|
151
92
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
Session switching or shutdown stops live workers. After an unclean restart,
|
|
157
|
-
unfinished work is marked interrupted; completed but undelivered background
|
|
158
|
-
results are delivered when the root session resumes.
|
|
159
|
-
|
|
160
|
-
Workers check agent permissions before tool dispatch: `deny` blocks, `ask` prompts in
|
|
161
|
-
the root UI, and `allow` runs the tool. Required sandbox startup failures stop the
|
|
162
|
-
task. With `--no-sandbox` or `enabled: false`, workers run without OS isolation and
|
|
163
|
-
Pi shows a warning.
|
|
164
|
-
|
|
165
|
-
### Platform behavior
|
|
166
|
-
|
|
167
|
-
- **Linux**: seccomp query traps can grant a blocked filesystem or network
|
|
168
|
-
operation dynamically, so an approved worker can continue without restart.
|
|
169
|
-
- **macOS**: Seatbelt policy is fixed when the worker starts. Update
|
|
170
|
-
`sandbox.json`, then restart or continue the task in a fresh worker to apply
|
|
171
|
-
additional filesystem access. Main-agent Bash can retry a command, but there
|
|
172
|
-
is no live worker-policy update.
|
|
173
|
-
- **Windows AppContainer**: the packaged policy selects standard AppContainer for
|
|
174
|
-
Pi's Git Bash/MSYS runtime. With `windows.allowLoopback: false` (the default), the
|
|
175
|
-
extension does not start an unreachable proxy and the worker has no network.
|
|
176
|
-
Setting `windows.allowLoopback` to `true` enables the domain proxy, but also gives
|
|
177
|
-
the AppContainer access to every local loopback service, not only the proxy port.
|
|
178
|
-
Setting `network.allowNetwork` to `true` instead gives the entire worker
|
|
179
|
-
unrestricted network access; domain lists are then not a boundary. Both opt-ins
|
|
180
|
-
are explicit security downgrades and produce warnings.
|
|
181
|
-
- **Windows restricted user**: WFP blocks restricted accounts except for the
|
|
182
|
-
installed proxy port range, so domain-filtered proxying works without a broad
|
|
183
|
-
loopback exemption. `network.allowNetwork: true` leases a separately provisioned
|
|
184
|
-
unrestricted account. Filesystem grants are journaled and revoked after every
|
|
185
|
-
process tree exits.
|
|
186
|
-
|
|
187
|
-
### Credentials
|
|
188
|
-
|
|
189
|
-
Model requests originate inside each worker. Pi's `auth.json` is readable but
|
|
190
|
-
not writable by workers. Inherited credential environment variables also cross
|
|
191
|
-
the sandbox boundary, and plugins loaded in that worker share access to these
|
|
192
|
-
credentials. Landstrip does not provide a credential broker: load only trusted
|
|
193
|
-
plugins and use credentials appropriate for the sandboxed process. An OAuth
|
|
194
|
-
refresh that needs to rewrite `auth.json` must be completed in the root Pi
|
|
195
|
-
first.
|
|
196
|
-
|
|
197
|
-
## Configuration
|
|
198
|
-
|
|
199
|
-
Agent permissions are configured under `landstrip` in Pi `settings.json`. Sandbox
|
|
200
|
-
permissions are configured in `sandbox.json`. The files use different schemas and
|
|
201
|
-
control different stages.
|
|
93
|
+
`description`, `prompt`, and `subagent_type` are required. Foreground tasks
|
|
94
|
+
return their result directly. Background tasks return immediately and report
|
|
95
|
+
completion later. Continue a saved task with `task_id`; inspect it with
|
|
96
|
+
`/agents <id>`.
|
|
202
97
|
|
|
203
|
-
|
|
98
|
+
Workers use normal Pi resource discovery and plugin loading. Each task starts a
|
|
99
|
+
fresh process; continuing a task restores its saved session in a new process.
|
|
100
|
+
Agent `deny`, `ask`, and `allow` rules are checked before tool dispatch.
|
|
204
101
|
|
|
205
|
-
|
|
206
|
-
|
|
102
|
+
Model requests originate inside each worker. Workers can read Pi authentication
|
|
103
|
+
and inherited credential environment variables. Loaded worker plugins share
|
|
104
|
+
that access, so install only trusted plugins and use suitable credentials.
|
|
207
105
|
|
|
208
|
-
|
|
209
|
-
projects, `.pi/agents/*.md`. Nested directories are supported. Project Markdown
|
|
210
|
-
agents override global Markdown agents. Settings under `landstrip.agent` override
|
|
211
|
-
Markdown and OpenCode agents with the same name.
|
|
106
|
+
## Configuration
|
|
212
107
|
|
|
213
|
-
|
|
108
|
+
Agent settings are read from `~/.pi/agent/settings.json` and, for trusted
|
|
109
|
+
projects, `.pi/settings.json`. Sandbox settings are read from
|
|
110
|
+
`~/.pi/agent/sandbox.json` and `.pi/sandbox.json`.
|
|
214
111
|
|
|
215
112
|
```json
|
|
216
113
|
{
|
|
@@ -220,7 +117,7 @@ Add agent configuration under `landstrip` in Pi `settings.json`:
|
|
|
220
117
|
"review": {
|
|
221
118
|
"description": "Review code without modifying it",
|
|
222
119
|
"mode": "subagent",
|
|
223
|
-
"prompt": "
|
|
120
|
+
"prompt": "Report concrete findings.",
|
|
224
121
|
"permission": {
|
|
225
122
|
"edit": "deny",
|
|
226
123
|
"bash": "ask"
|
|
@@ -237,167 +134,66 @@ Add agent configuration under `landstrip` in Pi `settings.json`:
|
|
|
237
134
|
}
|
|
238
135
|
```
|
|
239
136
|
|
|
240
|
-
`landstrip.maxSubagents`
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
the global value.
|
|
245
|
-
|
|
246
|
-
`landstrip.permission` applies to every agent. Each
|
|
247
|
-
`landstrip.agent.<name>.permission` map adds agent-specific rules; later matching
|
|
248
|
-
rules win. Agent definitions support `mode`, `prompt`, `model`, `variant`, `steps`,
|
|
249
|
-
`color`, `disable`, and `allow`/`ask`/`deny` permissions. `{file:path}` prompt
|
|
250
|
-
references are resolved from the defining `settings.json`. Provider-specific fields
|
|
251
|
-
belong under `options`.
|
|
252
|
-
|
|
253
|
-
`hidden` removes an agent from `/agents`. A hidden, subagent-capable agent remains
|
|
254
|
-
available to `task`. Disabled agents remain visible but dimmed and cannot be activated
|
|
255
|
-
or passed to `task`. Agent `mode` controls activation: only `primary` and `all` agents
|
|
256
|
-
can be selected as primary, while only `subagent` and `all` agents can be passed to
|
|
257
|
-
`task`. Missing, ambiguous, or unauthenticated primary-agent models produce an error
|
|
258
|
-
and leave the current agent active.
|
|
259
|
-
|
|
260
|
-
OpenCode integration flags also live in Pi `settings.json` under
|
|
261
|
-
`landstrip.opencode`; both default to `true`:
|
|
262
|
-
|
|
263
|
-
```json
|
|
264
|
-
{
|
|
265
|
-
"packages": ["npm:pi-landstrip"],
|
|
266
|
-
"landstrip": {
|
|
267
|
-
"opencode": {
|
|
268
|
-
"showGlobalAgents": true,
|
|
269
|
-
"showLocalAgents": true
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
- `showGlobalAgents` imports `agent` definitions from `opencode.json` and
|
|
276
|
-
`opencode.jsonc`, plus Markdown agents under `agent/` and `agents/`, in the
|
|
277
|
-
OpenCode global config directory (`$OPENCODE_CONFIG_DIR`,
|
|
278
|
-
`$XDG_CONFIG_HOME/opencode`, or `~/.config/opencode`).
|
|
279
|
-
- `showLocalAgents` imports `agent` definitions from project `opencode.json` and
|
|
280
|
-
`opencode.jsonc`, `.opencode/opencode.json` and `.opencode/opencode.jsonc`, and
|
|
281
|
-
Markdown agents under `.opencode/agent/` and `.opencode/agents/`.
|
|
282
|
-
|
|
283
|
-
Set either flag to `false` to disable that OpenCode source. Project OpenCode agents
|
|
284
|
-
override global OpenCode agents. Pi Markdown agents and `landstrip.agent` settings
|
|
285
|
-
take precedence over OpenCode imports. Project sources require a trusted project.
|
|
286
|
-
OpenCode config files support JSONC comments, trailing commas, and `{file:path}`
|
|
287
|
-
prompt references.
|
|
137
|
+
`landstrip.maxSubagents` is the concurrency limit, from 0 to 16. Zero disables
|
|
138
|
+
the `task` tool. Agent definitions support `mode`, `prompt`, `model`, `variant`,
|
|
139
|
+
`steps`, `color`, `hidden`, `disable`, `options`, and permission rules. Modes
|
|
140
|
+
are `primary`, `subagent`, or `all`.
|
|
288
141
|
|
|
289
|
-
|
|
142
|
+
Pi Markdown agents are loaded from `~/.pi/agent/agents/` and `.pi/agents/`.
|
|
143
|
+
`landstrip.agent` settings override Markdown agents with the same name.
|
|
144
|
+
Project definitions override global definitions.
|
|
290
145
|
|
|
291
|
-
|
|
292
|
-
|
|
146
|
+
OpenCode agent imports are enabled by default. Set
|
|
147
|
+
`landstrip.opencode.showGlobalAgents` or `showLocalAgents` to `false` to disable
|
|
148
|
+
a source. OpenCode agents fill missing names; Pi agents take precedence.
|
|
149
|
+
Project imports require a trusted project.
|
|
293
150
|
|
|
294
|
-
|
|
151
|
+
See the main [Landstrip documentation](https://github.com/landstrip/landstrip#readme)
|
|
152
|
+
for `sandbox.json` fields and platform policy semantics.
|
|
295
153
|
|
|
296
|
-
|
|
297
|
-
{
|
|
298
|
-
"windows": {
|
|
299
|
-
"appContainerMode": "standard",
|
|
300
|
-
"allowLoopback": false
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
`appContainerMode` accepts `"lpac"` or `"standard"`. The Pi package defaults to
|
|
306
|
-
standard mode for Git Bash. `allowLoopback` applies only to AppContainer. Landstrip
|
|
307
|
-
selects Windows isolation automatically.
|
|
154
|
+
### Platform notes
|
|
308
155
|
|
|
309
|
-
|
|
156
|
+
- **Linux:** blocked operations can be approved while the command is running.
|
|
157
|
+
- **macOS:** a worker's Seatbelt policy is fixed at startup; restart the task to
|
|
158
|
+
apply new filesystem access.
|
|
159
|
+
- **Windows:** the bundled policy uses standard AppContainer because Pi's Git
|
|
160
|
+
Bash cannot start in LPAC. Standard mode is weaker than LPAC.
|
|
161
|
+
`windows.allowLoopback` enables the domain proxy but exposes every local
|
|
162
|
+
loopback service. Restricted-user mode avoids that broad exemption.
|
|
310
163
|
|
|
311
|
-
|
|
164
|
+
Install restricted-user mode from the intended Windows host account, then
|
|
165
|
+
restart Pi:
|
|
312
166
|
|
|
313
167
|
```powershell
|
|
314
168
|
npx @landstrip/landstrip windows install
|
|
315
169
|
npx @landstrip/landstrip windows status
|
|
316
170
|
```
|
|
317
171
|
|
|
318
|
-
|
|
319
|
-
unrestricted-network accounts, and proxy ports 60080–60111 by default. Use
|
|
320
|
-
`windows install --help` to change these values. Restart Pi after installation.
|
|
321
|
-
|
|
322
|
-
Before each launch, the extension checks the installation. A failed health check
|
|
323
|
-
stops the task. Restricted-user isolation does not support `windows.allowLoopback`
|
|
324
|
-
or `network.allowLocalBinding`. Remove the accounts, WFP rules, runner, and ACL
|
|
325
|
-
grants with:
|
|
326
|
-
|
|
327
|
-
```powershell
|
|
328
|
-
npx @landstrip/landstrip windows uninstall
|
|
329
|
-
```
|
|
172
|
+
Remove it with `npx @landstrip/landstrip windows uninstall`.
|
|
330
173
|
|
|
331
174
|
## Plugin API
|
|
332
175
|
|
|
333
|
-
Pi extensions can discover the
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
```ts
|
|
337
|
-
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
338
|
-
import { useLandstrip } from 'pi-landstrip/api';
|
|
339
|
-
|
|
340
|
-
export default function (pi: ExtensionAPI) {
|
|
341
|
-
const dispose = useLandstrip(pi, (landstrip) => {
|
|
342
|
-
const context = landstrip.getContext();
|
|
343
|
-
// Register Landstrip-aware behavior here.
|
|
344
|
-
});
|
|
345
|
-
pi.on('session_shutdown', dispose);
|
|
346
|
-
}
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
Discovery is callback-based so it works regardless of extension load order.
|
|
350
|
-
Do not wait for discovery from an extension factory: Pi initializes extension
|
|
351
|
-
factories sequentially. The returned function stops future notifications.
|
|
352
|
-
|
|
353
|
-
The runtime exposes:
|
|
176
|
+
Other Pi extensions can discover the runtime with `useLandstrip` from
|
|
177
|
+
`pi-landstrip/api`. The versioned runtime provides:
|
|
354
178
|
|
|
355
|
-
- `getContext()` for
|
|
356
|
-
|
|
357
|
-
- `
|
|
358
|
-
|
|
359
|
-
- `
|
|
360
|
-
sandbox policy.
|
|
361
|
-
- `registerWorkerExtension()` to load an absolute extension entry in Landstrip
|
|
362
|
-
subagents. The returned function removes the registration.
|
|
363
|
-
- `on()` for typed `sandbox.changed`, `subagent.start` and `subagent.end`
|
|
364
|
-
lifecycle events.
|
|
179
|
+
- `getContext()` for sandbox and task context.
|
|
180
|
+
- `createBashTool()` for Landstrip-backed command tools.
|
|
181
|
+
- `prepareProcess()` for one policy-aware process launch.
|
|
182
|
+
- `registerWorkerExtension()` for trusted worker extensions.
|
|
183
|
+
- `on()` for sandbox and subagent lifecycle events.
|
|
365
184
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
Workers receive a base64url JSON `LANDSTRIP_CONTEXT` environment variable.
|
|
371
|
-
Extensions can read it without runtime discovery:
|
|
372
|
-
|
|
373
|
-
```ts
|
|
374
|
-
import { contextFromEnvironment } from 'pi-landstrip/api';
|
|
375
|
-
|
|
376
|
-
const context = contextFromEnvironment();
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
This public context is informational and contains no permission rules, policy,
|
|
380
|
-
credentials or proxy secrets. Environment values can be spoofed outside a
|
|
381
|
-
Landstrip worker and must not be treated as authorization.
|
|
382
|
-
|
|
383
|
-
Generic process preparation fails closed on Windows when sandboxing is disabled.
|
|
384
|
-
Windows cannot reliably reclaim an unsandboxed descendant tree after its leader
|
|
385
|
-
has exited; enabled Landstrip launches use an AppContainer job object instead.
|
|
185
|
+
Discovery is callback-based and independent of extension load order. Worker
|
|
186
|
+
extensions are trusted code. Workers also receive an informational
|
|
187
|
+
`LANDSTRIP_CONTEXT` environment value; it is not proof of authorization.
|
|
386
188
|
|
|
387
189
|
## Limits
|
|
388
190
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
preventing nested-task deadlocks. A worker receives a nested `task` tool only
|
|
393
|
-
when its agent has an explicit `task` permission. Nested tasks are separate Pi
|
|
394
|
-
processes supervised by the root and Landstrip-wrapped unless sandboxing is
|
|
395
|
-
explicitly disabled, so an inactive parent process can remain alive during the
|
|
396
|
-
handoff. Persisted sessions remain resumable by `task_id`.
|
|
191
|
+
At most `maxSubagents` tasks run concurrently. Nested task depth is capped at
|
|
192
|
+
3, and a nested `task` tool is available only when the parent agent has explicit
|
|
193
|
+
permission. Session switching and shutdown stop live workers.
|
|
397
194
|
|
|
398
195
|
## License
|
|
399
196
|
|
|
400
|
-
`pi-landstrip` is licensed under `Apache-2.0`. See [LICENSE](LICENSE).
|
|
401
|
-
|
|
402
|
-
The bundled `@landstrip/landstrip` package is licensed separately as
|
|
197
|
+
`pi-landstrip` is licensed under `Apache-2.0`. See [LICENSE](LICENSE). The
|
|
198
|
+
bundled `@landstrip/landstrip` package is licensed separately as
|
|
403
199
|
`Apache-2.0 AND LGPL-2.1-or-later`.
|
package/dist/index.ts
CHANGED
|
@@ -106,28 +106,28 @@ var BUILT_IN_LANDSTRIP_CONFIG = {
|
|
|
106
106
|
maxSubagents: 1,
|
|
107
107
|
agent: {
|
|
108
108
|
build: {
|
|
109
|
-
description: "
|
|
109
|
+
description: "Implement and test requested changes",
|
|
110
110
|
mode: "primary",
|
|
111
111
|
color: "success",
|
|
112
|
-
prompt: "
|
|
112
|
+
prompt: "Implement, test, and complete the request."
|
|
113
113
|
},
|
|
114
114
|
plan: {
|
|
115
|
-
description: "
|
|
115
|
+
description: "Plan before running commands or editing files",
|
|
116
116
|
mode: "primary",
|
|
117
117
|
color: "warning",
|
|
118
|
-
prompt: "
|
|
118
|
+
prompt: "Analyze the request and produce a clear plan before making changes.",
|
|
119
119
|
permission: { edit: "ask", bash: "ask", task: "ask" }
|
|
120
120
|
},
|
|
121
121
|
general: {
|
|
122
122
|
description: "General-purpose agent for complex tasks",
|
|
123
123
|
mode: "subagent",
|
|
124
|
-
prompt: "Complete the
|
|
124
|
+
prompt: "Complete the task and return a concise result.",
|
|
125
125
|
permission: { todowrite: "deny" }
|
|
126
126
|
},
|
|
127
127
|
scout: {
|
|
128
|
-
description: "
|
|
128
|
+
description: "Read-only codebase reconnaissance",
|
|
129
129
|
mode: "subagent",
|
|
130
|
-
prompt: "
|
|
130
|
+
prompt: "Inspect the codebase without editing. Return concise findings with exact file references and a recommended starting point.",
|
|
131
131
|
permission: {
|
|
132
132
|
"*": "deny",
|
|
133
133
|
read: {
|
|
@@ -142,9 +142,9 @@ var BUILT_IN_LANDSTRIP_CONFIG = {
|
|
|
142
142
|
}
|
|
143
143
|
},
|
|
144
144
|
explore: {
|
|
145
|
-
description: "
|
|
145
|
+
description: "Codebase research with shell and web access",
|
|
146
146
|
mode: "subagent",
|
|
147
|
-
prompt: "Explore
|
|
147
|
+
prompt: "Explore without editing. Use shell or web tools when needed, then return concise findings with file references.",
|
|
148
148
|
permission: {
|
|
149
149
|
"*": "deny",
|
|
150
150
|
read: {
|
|
@@ -1721,6 +1721,22 @@ var packageDir = dirname4(fileURLToPath(import.meta.url));
|
|
|
1721
1721
|
var MAX_TASK_OUTPUT_BYTES = 64 * 1024;
|
|
1722
1722
|
var INSPECTOR_BODY_LINES = 16;
|
|
1723
1723
|
var PI_THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
1724
|
+
var AGENTS_HELP_ROWS = [
|
|
1725
|
+
["Tab", "Next tab"],
|
|
1726
|
+
["Shift+Tab", "Change scope"],
|
|
1727
|
+
["↑ / ↓", "Select or scroll"],
|
|
1728
|
+
["← / →", "Sibling task"],
|
|
1729
|
+
["Enter", "Open / activate / save"],
|
|
1730
|
+
["Esc", "Back / close"],
|
|
1731
|
+
["Ctrl+C", "Close"],
|
|
1732
|
+
["D / Space", "Toggle agent"],
|
|
1733
|
+
["Space", "Toggle sandbox"],
|
|
1734
|
+
["I", "Use global value"],
|
|
1735
|
+
["0-9 / + / -", "Set subagent limit"],
|
|
1736
|
+
["P / Backspace", "Parent task"],
|
|
1737
|
+
["Ctrl+G", "Edit agent"],
|
|
1738
|
+
["Ctrl+D", "Delete agent"]
|
|
1739
|
+
];
|
|
1724
1740
|
var MIN_SUPPORTED_PI_VERSION = [0, 82, 0];
|
|
1725
1741
|
var cachedPiPackage;
|
|
1726
1742
|
var piPackageResolved = false;
|
|
@@ -1794,12 +1810,12 @@ function readPiPackage(pkgPath) {
|
|
|
1794
1810
|
}
|
|
1795
1811
|
}
|
|
1796
1812
|
var taskParameters = Type.Object({
|
|
1797
|
-
description: Type.String({ description: "
|
|
1798
|
-
prompt: Type.String({ description: "
|
|
1799
|
-
subagent_type: Type.String({ description: "
|
|
1800
|
-
task_id: Type.Optional(Type.String({ description: "
|
|
1801
|
-
command: Type.Optional(Type.String({ description: "
|
|
1802
|
-
background: Type.Optional(Type.Boolean({ description: "
|
|
1813
|
+
description: Type.String({ description: "Short task label (3-5 words)" }),
|
|
1814
|
+
prompt: Type.String({ description: "Full instructions for the subagent" }),
|
|
1815
|
+
subagent_type: Type.String({ description: "Subagent name" }),
|
|
1816
|
+
task_id: Type.Optional(Type.String({ description: "Task ID to resume" })),
|
|
1817
|
+
command: Type.Optional(Type.String({ description: "Originating command, if any" })),
|
|
1818
|
+
background: Type.Optional(Type.Boolean({ description: "Return immediately and run in background" }))
|
|
1803
1819
|
});
|
|
1804
1820
|
var TASK_STATES = new Set([
|
|
1805
1821
|
"queued",
|
|
@@ -2200,7 +2216,7 @@ function registerSubagentWorker(pi, config) {
|
|
|
2200
2216
|
pi.registerTool({
|
|
2201
2217
|
name: "task",
|
|
2202
2218
|
label: "Task",
|
|
2203
|
-
description: "Delegate
|
|
2219
|
+
description: "Delegate a task to a Pi subagent process.",
|
|
2204
2220
|
parameters: taskParameters,
|
|
2205
2221
|
executionMode: "parallel",
|
|
2206
2222
|
async execute(_id, input, signal, _onUpdate, ctx) {
|
|
@@ -2262,7 +2278,7 @@ class SubagentRuntime {
|
|
|
2262
2278
|
register() {
|
|
2263
2279
|
this.pi.registerTool(this.createTaskTool());
|
|
2264
2280
|
this.pi.registerCommand("agents", {
|
|
2265
|
-
description: "
|
|
2281
|
+
description: "Manage agents, tasks, and concurrency",
|
|
2266
2282
|
handler: async (args, ctx) => this.openAgents(args, ctx)
|
|
2267
2283
|
});
|
|
2268
2284
|
this.pi.registerShortcut("ctrl+shift+a", {
|
|
@@ -2420,8 +2436,12 @@ ${this.primaryAgent.prompt}` };
|
|
|
2420
2436
|
...lines2.map((line) => boxRow(theme, width, line)),
|
|
2421
2437
|
boxBottom(theme, width)
|
|
2422
2438
|
];
|
|
2439
|
+
const pad = (value, cellWidth) => {
|
|
2440
|
+
const clipped = truncateToWidth2(value, cellWidth);
|
|
2441
|
+
return `${clipped}${" ".repeat(Math.max(0, cellWidth - visibleWidth2(clipped)))}`;
|
|
2442
|
+
};
|
|
2423
2443
|
const tabLabel = (label, active) => active ? theme.fg("accent", theme.bold(`[${label}]`)) : theme.fg("muted", label);
|
|
2424
|
-
const tabs = `${tabLabel("Agents", tab === "agents")} ${tabLabel("Tasks", tab === "tasks")} ${tabLabel("Settings", tab === "settings")} ${theme.fg("dim", `· ${scope === "project" ? "Project" : "Global"} scope`)}`;
|
|
2444
|
+
const tabs = `${tabLabel("Agents", tab === "agents")} ${tabLabel("Tasks", tab === "tasks")} ${tabLabel("Settings", tab === "settings")} ${tabLabel("Help", tab === "help")} ${theme.fg("dim", `· ${scope === "project" ? "Project" : "Global"} scope`)}`;
|
|
2425
2445
|
if (tab === "agents") {
|
|
2426
2446
|
const start = Math.max(0, Math.min(selectedAgent - 3, agents.length - 7));
|
|
2427
2447
|
const shown2 = agents.slice(start, start + 7);
|
|
@@ -2431,10 +2451,6 @@ ${this.primaryAgent.prompt}` };
|
|
|
2431
2451
|
const disabledWidth = 8;
|
|
2432
2452
|
const fixedWidth = 4 + nameWidth + 1 + modeWidth + 1 + sourceWidth + 1 + disabledWidth;
|
|
2433
2453
|
const modelWidth = Math.max(10, Math.min(30, contentWidth - fixedWidth - 1));
|
|
2434
|
-
const pad = (value, cellWidth) => {
|
|
2435
|
-
const clipped = truncateToWidth2(value, cellWidth);
|
|
2436
|
-
return `${clipped}${" ".repeat(Math.max(0, cellWidth - visibleWidth2(clipped)))}`;
|
|
2437
|
-
};
|
|
2438
2454
|
const lines2 = [
|
|
2439
2455
|
tabs,
|
|
2440
2456
|
"",
|
|
@@ -2491,8 +2507,11 @@ ${this.primaryAgent.prompt}` };
|
|
|
2491
2507
|
lines2.push(` ${theme.fg("error", diagnostic)}`);
|
|
2492
2508
|
}
|
|
2493
2509
|
}
|
|
2494
|
-
|
|
2495
|
-
|
|
2510
|
+
if (confirmingDeleteAgent) {
|
|
2511
|
+
lines2.push("", theme.fg("error", `Delete @${confirmingDeleteAgent}? enter confirm esc cancel`));
|
|
2512
|
+
} else if (saving) {
|
|
2513
|
+
lines2.push("", theme.fg("dim", "Saving…"));
|
|
2514
|
+
}
|
|
2496
2515
|
return box(lines2);
|
|
2497
2516
|
}
|
|
2498
2517
|
if (tab === "settings") {
|
|
@@ -2518,7 +2537,19 @@ ${this.primaryAgent.prompt}` };
|
|
|
2518
2537
|
if (selectedMaxSubagents() === 0) {
|
|
2519
2538
|
lines2.push(` ${theme.fg("dim", "Subagents disabled")}`);
|
|
2520
2539
|
}
|
|
2521
|
-
|
|
2540
|
+
if (saving)
|
|
2541
|
+
lines2.push("", theme.fg("dim", "Saving…"));
|
|
2542
|
+
return box(lines2);
|
|
2543
|
+
}
|
|
2544
|
+
if (tab === "help") {
|
|
2545
|
+
const shortcutWidth = Math.max(visibleWidth2("Shortcut"), ...AGENTS_HELP_ROWS.map(([shortcut]) => visibleWidth2(shortcut)));
|
|
2546
|
+
const descriptionWidth = Math.max(1, contentWidth - shortcutWidth - 4);
|
|
2547
|
+
const lines2 = [
|
|
2548
|
+
tabs,
|
|
2549
|
+
"",
|
|
2550
|
+
theme.fg("dim", ` ${pad("Shortcut", shortcutWidth)} Description`),
|
|
2551
|
+
...AGENTS_HELP_ROWS.map(([shortcut, description]) => ` ${theme.fg("accent", pad(shortcut, shortcutWidth))} ${theme.fg("text", truncateToWidth2(description, descriptionWidth))}`)
|
|
2552
|
+
];
|
|
2522
2553
|
return box(lines2);
|
|
2523
2554
|
}
|
|
2524
2555
|
if (!detail) {
|
|
@@ -2535,7 +2566,6 @@ ${this.primaryAgent.prompt}` };
|
|
|
2535
2566
|
lines2.push(`${cursor} ${indent}${taskState(theme, task2)} ${theme.fg("accent", `@${task2.agent}`)} ${theme.fg("text", task2.description)} ${theme.fg("dim", task2.id.slice(0, 8))}`);
|
|
2536
2567
|
}
|
|
2537
2568
|
}
|
|
2538
|
-
lines2.push("", theme.fg("dim", "↑↓ select enter inspect tab next ⇧tab scope esc close"));
|
|
2539
2569
|
return box(lines2);
|
|
2540
2570
|
}
|
|
2541
2571
|
const task = tasks[selectedTask];
|
|
@@ -2568,7 +2598,6 @@ ${this.primaryAgent.prompt}` };
|
|
|
2568
2598
|
if (transcript.length > INSPECTOR_BODY_LINES) {
|
|
2569
2599
|
lines.push(theme.fg("dim", `${scroll + 1}-${scroll + shown.length} of ${transcript.length}`));
|
|
2570
2600
|
}
|
|
2571
|
-
lines.push(theme.fg("dim", "Parent p/backspace Prev ← Next → ↑↓ scroll tab next ⇧tab scope esc back"));
|
|
2572
2601
|
return box(lines.map((line) => truncateToWidth2(line, contentWidth)));
|
|
2573
2602
|
},
|
|
2574
2603
|
handleInput: (data) => {
|
|
@@ -2616,7 +2645,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
2616
2645
|
return;
|
|
2617
2646
|
}
|
|
2618
2647
|
if (matchesKey(data, "tab")) {
|
|
2619
|
-
const tabs = ["agents", "tasks", "settings"];
|
|
2648
|
+
const tabs = ["agents", "tasks", "settings", "help"];
|
|
2620
2649
|
tab = tabs[(tabs.indexOf(tab) + 1) % tabs.length] ?? "agents";
|
|
2621
2650
|
detail = false;
|
|
2622
2651
|
editing = false;
|
|
@@ -2766,6 +2795,11 @@ ${this.primaryAgent.prompt}` };
|
|
|
2766
2795
|
tui.requestRender();
|
|
2767
2796
|
return;
|
|
2768
2797
|
}
|
|
2798
|
+
if (tab === "help") {
|
|
2799
|
+
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c"))
|
|
2800
|
+
done();
|
|
2801
|
+
return;
|
|
2802
|
+
}
|
|
2769
2803
|
if (!detail) {
|
|
2770
2804
|
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c"))
|
|
2771
2805
|
done();
|
|
@@ -2873,7 +2907,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
2873
2907
|
return {
|
|
2874
2908
|
name: "task",
|
|
2875
2909
|
label: "Task",
|
|
2876
|
-
description: "Delegate a task to a
|
|
2910
|
+
description: "Delegate a task to a Pi subagent process." + (descriptions ? `
|
|
2877
2911
|
|
|
2878
2912
|
Available subagents:
|
|
2879
2913
|
${descriptions}` : ""),
|
|
@@ -5574,7 +5608,7 @@ ${message}
|
|
|
5574
5608
|
function register(pi) {
|
|
5575
5609
|
const maybePi = pi;
|
|
5576
5610
|
maybePi.registerFlag?.("no-sandbox", {
|
|
5577
|
-
description: "Disable
|
|
5611
|
+
description: "Disable Landstrip OS sandboxing",
|
|
5578
5612
|
type: "boolean",
|
|
5579
5613
|
default: false
|
|
5580
5614
|
});
|
|
@@ -5618,7 +5652,7 @@ ${message}
|
|
|
5618
5652
|
unpublishRuntime = undefined;
|
|
5619
5653
|
});
|
|
5620
5654
|
maybePi.registerCommand?.("sandbox", {
|
|
5621
|
-
description: "
|
|
5655
|
+
description: "Inspect and toggle the sandbox",
|
|
5622
5656
|
handler: async (_args, ctx) => {
|
|
5623
5657
|
if (!ctx.hasUI)
|
|
5624
5658
|
return;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-landstrip",
|
|
3
|
-
"version": "0.18.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.18.6",
|
|
4
|
+
"description": "Sandboxed Bash and process-backed agents for Pi",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"landstrip",
|
|
7
7
|
"pi-extension",
|
|
@@ -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.6",
|
|
43
43
|
"jsonc-parser": "^3.3.1",
|
|
44
44
|
"minimatch": "^10.2.5",
|
|
45
45
|
"yaml": "^2.9.0"
|