pi-landstrip 0.18.0 → 0.18.2
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 +84 -177
- package/dist/index.ts +201 -175
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,21 +1,14 @@
|
|
|
1
1
|
# pi-landstrip
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
Subagents 3 active
|
|
5
|
-
├─ ● running @explore Design Pi entry parsing
|
|
6
|
-
├─ ● running @general Design trigger serialization
|
|
7
|
-
└─ ● running @general Design bounded summaries
|
|
8
|
-
```
|
|
3
|
+
<img alt="Screenshot of Pi running pi-landstrip" src="screenshot.png" width="800">
|
|
9
4
|
|
|
10
|
-
`pi-landstrip`
|
|
11
|
-
|
|
12
|
-
uses an Anthropic-compatible policy and delegates OS-level enforcement to
|
|
5
|
+
`pi-landstrip` adds sandboxed Bash, primary agents, and sandboxed subagents to
|
|
6
|
+
[Pi](https://pi.dev/). OS isolation is provided by
|
|
13
7
|
[`landstrip`](https://github.com/landstrip/landstrip).
|
|
14
8
|
|
|
15
|
-
The
|
|
16
|
-
|
|
17
|
-
`
|
|
18
|
-
and built-in agent defaults.
|
|
9
|
+
The package includes a default [sandbox policy](./sandbox.json). Configure agents,
|
|
10
|
+
subagent limits, permissions, and OpenCode imports under `landstrip` in Pi
|
|
11
|
+
`settings.json`. Trusted project settings override global settings.
|
|
19
12
|
|
|
20
13
|
Process-backed subagents require Pi >= 0.82.0, and Node.js >= 22.19.0.
|
|
21
14
|
|
|
@@ -47,14 +40,11 @@ Alternatively, place the extension under `~/.pi/agent/extensions/` (global) or
|
|
|
47
40
|
|
|
48
41
|
On unsupported platforms the extension loads but leaves sandboxing disabled.
|
|
49
42
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
`/sandbox` and the status line report the active implementation and mode. There
|
|
54
|
-
is no silent LPAC-to-standard fallback.
|
|
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.
|
|
55
46
|
|
|
56
|
-
|
|
57
|
-
It requires one-time elevation and persistent local accounts and WFP rules; see
|
|
47
|
+
Restricted-user isolation supports Git Bash with restricted local accounts. See
|
|
58
48
|
[Windows restricted-user installation](#windows-restricted-user-installation).
|
|
59
49
|
|
|
60
50
|
## Disabling
|
|
@@ -78,11 +68,10 @@ without a project-trust API use only global configuration.
|
|
|
78
68
|
|
|
79
69
|
## Behavior
|
|
80
70
|
|
|
81
|
-
When a
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
global approvals are written to `~/.pi/agent/sandbox.json`.
|
|
71
|
+
When a command requests additional access, the extension opens an approval dialog.
|
|
72
|
+
The user can allow once, allow for the session, save the approval globally or for the
|
|
73
|
+
project, or reject it. Project approvals go to `.pi/sandbox.json`; global approvals
|
|
74
|
+
go to `~/.pi/agent/sandbox.json`.
|
|
86
75
|
|
|
87
76
|
The main agent remains a normal Pi process. `pi-landstrip` replaces Bash execution,
|
|
88
77
|
including AI `bash` calls and manually typed shell commands (`!` and `!!`), with a
|
|
@@ -101,64 +90,39 @@ task's dedicated session directory. These paths are required to construct a
|
|
|
101
90
|
normal Pi worker and are not persisted into `sandbox.json`. The worker receives
|
|
102
91
|
write access only to its own session and temporary directories.
|
|
103
92
|
|
|
104
|
-
Use `/sandbox` to inspect
|
|
105
|
-
|
|
106
|
-
and task session, and setting the global or trusted-project concurrency limit.
|
|
93
|
+
Use `/sandbox` to inspect or disable the sandbox. Use `/agents` to select a primary
|
|
94
|
+
agent, inspect primary agents, subagents, and tasks, and set subagent limits.
|
|
107
95
|
|
|
108
96
|
## Permission model
|
|
109
97
|
|
|
110
|
-
Agent
|
|
111
|
-
different targets and make decisions at different stages:
|
|
112
|
-
|
|
113
|
-
| Layer | Target | Decision point | Governs |
|
|
114
|
-
| ------------------ | ------------------------------------------------ | -------------------- | ------------------------------------------------------------------- |
|
|
115
|
-
| Agent permission | A primary agent or subagent | Before tool dispatch | Whether that agent may invoke a tool with the declared arguments |
|
|
116
|
-
| Sandbox permission | A sandboxed command and its descendant processes | During execution | The filesystem and network operations the command actually attempts |
|
|
98
|
+
Agent and sandbox permissions apply at different stages:
|
|
117
99
|
|
|
118
|
-
|
|
100
|
+
| Layer | Checked before or during | Controls |
|
|
101
|
+
| ------------------ | ------------------------ | ------------------------------------------ |
|
|
102
|
+
| Agent permission | Before tool dispatch | Which tools an agent may call |
|
|
103
|
+
| Sandbox permission | During process execution | Filesystem and network access for commands |
|
|
119
104
|
|
|
120
105
|
```text
|
|
121
|
-
agent → agent permission
|
|
106
|
+
agent → agent permission → tool → sandbox → OS resource
|
|
122
107
|
```
|
|
123
108
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
dispatched. A sandbox query comes from the command reaching the OS boundary. Where
|
|
127
|
-
the platform supports dynamic queries, such as Linux seccomp traps, this can happen
|
|
128
|
-
after the process has started; the broker holds the operation while the user decides.
|
|
129
|
-
|
|
130
|
-
Neither policy can replace the other:
|
|
131
|
-
|
|
132
|
-
- An agent `allow` permits tool dispatch but cannot widen the sandbox.
|
|
133
|
-
- A sandbox approval permits a concrete OS operation but does not authorize an agent
|
|
134
|
-
to invoke a tool denied by its agent policy.
|
|
135
|
-
- Agent policy describes tool requests known before dispatch. Sandbox policy constrains
|
|
136
|
-
executable behavior, including unexpected or malicious native code.
|
|
137
|
-
- Agent and sandbox approvals are tracked separately and do not imply one another.
|
|
138
|
-
|
|
139
|
-
For example, an agent may be allowed to invoke Bash, while the launched executable is
|
|
140
|
-
still blocked or queried when it attempts `openat("/home/user/.ssh/config")` or
|
|
141
|
-
`connect("127.0.0.1:5432")`.
|
|
109
|
+
Each layer prompts independently. An agent approval allows tool dispatch; sandbox
|
|
110
|
+
restrictions still apply to the resulting process.
|
|
142
111
|
|
|
143
112
|
## Primary agents
|
|
144
113
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
The selection controls the root system prompt and permissions and is restored
|
|
152
|
-
with the session. Press `Ctrl+Shift+A` while Pi is idle to cycle through enabled
|
|
153
|
-
primary agents; switching is blocked while an agent run is active.
|
|
114
|
+
`/agents` lists all configured agents and their modes. Press Enter to activate a
|
|
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.
|
|
117
|
+
|
|
118
|
+
The selected prompt and permissions are stored in the session. Press `Ctrl+Shift+A`
|
|
119
|
+
while Pi is idle to cycle through visible primary agents.
|
|
154
120
|
|
|
155
121
|
## Subagents
|
|
156
122
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
root Pi process supervises RPC, permissions, nesting, persistence, and result
|
|
161
|
-
delivery.
|
|
123
|
+
The `task` tool runs each subagent in a separate `pi --mode rpc` process inside a
|
|
124
|
+
Landstrip sandbox. The root Pi process manages RPC, permissions, nesting, sessions,
|
|
125
|
+
and results.
|
|
162
126
|
|
|
163
127
|
Workers use normal Pi resource discovery and plugin loading, plus the
|
|
164
128
|
`pi-landstrip` worker extension. Requested tools are composed inside the worker,
|
|
@@ -166,7 +130,7 @@ so plugins can add or replace implementations. Each task starts a fresh Pi
|
|
|
166
130
|
process and plugin instances; continuing a `task_id` restores its persisted Pi
|
|
167
131
|
session in a new process.
|
|
168
132
|
|
|
169
|
-
The tool accepts
|
|
133
|
+
The tool accepts these fields:
|
|
170
134
|
|
|
171
135
|
```json
|
|
172
136
|
{
|
|
@@ -179,28 +143,18 @@ The tool accepts the OpenCode task fields:
|
|
|
179
143
|
}
|
|
180
144
|
```
|
|
181
145
|
|
|
182
|
-
Foreground tasks return
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
The **Sessions** tab in `/agents` inspects child transcripts and supports parent and
|
|
186
|
-
sibling navigation. Open a specific task directly with `/agents <id>`. Completed and
|
|
187
|
-
failed task metadata remains available after reload, and persisted sessions can be
|
|
188
|
-
continued with `task_id`.
|
|
146
|
+
Foreground tasks return their result directly. Background tasks return immediately
|
|
147
|
+
and report completion later. The **Tasks** tab in `/agents` shows task status and
|
|
148
|
+
output. Open a task with `/agents <id>`. Continue a saved session with `task_id`.
|
|
189
149
|
|
|
190
150
|
Session switching or shutdown stops live workers. After an unclean restart,
|
|
191
151
|
unfinished work is marked interrupted; completed but undelivered background
|
|
192
152
|
results are delivered when the root session resumes.
|
|
193
153
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
session and its descendants.
|
|
199
|
-
|
|
200
|
-
Subagents fail closed when sandboxing is expected but unavailable, unsupported,
|
|
201
|
-
or missing. An explicit `--no-sandbox` flag or `enabled: false` configuration is
|
|
202
|
-
treated as an intentional opt-out and uses the warned unsandboxed process path.
|
|
203
|
-
Unsupported Pi versions still fail task startup.
|
|
154
|
+
Workers check agent permissions before tool dispatch: `deny` blocks, `ask` prompts in
|
|
155
|
+
the root UI, and `allow` runs the tool. Required sandbox startup failures stop the
|
|
156
|
+
task. With `--no-sandbox` or `enabled: false`, workers run without OS isolation and
|
|
157
|
+
Pi shows a warning.
|
|
204
158
|
|
|
205
159
|
### Platform behavior
|
|
206
160
|
|
|
@@ -236,30 +190,21 @@ first.
|
|
|
236
190
|
|
|
237
191
|
## Configuration
|
|
238
192
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
primary agents and subagents exist and which tools they may dispatch.
|
|
243
|
-
- Sandbox policy belongs in `sandbox.json`. It controls the filesystem and network
|
|
244
|
-
operations attempted by sandboxed commands and worker processes.
|
|
245
|
-
|
|
246
|
-
Rules cannot be copied between these files: the schemas, targets, and enforcement
|
|
247
|
-
stages are different, so an agent rule is neither valid nor equivalent as a sandbox
|
|
248
|
-
rule, or vice versa.
|
|
193
|
+
Agent permissions are configured under `landstrip` in Pi `settings.json`. Sandbox
|
|
194
|
+
permissions are configured in `sandbox.json`. The files use different schemas and
|
|
195
|
+
control different stages.
|
|
249
196
|
|
|
250
197
|
### Agent policy (`settings.json`)
|
|
251
198
|
|
|
252
|
-
Agent
|
|
253
|
-
|
|
254
|
-
are merged over internal defaults. The built-in subagents are `code-reviewer`,
|
|
255
|
-
`explore`, `general`, and the OpenCode-compatible `scout` reconnaissance agent.
|
|
199
|
+
Agent settings are read from `~/.pi/agent/settings.json` and, for trusted projects,
|
|
200
|
+
`.pi/settings.json`. Built-in subagents are `explore`, `general`, and `scout`.
|
|
256
201
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
202
|
+
Pi Markdown agents are loaded from `~/.pi/agent/agents/*.md` and, for trusted
|
|
203
|
+
projects, `.pi/agents/*.md`. Nested directories are supported. Project Markdown
|
|
204
|
+
agents override global Markdown agents. Settings under `landstrip.agent` override
|
|
205
|
+
Markdown and OpenCode agents with the same name.
|
|
260
206
|
|
|
261
|
-
|
|
262
|
-
`sandbox.json`:
|
|
207
|
+
Add agent configuration under `landstrip` in Pi `settings.json`:
|
|
263
208
|
|
|
264
209
|
```json
|
|
265
210
|
{
|
|
@@ -286,30 +231,21 @@ An agent configuration example follows. This belongs in Pi `settings.json`, not
|
|
|
286
231
|
}
|
|
287
232
|
```
|
|
288
233
|
|
|
289
|
-
`landstrip.maxSubagents`
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
`
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
`
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
Primary agents honor configured models and supported Pi thinking-level variants;
|
|
306
|
-
omitting either preserves the current session setting. A model may use the full
|
|
307
|
-
`provider/model` name or a bare model ID when that ID is unique. Selection fails
|
|
308
|
-
without changing the active primary agent when the model is missing, ambiguous, or
|
|
309
|
-
unavailable for authentication. Subagent workers also honor model selection,
|
|
310
|
-
supported Pi thinking-level variants, and step limits. Later matching permission
|
|
311
|
-
rules win. Put provider-specific values under `options`; unknown agent fields are
|
|
312
|
-
rejected.
|
|
234
|
+
`landstrip.maxSubagents` sets the maximum number of concurrent subagents from 0 to 16. The default is 1; zero disables `task`. The `/agents` **Settings** tab provides
|
|
235
|
+
**Maximum subagents (global)** and **Maximum subagents (local)**.
|
|
236
|
+
|
|
237
|
+
`landstrip.permission` applies to every agent. Each
|
|
238
|
+
`landstrip.agent.<name>.permission` map adds agent-specific rules; later matching
|
|
239
|
+
rules win. Agent definitions support `mode`, `prompt`, `model`, `variant`, `steps`,
|
|
240
|
+
`color`, `disable`, and `allow`/`ask`/`deny` permissions. `{file:path}` prompt
|
|
241
|
+
references are resolved from the defining `settings.json`. Provider-specific fields
|
|
242
|
+
belong under `options`.
|
|
243
|
+
|
|
244
|
+
`hidden` removes an agent from `/agents`. A hidden, subagent-capable agent remains
|
|
245
|
+
available to `task`. Agent `mode` controls activation: only `primary` and `all` agents
|
|
246
|
+
can be selected as primary, while only `subagent` and `all` agents can be passed to
|
|
247
|
+
`task`. Missing, ambiguous, or unauthenticated primary-agent models produce an error
|
|
248
|
+
and leave the current agent active.
|
|
313
249
|
|
|
314
250
|
OpenCode integration flags also live in Pi `settings.json` under
|
|
315
251
|
`landstrip.opencode`; both default to `true`:
|
|
@@ -334,25 +270,18 @@ OpenCode integration flags also live in Pi `settings.json` under
|
|
|
334
270
|
`opencode.jsonc`, `.opencode/opencode.json` and `.opencode/opencode.jsonc`, and
|
|
335
271
|
Markdown agents under `.opencode/agent/` and `.opencode/agents/`.
|
|
336
272
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
global OpenCode agents still load when `showGlobalAgents` is enabled.
|
|
343
|
-
|
|
344
|
-
OpenCode JSONC comments and trailing commas are supported. `{file:path}` prompt tokens
|
|
345
|
-
resolve relative to the OpenCode config file or Pi `settings.json` that defines them.
|
|
273
|
+
Set either flag to `false` to disable that OpenCode source. Project OpenCode agents
|
|
274
|
+
override global OpenCode agents. Pi Markdown agents and `landstrip.agent` settings
|
|
275
|
+
take precedence over OpenCode imports. Project sources require a trusted project.
|
|
276
|
+
OpenCode config files support JSONC comments, trailing commas, and `{file:path}`
|
|
277
|
+
prompt references.
|
|
346
278
|
|
|
347
279
|
### Sandbox policy (`sandbox.json`)
|
|
348
280
|
|
|
349
|
-
Sandbox
|
|
350
|
-
`.pi/sandbox.json`.
|
|
351
|
-
commands; it does not grant an agent permission to dispatch a tool. Likewise, an
|
|
352
|
-
agent `allow` cannot grant filesystem or network access through the sandbox.
|
|
281
|
+
Sandbox settings are read from `~/.pi/agent/sandbox.json` and, for trusted projects,
|
|
282
|
+
`.pi/sandbox.json`. They control runtime filesystem and network access.
|
|
353
283
|
|
|
354
|
-
|
|
355
|
-
`settings.json`:
|
|
284
|
+
Configure Windows sandbox fields in `sandbox.json`:
|
|
356
285
|
|
|
357
286
|
```json
|
|
358
287
|
{
|
|
@@ -363,11 +292,9 @@ For example, Windows sandbox fields belong in `sandbox.json`, not Pi
|
|
|
363
292
|
}
|
|
364
293
|
```
|
|
365
294
|
|
|
366
|
-
`appContainerMode`
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
active. Windows implementation selection is automatic and cannot be changed by
|
|
370
|
-
project configuration.
|
|
295
|
+
`appContainerMode` accepts `"lpac"` or `"standard"`. The Pi package defaults to
|
|
296
|
+
standard mode for Git Bash. `allowLoopback` applies only to AppContainer. Landstrip
|
|
297
|
+
selects Windows isolation automatically.
|
|
371
298
|
|
|
372
299
|
#### Windows restricted-user installation
|
|
373
300
|
|
|
@@ -378,39 +305,19 @@ npx @landstrip/landstrip windows install
|
|
|
378
305
|
npx @landstrip/landstrip windows status
|
|
379
306
|
```
|
|
380
307
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
`
|
|
384
|
-
restart Pi. Uninstalling returns Landstrip to AppContainer automatically.
|
|
308
|
+
Installation requests elevation and creates eight restricted-network accounts, two
|
|
309
|
+
unrestricted-network accounts, and proxy ports 60080–60111 by default. Use
|
|
310
|
+
`windows install --help` to change these values. Restart Pi after installation.
|
|
385
311
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
and recovered per-run ACL grants with:
|
|
312
|
+
Before each launch, the extension checks the installation. A failed health check
|
|
313
|
+
stops the task. Restricted-user isolation does not support `windows.allowLoopback`
|
|
314
|
+
or `network.allowLocalBinding`. Remove the accounts, WFP rules, runner, and ACL
|
|
315
|
+
grants with:
|
|
391
316
|
|
|
392
317
|
```powershell
|
|
393
318
|
npx @landstrip/landstrip windows uninstall
|
|
394
319
|
```
|
|
395
320
|
|
|
396
|
-
## Configuration migration
|
|
397
|
-
|
|
398
|
-
`subagents.json` is no longer supported. Move its values into the corresponding
|
|
399
|
-
global or project Pi `settings.json`, then delete the old file:
|
|
400
|
-
|
|
401
|
-
- Move top-level `maxSubagents` to `landstrip.maxSubagents`.
|
|
402
|
-
- Move `subagents.agent` to `landstrip.agent`.
|
|
403
|
-
- Move `subagents.permission` to `landstrip.permission`.
|
|
404
|
-
- Move top-level `opencode` to `landstrip.opencode`.
|
|
405
|
-
- Convert legacy `tools` booleans to explicit `permission` rules.
|
|
406
|
-
- Move legacy top-level Pi `agent`, `permission`, `subagents`, and `maxSubagents`
|
|
407
|
-
settings under `landstrip`.
|
|
408
|
-
- Leave sandbox policy in `~/.pi/agent/sandbox.json` and `.pi/sandbox.json`; those
|
|
409
|
-
files are unchanged.
|
|
410
|
-
|
|
411
|
-
An existing `~/.pi/agent/subagents.json` or trusted-project `.pi/subagents.json`
|
|
412
|
-
produces an actionable migration diagnostic instead of being loaded.
|
|
413
|
-
|
|
414
321
|
## Plugin API
|
|
415
322
|
|
|
416
323
|
Pi extensions can discover the active Landstrip runtime through the versioned
|
package/dist/index.ts
CHANGED
|
@@ -3,10 +3,10 @@ import { spawn as spawn2, spawnSync } from "node:child_process";
|
|
|
3
3
|
import { randomBytes } from "node:crypto";
|
|
4
4
|
import { lookup } from "node:dns/promises";
|
|
5
5
|
import {
|
|
6
|
-
existsSync as
|
|
6
|
+
existsSync as existsSync4,
|
|
7
7
|
mkdirSync as mkdirSync3,
|
|
8
8
|
mkdtempSync as mkdtempSync2,
|
|
9
|
-
readFileSync as
|
|
9
|
+
readFileSync as readFileSync5,
|
|
10
10
|
realpathSync as realpathSync2,
|
|
11
11
|
rmSync as rmSync2,
|
|
12
12
|
writeFileSync as writeFileSync3
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
Socket as NetSocket
|
|
20
20
|
} from "node:net";
|
|
21
21
|
import { homedir as homedir4, tmpdir as tmpdir2 } from "node:os";
|
|
22
|
-
import { basename as
|
|
22
|
+
import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute3, join as join6, parse, resolve as resolve3 } from "node:path";
|
|
23
23
|
import { StringDecoder as StringDecoder2 } from "node:string_decoder";
|
|
24
24
|
import { fileURLToPath as fileURLToPath2, URL } from "node:url";
|
|
25
25
|
import {
|
|
@@ -70,18 +70,18 @@ function encodeLandstripContext(context) {
|
|
|
70
70
|
// subagents.ts
|
|
71
71
|
import { randomUUID } from "node:crypto";
|
|
72
72
|
import {
|
|
73
|
-
existsSync as
|
|
73
|
+
existsSync as existsSync3,
|
|
74
74
|
lstatSync,
|
|
75
75
|
mkdirSync as mkdirSync2,
|
|
76
76
|
mkdtempSync,
|
|
77
|
-
readFileSync as
|
|
77
|
+
readFileSync as readFileSync4,
|
|
78
78
|
readlinkSync,
|
|
79
79
|
realpathSync,
|
|
80
80
|
rmSync,
|
|
81
81
|
writeFileSync as writeFileSync2
|
|
82
82
|
} from "node:fs";
|
|
83
83
|
import { homedir as homedir3, tmpdir } from "node:os";
|
|
84
|
-
import { basename
|
|
84
|
+
import { basename, dirname as dirname3, isAbsolute as isAbsolute2, join as join5, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
|
|
85
85
|
import { fileURLToPath } from "node:url";
|
|
86
86
|
import {
|
|
87
87
|
getAgentDir as getAgentDir3,
|
|
@@ -92,7 +92,6 @@ import { matchesKey, Text, truncateToWidth as truncateToWidth2 } from "@earendil
|
|
|
92
92
|
import { Type } from "typebox";
|
|
93
93
|
|
|
94
94
|
// agents.ts
|
|
95
|
-
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
|
|
96
95
|
import { join as join4 } from "node:path";
|
|
97
96
|
import { minimatch } from "minimatch";
|
|
98
97
|
import { getAgentDir as getAgentDir2 } from "@earendil-works/pi-coding-agent";
|
|
@@ -113,46 +112,20 @@ var BUILT_IN_LANDSTRIP_CONFIG = {
|
|
|
113
112
|
prompt: "Work in build mode. Implement, test, and complete the requested changes."
|
|
114
113
|
},
|
|
115
114
|
plan: {
|
|
116
|
-
description: "Planning agent that asks before commands
|
|
115
|
+
description: "Planning agent that asks before shell commands and file edits",
|
|
117
116
|
mode: "primary",
|
|
118
117
|
color: "warning",
|
|
119
118
|
prompt: "Work in plan mode. Analyze the problem and produce a clear plan before making changes.",
|
|
120
119
|
permission: { edit: "ask", bash: "ask", task: "ask" }
|
|
121
120
|
},
|
|
122
|
-
"code-reviewer": {
|
|
123
|
-
description: "Read-only reviewer for concrete code defects",
|
|
124
|
-
mode: "subagent",
|
|
125
|
-
color: "info",
|
|
126
|
-
prompt: "Review the requested code without modifying files. Inspect correctness, security, regressions, tests, performance, and maintainability. Report only concrete findings ordered by severity with exact file and line references, explain the impact, and recommend a concise fix. State clearly when there are no findings. Git inspection permits only these exact commands: `git --no-optional-locks -c core.fsmonitor=false status --short`, `git --no-pager diff --no-ext-diff --no-textconv`, `git --no-pager diff --cached --no-ext-diff --no-textconv`, `git --no-pager log -20 --oneline`, and `git --no-pager show --stat --no-ext-diff --no-textconv`.",
|
|
127
|
-
permission: {
|
|
128
|
-
"*": "deny",
|
|
129
|
-
read: {
|
|
130
|
-
"*": "allow",
|
|
131
|
-
"**/*.env": "ask",
|
|
132
|
-
"**/*.env.*": "ask",
|
|
133
|
-
"**/*.env.example": "allow"
|
|
134
|
-
},
|
|
135
|
-
glob: "allow",
|
|
136
|
-
grep: "allow",
|
|
137
|
-
list: "allow",
|
|
138
|
-
bash: {
|
|
139
|
-
"*": "deny",
|
|
140
|
-
"git --no-optional-locks -c core.fsmonitor=false status --short": "allow",
|
|
141
|
-
"git --no-pager diff --no-ext-diff --no-textconv": "allow",
|
|
142
|
-
"git --no-pager diff --cached --no-ext-diff --no-textconv": "allow",
|
|
143
|
-
"git --no-pager log -20 --oneline": "allow",
|
|
144
|
-
"git --no-pager show --stat --no-ext-diff --no-textconv": "allow"
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
},
|
|
148
121
|
general: {
|
|
149
|
-
description: "General-purpose agent for
|
|
122
|
+
description: "General-purpose agent for complex tasks",
|
|
150
123
|
mode: "subagent",
|
|
151
124
|
prompt: "Complete the delegated task autonomously. Return one concise result to the parent.",
|
|
152
125
|
permission: { todowrite: "deny" }
|
|
153
126
|
},
|
|
154
127
|
scout: {
|
|
155
|
-
description: "Fast codebase reconnaissance
|
|
128
|
+
description: "Fast codebase reconnaissance",
|
|
156
129
|
mode: "subagent",
|
|
157
130
|
prompt: "Scout the codebase without modifying it. Return concise findings with exact file references, key code, architecture, and a recommended starting point.",
|
|
158
131
|
permission: {
|
|
@@ -169,7 +142,7 @@ var BUILT_IN_LANDSTRIP_CONFIG = {
|
|
|
169
142
|
}
|
|
170
143
|
},
|
|
171
144
|
explore: {
|
|
172
|
-
description: "Fast
|
|
145
|
+
description: "Fast codebase exploration",
|
|
173
146
|
mode: "subagent",
|
|
174
147
|
prompt: "Explore the codebase without modifying it. Report findings with file references. Use bash and network fetch tools when needed for git, PR metadata, or remote docs; do not edit files.",
|
|
175
148
|
permission: {
|
|
@@ -448,39 +421,39 @@ function getPiConfigPaths(cwd, fileName, agentDir = getAgentDir()) {
|
|
|
448
421
|
projectPath: join2(cwd, ".pi", fileName)
|
|
449
422
|
};
|
|
450
423
|
}
|
|
451
|
-
function
|
|
452
|
-
if (!existsSync(path))
|
|
453
|
-
return;
|
|
454
|
-
const settingsPath = join2(dirname(path), "settings.json");
|
|
455
|
-
throw new Error(`${path} is no longer supported; move its values to landstrip.maxSubagents, landstrip.agent, landstrip.permission, and landstrip.opencode in ${settingsPath}`);
|
|
456
|
-
}
|
|
457
|
-
async function setMaxSubagentsConfigForScope(cwd, maxSubagents, scope, agentDir = getAgentDir()) {
|
|
458
|
-
if (!Number.isInteger(maxSubagents) || maxSubagents < 0 || maxSubagents > MAX_SUBAGENTS) {
|
|
459
|
-
throw new Error(`maxSubagents must be an integer from 0 to ${MAX_SUBAGENTS}`);
|
|
460
|
-
}
|
|
424
|
+
async function writeMaxSubagentsConfigForScope(cwd, maxSubagents, scope, agentDir) {
|
|
461
425
|
const paths = getPiConfigPaths(cwd, "settings.json", agentDir);
|
|
462
426
|
const path = scope === "global" ? paths.globalPath : paths.projectPath;
|
|
463
|
-
const legacyPaths = getPiConfigPaths(cwd, "subagents.json", agentDir);
|
|
464
|
-
assertNoSubagentsConfig(scope === "global" ? legacyPaths.globalPath : legacyPaths.projectPath);
|
|
465
427
|
await withFileMutationQueue(path, async () => {
|
|
466
428
|
const settings = existsSync(path) ? readJsonObject(path) : {};
|
|
467
429
|
if (settings.landstrip !== undefined && !isRecord2(settings.landstrip)) {
|
|
468
430
|
throw new Error(`${path}: landstrip must be a JSON object`);
|
|
469
431
|
}
|
|
470
432
|
const landstrip = isRecord2(settings.landstrip) ? { ...settings.landstrip } : {};
|
|
471
|
-
|
|
472
|
-
|
|
433
|
+
if (maxSubagents === undefined)
|
|
434
|
+
delete landstrip.maxSubagents;
|
|
435
|
+
else
|
|
436
|
+
landstrip.maxSubagents = maxSubagents;
|
|
437
|
+
if (Object.keys(landstrip).length === 0)
|
|
438
|
+
delete settings.landstrip;
|
|
439
|
+
else
|
|
440
|
+
settings.landstrip = landstrip;
|
|
473
441
|
mkdirSync(dirname(path), { recursive: true });
|
|
474
442
|
writeFileSync(path, `${JSON.stringify(settings, null, 2)}
|
|
475
443
|
`, { mode: 384 });
|
|
476
444
|
});
|
|
477
445
|
}
|
|
446
|
+
async function setMaxSubagentsConfigForScope(cwd, maxSubagents, scope, agentDir = getAgentDir()) {
|
|
447
|
+
if (!Number.isInteger(maxSubagents) || maxSubagents < 0 || maxSubagents > MAX_SUBAGENTS) {
|
|
448
|
+
throw new Error(`maxSubagents must be an integer from 0 to ${MAX_SUBAGENTS}`);
|
|
449
|
+
}
|
|
450
|
+
await writeMaxSubagentsConfigForScope(cwd, maxSubagents, scope, agentDir);
|
|
451
|
+
}
|
|
452
|
+
async function clearMaxSubagentsConfigForScope(cwd, scope, agentDir = getAgentDir()) {
|
|
453
|
+
await writeMaxSubagentsConfigForScope(cwd, undefined, scope, agentDir);
|
|
454
|
+
}
|
|
478
455
|
function loadLandstripConfig(cwd, includeProject = true, agentDir = getAgentDir()) {
|
|
479
456
|
const settingsPaths = getPiConfigPaths(cwd, "settings.json", agentDir);
|
|
480
|
-
const legacyPaths = getPiConfigPaths(cwd, "subagents.json", agentDir);
|
|
481
|
-
assertNoSubagentsConfig(legacyPaths.globalPath);
|
|
482
|
-
if (includeProject)
|
|
483
|
-
assertNoSubagentsConfig(legacyPaths.projectPath);
|
|
484
457
|
const builtInConfig = BUILT_IN_LANDSTRIP_CONFIG;
|
|
485
458
|
const globalConfig = readLandstripSettings(settingsPaths.globalPath);
|
|
486
459
|
const projectConfig = includeProject ? readLandstripSettings(settingsPaths.projectPath) : undefined;
|
|
@@ -501,11 +474,22 @@ function loadLandstripConfig(cwd, includeProject = true, agentDir = getAgentDir(
|
|
|
501
474
|
const opencode = { ...DEFAULT_OPENCODE, ...config.opencode };
|
|
502
475
|
return { ...config, maxSubagents, agent: config.agent, opencode, agentSources };
|
|
503
476
|
}
|
|
477
|
+
function loadMaxSubagentsSettings(cwd, includeProject = true, agentDir = getAgentDir()) {
|
|
478
|
+
const global = loadLandstripConfig(cwd, false, agentDir).maxSubagents;
|
|
479
|
+
if (!includeProject)
|
|
480
|
+
return { global };
|
|
481
|
+
const paths = getPiConfigPaths(cwd, "settings.json", agentDir);
|
|
482
|
+
const project = readLandstripSettings(paths.projectPath).maxSubagents;
|
|
483
|
+
if (project !== undefined && (!Number.isInteger(project) || project < 0 || project > MAX_SUBAGENTS)) {
|
|
484
|
+
throw new Error(`maxSubagents must be an integer from 0 to ${MAX_SUBAGENTS}`);
|
|
485
|
+
}
|
|
486
|
+
return { global, project };
|
|
487
|
+
}
|
|
504
488
|
|
|
505
489
|
// opencode-agents.ts
|
|
506
490
|
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3, statSync } from "node:fs";
|
|
507
491
|
import { homedir as homedir2 } from "node:os";
|
|
508
|
-
import {
|
|
492
|
+
import { dirname as dirname2, extname, join as join3, relative, sep } from "node:path";
|
|
509
493
|
import { parse as parseYaml } from "yaml";
|
|
510
494
|
import { parse as parseJsonc, printParseErrorCode } from "jsonc-parser";
|
|
511
495
|
var AGENT_DIR_PREFIXES = ["agent/", "agents/"];
|
|
@@ -552,9 +536,8 @@ function agentNameFromPath(dir, filePath) {
|
|
|
552
536
|
return extension2.length > 0 ? candidate.slice(0, -extension2.length) : candidate;
|
|
553
537
|
}
|
|
554
538
|
}
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
return extension.length > 0 ? base.slice(0, -extension.length) : base;
|
|
539
|
+
const extension = extname(relativePath);
|
|
540
|
+
return extension.length > 0 ? relativePath.slice(0, -extension.length) : relativePath;
|
|
558
541
|
}
|
|
559
542
|
function parseFrontmatter(content) {
|
|
560
543
|
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n([\s\S]*))?$/);
|
|
@@ -689,26 +672,36 @@ function loadConfigAgentsFromDir(dir, source, agents, diagnostics) {
|
|
|
689
672
|
loadAgentsFromConfigFile(join3(dir, file), source, agents, diagnostics);
|
|
690
673
|
}
|
|
691
674
|
}
|
|
692
|
-
function
|
|
693
|
-
for (const
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
diagnostics.push(`${filePath}: agent name is empty`);
|
|
702
|
-
continue;
|
|
703
|
-
}
|
|
704
|
-
const raw = normalizeOpenCodeAgentRaw(data, body.trim());
|
|
705
|
-
agents.set(name, { name, source, path: filePath, raw });
|
|
706
|
-
} catch (error) {
|
|
707
|
-
diagnostics.push(`${filePath}: ${formatError(error)}`);
|
|
675
|
+
function loadAgentsFromRoot(root, nameRoot, source, agents, diagnostics) {
|
|
676
|
+
for (const filePath of listMarkdownFiles(root)) {
|
|
677
|
+
try {
|
|
678
|
+
const content = readFileSync3(filePath, "utf8");
|
|
679
|
+
const { data, body } = parseFrontmatter(content);
|
|
680
|
+
const name = agentNameFromPath(nameRoot, filePath);
|
|
681
|
+
if (!name) {
|
|
682
|
+
diagnostics.push(`${filePath}: agent name is empty`);
|
|
683
|
+
continue;
|
|
708
684
|
}
|
|
685
|
+
const raw = normalizeOpenCodeAgentRaw(data, body.trim());
|
|
686
|
+
agents.set(name, { name, source, path: filePath, raw });
|
|
687
|
+
} catch (error) {
|
|
688
|
+
diagnostics.push(`${filePath}: ${formatError(error)}`);
|
|
709
689
|
}
|
|
710
690
|
}
|
|
711
691
|
}
|
|
692
|
+
function loadAgentsFromDir(dir, source, agents, diagnostics) {
|
|
693
|
+
for (const subdir of ["agent", "agents"]) {
|
|
694
|
+
loadAgentsFromRoot(join3(dir, subdir), dir, source, agents, diagnostics);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
function loadPiMarkdownAgents(options) {
|
|
698
|
+
const agents = new Map;
|
|
699
|
+
const diagnostics = [];
|
|
700
|
+
for (const directory of options.directories) {
|
|
701
|
+
loadAgentsFromRoot(directory.path, directory.path, directory.source, agents, diagnostics);
|
|
702
|
+
}
|
|
703
|
+
return { agents, diagnostics };
|
|
704
|
+
}
|
|
712
705
|
function loadOpenCodeAgents(options) {
|
|
713
706
|
const agents = new Map;
|
|
714
707
|
const diagnostics = [];
|
|
@@ -773,24 +766,6 @@ function normalizePermissions(value) {
|
|
|
773
766
|
throw new Error("permission must be allow, ask, deny, or a map");
|
|
774
767
|
return Object.entries(value).flatMap(([permission, rules]) => permissionEntries(permission, rules));
|
|
775
768
|
}
|
|
776
|
-
function legacyConfigWarnings(piAgentDir) {
|
|
777
|
-
const path = join4(piAgentDir, "settings.json");
|
|
778
|
-
if (!existsSync3(path))
|
|
779
|
-
return [];
|
|
780
|
-
try {
|
|
781
|
-
const value = JSON.parse(readFileSync4(path, "utf8"));
|
|
782
|
-
if (!isRecord2(value))
|
|
783
|
-
return [];
|
|
784
|
-
const fields = ["agent", "permission", "subagents", "maxSubagents"].filter((field) => value[field] !== undefined);
|
|
785
|
-
if (fields.length === 0)
|
|
786
|
-
return [];
|
|
787
|
-
return [
|
|
788
|
-
`${path}: legacy ${fields.join(", ")} configuration is ignored; move it under landstrip`
|
|
789
|
-
];
|
|
790
|
-
} catch {
|
|
791
|
-
return [];
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
769
|
function normalizeAgent(name, raw, source) {
|
|
795
770
|
for (const field of ["description", "prompt", "model", "variant", "color"]) {
|
|
796
771
|
if (raw[field] !== undefined && typeof raw[field] !== "string") {
|
|
@@ -846,10 +821,6 @@ function normalizeAgent(name, raw, source) {
|
|
|
846
821
|
};
|
|
847
822
|
}
|
|
848
823
|
function loadAgentCatalog(cwd, piAgentDir = getAgentDir2(), includeProject = true) {
|
|
849
|
-
const warnings = [
|
|
850
|
-
...legacyConfigWarnings(piAgentDir),
|
|
851
|
-
...includeProject ? legacyConfigWarnings(join4(cwd, ".pi")) : []
|
|
852
|
-
];
|
|
853
824
|
const diagnostics = [];
|
|
854
825
|
let maxSubagents = 0;
|
|
855
826
|
let configuredAgents = {};
|
|
@@ -884,6 +855,22 @@ function loadAgentCatalog(cwd, piAgentDir = getAgentDir2(), includeProject = tru
|
|
|
884
855
|
diagnostics.push(`${imported.path}: ${formatError(error)}`);
|
|
885
856
|
}
|
|
886
857
|
}
|
|
858
|
+
const piMarkdown = loadPiMarkdownAgents({
|
|
859
|
+
directories: [
|
|
860
|
+
{ path: join4(piAgentDir, "agents"), source: "global" },
|
|
861
|
+
...includeProject ? [{ path: join4(cwd, ".pi", "agents"), source: "local" }] : []
|
|
862
|
+
]
|
|
863
|
+
});
|
|
864
|
+
diagnostics.push(...piMarkdown.diagnostics);
|
|
865
|
+
for (const imported of piMarkdown.agents.values()) {
|
|
866
|
+
try {
|
|
867
|
+
const agent = normalizeAgent(imported.name, imported.raw, imported.source);
|
|
868
|
+
if (agent)
|
|
869
|
+
normalized.set(imported.name, agent);
|
|
870
|
+
} catch (error) {
|
|
871
|
+
diagnostics.push(`${imported.path}: ${formatError(error)}`);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
887
874
|
for (const name of Object.keys(configuredAgents).sort()) {
|
|
888
875
|
const value = configuredAgents[name];
|
|
889
876
|
if (!isRecord2(value)) {
|
|
@@ -906,7 +893,7 @@ function loadAgentCatalog(cwd, piAgentDir = getAgentDir2(), includeProject = tru
|
|
|
906
893
|
} catch (error) {
|
|
907
894
|
diagnostics.push(formatError(error));
|
|
908
895
|
}
|
|
909
|
-
return { agents: normalized, permissions, diagnostics,
|
|
896
|
+
return { agents: normalized, permissions, diagnostics, maxSubagents };
|
|
910
897
|
}
|
|
911
898
|
function permissionDecision(rules, permission, resource = "*") {
|
|
912
899
|
let decision = "ask";
|
|
@@ -1407,7 +1394,7 @@ function resolvePiPackage() {
|
|
|
1407
1394
|
let dir2 = dirname3(entry);
|
|
1408
1395
|
for (;; ) {
|
|
1409
1396
|
const pkgPath = join5(dir2, "package.json");
|
|
1410
|
-
if (
|
|
1397
|
+
if (existsSync3(pkgPath)) {
|
|
1411
1398
|
const pkg = readPiPackage(pkgPath);
|
|
1412
1399
|
if (pkg)
|
|
1413
1400
|
return pkg;
|
|
@@ -1421,7 +1408,7 @@ function resolvePiPackage() {
|
|
|
1421
1408
|
let dir = packageDir;
|
|
1422
1409
|
for (;; ) {
|
|
1423
1410
|
const pkgPath = join5(dir, "node_modules", "@earendil-works", "pi-coding-agent", "package.json");
|
|
1424
|
-
if (
|
|
1411
|
+
if (existsSync3(pkgPath)) {
|
|
1425
1412
|
const pkg = readPiPackage(pkgPath);
|
|
1426
1413
|
if (pkg)
|
|
1427
1414
|
return pkg;
|
|
@@ -1434,7 +1421,7 @@ function resolvePiPackage() {
|
|
|
1434
1421
|
}
|
|
1435
1422
|
function readPiPackage(pkgPath) {
|
|
1436
1423
|
try {
|
|
1437
|
-
const value = JSON.parse(
|
|
1424
|
+
const value = JSON.parse(readFileSync4(pkgPath, "utf8"));
|
|
1438
1425
|
if (!isRecord2(value) || value.name !== "@earendil-works/pi-coding-agent" || typeof value.version !== "string") {
|
|
1439
1426
|
return;
|
|
1440
1427
|
}
|
|
@@ -1445,7 +1432,7 @@ function readPiPackage(pkgPath) {
|
|
|
1445
1432
|
if (!binFile)
|
|
1446
1433
|
return;
|
|
1447
1434
|
const cliEntry = join5(dirname3(pkgPath), binFile);
|
|
1448
|
-
if (!
|
|
1435
|
+
if (!existsSync3(cliEntry))
|
|
1449
1436
|
return;
|
|
1450
1437
|
cachedPiPackage = { cliEntry, version: parts.slice(0, 3) };
|
|
1451
1438
|
return cachedPiPackage;
|
|
@@ -1519,7 +1506,7 @@ function messageContentText(message) {
|
|
|
1519
1506
|
`);
|
|
1520
1507
|
}
|
|
1521
1508
|
function taskTranscript(task) {
|
|
1522
|
-
if (!task.sessionFile || !
|
|
1509
|
+
if (!task.sessionFile || !existsSync3(task.sessionFile)) {
|
|
1523
1510
|
const fallback = task.error ?? task.output ?? "Child session is not available yet.";
|
|
1524
1511
|
return fallback.split(`
|
|
1525
1512
|
`);
|
|
@@ -1764,7 +1751,7 @@ function canonicalPermissionPath(path, seen = new Set) {
|
|
|
1764
1751
|
const parent = dirname3(existing);
|
|
1765
1752
|
if (parent === existing)
|
|
1766
1753
|
return path;
|
|
1767
|
-
missing.unshift(
|
|
1754
|
+
missing.unshift(basename(existing));
|
|
1768
1755
|
existing = parent;
|
|
1769
1756
|
}
|
|
1770
1757
|
}
|
|
@@ -2014,26 +2001,31 @@ ${this.primaryAgent.prompt}` };
|
|
|
2014
2001
|
return;
|
|
2015
2002
|
}
|
|
2016
2003
|
const catalog = this.getAgentCatalog(ctx);
|
|
2017
|
-
for (const warning of catalog.warnings)
|
|
2018
|
-
ctx.ui.notify(warning, "warning");
|
|
2019
2004
|
for (const diagnostic of catalog.diagnostics)
|
|
2020
2005
|
ctx.ui.notify(diagnostic, "warning");
|
|
2021
|
-
const
|
|
2006
|
+
const visibleAgents = availableAgents(catalog);
|
|
2007
|
+
const primaryAgents = visibleAgents.filter((agent) => agentSupportsMode(agent, "primary"));
|
|
2008
|
+
const subagents = visibleAgents.filter((agent) => agentSupportsMode(agent, "subagent"));
|
|
2022
2009
|
const tasks = [...this.tasks.values()];
|
|
2023
2010
|
const requested = args.trim();
|
|
2024
2011
|
const projectTrusted = isProjectTrusted(ctx);
|
|
2025
|
-
let tab = requested ? "
|
|
2026
|
-
let
|
|
2012
|
+
let tab = requested ? "tasks" : "primary";
|
|
2013
|
+
let selectedPrimaryAgent = Math.max(0, primaryAgents.findIndex((agent) => agent.name === this.primaryAgent?.name));
|
|
2014
|
+
let selectedSubagent = 0;
|
|
2027
2015
|
let selectedTask = requested ? tasks.findIndex((task) => task.id === requested || task.id.startsWith(requested)) : 0;
|
|
2028
2016
|
if (selectedTask < 0) {
|
|
2029
2017
|
ctx.ui.notify(`Unknown task session: ${requested}`, "error");
|
|
2030
2018
|
return;
|
|
2031
2019
|
}
|
|
2032
2020
|
let selectedSetting = 0;
|
|
2033
|
-
let
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2021
|
+
let maxSubagentsSettings = loadMaxSubagentsSettings(ctx.cwd, projectTrusted);
|
|
2022
|
+
const selectedMaxSubagents = () => selectedSetting === 0 ? maxSubagentsSettings.global : maxSubagentsSettings.project ?? maxSubagentsSettings.global;
|
|
2023
|
+
const updateSelectedMaxSubagents = (value) => {
|
|
2024
|
+
maxSubagentsSettings = selectedSetting === 0 ? { ...maxSubagentsSettings, global: value } : { ...maxSubagentsSettings, project: value };
|
|
2025
|
+
dirtySettings.add(selectedSetting);
|
|
2026
|
+
};
|
|
2027
|
+
const dirtySettings = new Set;
|
|
2028
|
+
let saving = false;
|
|
2037
2029
|
let editing = false;
|
|
2038
2030
|
let detail = requested.length > 0;
|
|
2039
2031
|
let scroll = 0;
|
|
@@ -2046,8 +2038,10 @@ ${this.primaryAgent.prompt}` };
|
|
|
2046
2038
|
boxBottom(theme, width)
|
|
2047
2039
|
];
|
|
2048
2040
|
const tabLabel = (label, active) => active ? theme.fg("accent", theme.bold(`[${label}]`)) : theme.fg("muted", label);
|
|
2049
|
-
const tabs = `${tabLabel("
|
|
2050
|
-
if (tab === "
|
|
2041
|
+
const tabs = `${tabLabel("Primary", tab === "primary")} ${tabLabel("Subagent", tab === "subagent")} ${tabLabel("Tasks", tab === "tasks")} ${tabLabel("Settings", tab === "settings")}`;
|
|
2042
|
+
if (tab === "primary" || tab === "subagent") {
|
|
2043
|
+
const agents = tab === "primary" ? primaryAgents : subagents;
|
|
2044
|
+
const selectedAgent = tab === "primary" ? selectedPrimaryAgent : selectedSubagent;
|
|
2051
2045
|
const start = Math.max(0, Math.min(selectedAgent - 3, agents.length - 7));
|
|
2052
2046
|
const shown2 = agents.slice(start, start + 7);
|
|
2053
2047
|
const lines2 = [tabs, ""];
|
|
@@ -2055,13 +2049,14 @@ ${this.primaryAgent.prompt}` };
|
|
|
2055
2049
|
const index = start + offset;
|
|
2056
2050
|
const selected = index === selectedAgent;
|
|
2057
2051
|
const cursor = selected ? theme.fg("accent", "›") : " ";
|
|
2058
|
-
const active = agent2.name === this.primaryAgent?.name ? theme.fg("success", "●") : theme.fg("dim", "○");
|
|
2052
|
+
const active = tab === "primary" && agent2.name === this.primaryAgent?.name ? theme.fg("success", "●") : theme.fg("dim", "○");
|
|
2059
2053
|
const name = colorizeAgentText(agent2.color, `@${agent2.name}`, (color, text) => theme.fg(color, text));
|
|
2060
2054
|
const metadata = theme.fg("dim", `${agent2.mode} · ${agent2.source} · ${agent2.model ?? "current model"}`);
|
|
2061
2055
|
lines2.push(`${cursor} ${active} ${name} ${metadata}`);
|
|
2062
2056
|
}
|
|
2063
|
-
if (agents.length === 0)
|
|
2064
|
-
lines2.push(theme.fg("muted",
|
|
2057
|
+
if (agents.length === 0) {
|
|
2058
|
+
lines2.push(theme.fg("muted", ` No ${tab} agents configured.`));
|
|
2059
|
+
}
|
|
2065
2060
|
const agent = agents[selectedAgent];
|
|
2066
2061
|
if (agent) {
|
|
2067
2062
|
lines2.push("", theme.fg("text", agent.description ?? "No description"));
|
|
@@ -2094,28 +2089,38 @@ ${this.primaryAgent.prompt}` };
|
|
|
2094
2089
|
lines2.push(` ${theme.fg("error", diagnostic)}`);
|
|
2095
2090
|
}
|
|
2096
2091
|
}
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
}
|
|
2100
|
-
const canActivate = agent !== undefined && agentSupportsMode(agent, "primary");
|
|
2101
|
-
lines2.push("", theme.fg("dim", `↑↓ select${canActivate ? " enter activate" : ""} tab/→ sessions esc close`));
|
|
2092
|
+
const canActivate = tab === "primary" && agent !== undefined;
|
|
2093
|
+
lines2.push("", theme.fg("dim", `↑↓ select${canActivate ? " enter activate" : ""} tab/→ next esc close`));
|
|
2102
2094
|
return box(lines2);
|
|
2103
2095
|
}
|
|
2104
2096
|
if (tab === "settings") {
|
|
2105
|
-
const
|
|
2097
|
+
const rows = [
|
|
2098
|
+
{
|
|
2099
|
+
label: "Maximum subagents (global)",
|
|
2100
|
+
value: maxSubagentsSettings.global,
|
|
2101
|
+
unavailable: false,
|
|
2102
|
+
inherited: false
|
|
2103
|
+
},
|
|
2104
|
+
{
|
|
2105
|
+
label: "Maximum subagents (local)",
|
|
2106
|
+
value: maxSubagentsSettings.project ?? maxSubagentsSettings.global,
|
|
2107
|
+
unavailable: !projectTrusted,
|
|
2108
|
+
inherited: maxSubagentsSettings.project === undefined
|
|
2109
|
+
}
|
|
2110
|
+
];
|
|
2106
2111
|
const lines2 = [tabs, ""];
|
|
2107
|
-
for (const [index,
|
|
2112
|
+
for (const [index, row] of rows.entries()) {
|
|
2108
2113
|
const selected = index === selectedSetting;
|
|
2109
|
-
const unavailable = scope === "Project" && !projectTrusted;
|
|
2110
2114
|
const cursor = selected ? theme.fg("accent", "›") : " ";
|
|
2111
|
-
const
|
|
2112
|
-
const
|
|
2115
|
+
const text = row.inherited ? `[ inherited: ${row.value} ]` : `[ ${row.value} ]`;
|
|
2116
|
+
const value = selected ? theme.fg("accent", text) : theme.fg(row.unavailable ? "muted" : "text", text);
|
|
2117
|
+
const label = row.unavailable ? theme.fg("muted", `${row.label} (project not trusted)`) : theme.fg("text", row.label);
|
|
2113
2118
|
lines2.push(`${cursor} ${value} ${label}`);
|
|
2114
2119
|
}
|
|
2115
|
-
if (
|
|
2116
|
-
lines2.push(` ${theme.fg("dim", "
|
|
2120
|
+
if (selectedMaxSubagents() === 0) {
|
|
2121
|
+
lines2.push(` ${theme.fg("dim", "Subagents disabled")}`);
|
|
2117
2122
|
}
|
|
2118
|
-
lines2.push("", theme.fg("dim", "↑↓
|
|
2123
|
+
lines2.push("", theme.fg("dim", saving ? "Saving…" : "↑↓ setting 0-9/+/- change i use global enter save tab primary esc close"));
|
|
2119
2124
|
return box(lines2);
|
|
2120
2125
|
}
|
|
2121
2126
|
if (!detail) {
|
|
@@ -2170,26 +2175,36 @@ ${this.primaryAgent.prompt}` };
|
|
|
2170
2175
|
},
|
|
2171
2176
|
handleInput: (data) => {
|
|
2172
2177
|
if (matchesKey(data, "tab")) {
|
|
2173
|
-
const tabs = ["
|
|
2174
|
-
tab = tabs[(tabs.indexOf(tab) + 1) % tabs.length] ?? "
|
|
2178
|
+
const tabs = ["primary", "subagent", "tasks", "settings"];
|
|
2179
|
+
tab = tabs[(tabs.indexOf(tab) + 1) % tabs.length] ?? "primary";
|
|
2175
2180
|
detail = false;
|
|
2176
2181
|
editing = false;
|
|
2177
2182
|
scroll = 0;
|
|
2178
2183
|
tui.requestRender();
|
|
2179
2184
|
return;
|
|
2180
2185
|
}
|
|
2181
|
-
if (tab === "
|
|
2186
|
+
if (tab === "primary" || tab === "subagent") {
|
|
2187
|
+
const agents = tab === "primary" ? primaryAgents : subagents;
|
|
2188
|
+
const selectedAgent = tab === "primary" ? selectedPrimaryAgent : selectedSubagent;
|
|
2189
|
+
const setSelectedAgent = (index) => {
|
|
2190
|
+
if (tab === "primary")
|
|
2191
|
+
selectedPrimaryAgent = index;
|
|
2192
|
+
else
|
|
2193
|
+
selectedSubagent = index;
|
|
2194
|
+
};
|
|
2182
2195
|
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c"))
|
|
2183
2196
|
done();
|
|
2184
2197
|
else if (matchesKey(data, "up"))
|
|
2185
|
-
|
|
2198
|
+
setSelectedAgent(Math.max(0, selectedAgent - 1));
|
|
2186
2199
|
else if (matchesKey(data, "down") && agents.length > 0) {
|
|
2187
|
-
|
|
2200
|
+
setSelectedAgent(Math.min(agents.length - 1, selectedAgent + 1));
|
|
2201
|
+
} else if (matchesKey(data, "left") && tab === "subagent") {
|
|
2202
|
+
tab = "primary";
|
|
2188
2203
|
} else if (matchesKey(data, "right")) {
|
|
2189
|
-
tab = "
|
|
2190
|
-
} else if (matchesKey(data, "return")) {
|
|
2204
|
+
tab = tab === "primary" ? "subagent" : "tasks";
|
|
2205
|
+
} else if (matchesKey(data, "return") && tab === "primary") {
|
|
2191
2206
|
const agent = agents[selectedAgent];
|
|
2192
|
-
if (!agent
|
|
2207
|
+
if (!agent)
|
|
2193
2208
|
return;
|
|
2194
2209
|
this.selectPrimaryAgent(agent.name, ctx).then(() => tui.requestRender()).catch((error) => ctx.ui.notify(`Could not select primary agent: ${formatError(error)}`, "error"));
|
|
2195
2210
|
} else
|
|
@@ -2198,6 +2213,8 @@ ${this.primaryAgent.prompt}` };
|
|
|
2198
2213
|
return;
|
|
2199
2214
|
}
|
|
2200
2215
|
if (tab === "settings") {
|
|
2216
|
+
if (saving)
|
|
2217
|
+
return;
|
|
2201
2218
|
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
|
|
2202
2219
|
done();
|
|
2203
2220
|
return;
|
|
@@ -2209,29 +2226,46 @@ ${this.primaryAgent.prompt}` };
|
|
|
2209
2226
|
selectedSetting = Math.min(1, selectedSetting + 1);
|
|
2210
2227
|
editing = false;
|
|
2211
2228
|
} else if (/^[0-9]$/.test(data)) {
|
|
2212
|
-
const value = Number(editing ? `${
|
|
2229
|
+
const value = Number(editing ? `${selectedMaxSubagents()}${data}` : data);
|
|
2213
2230
|
if (value <= MAX_SUBAGENTS) {
|
|
2214
|
-
|
|
2231
|
+
updateSelectedMaxSubagents(value);
|
|
2215
2232
|
editing = true;
|
|
2216
2233
|
}
|
|
2217
2234
|
} else if (data === "+" || data === "-") {
|
|
2218
|
-
|
|
2235
|
+
updateSelectedMaxSubagents(Math.min(MAX_SUBAGENTS, Math.max(0, selectedMaxSubagents() + (data === "+" ? 1 : -1))));
|
|
2236
|
+
editing = false;
|
|
2237
|
+
} else if (data === "i" && selectedSetting === 1) {
|
|
2238
|
+
maxSubagentsSettings = { ...maxSubagentsSettings, project: undefined };
|
|
2239
|
+
dirtySettings.add(selectedSetting);
|
|
2219
2240
|
editing = false;
|
|
2220
2241
|
} else if (matchesKey(data, "return")) {
|
|
2221
|
-
const
|
|
2242
|
+
const setting = selectedSetting;
|
|
2243
|
+
const scope = setting === 0 ? "global" : "project";
|
|
2222
2244
|
if (scope === "project" && !projectTrusted) {
|
|
2223
|
-
ctx.ui.notify("
|
|
2245
|
+
ctx.ui.notify("Local settings require a trusted project", "warning");
|
|
2224
2246
|
return;
|
|
2225
2247
|
}
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2248
|
+
if (!dirtySettings.has(setting))
|
|
2249
|
+
return;
|
|
2250
|
+
const pendingSettings = maxSubagentsSettings;
|
|
2251
|
+
const inherited = scope === "project" && pendingSettings.project === undefined;
|
|
2252
|
+
const maxSubagents = selectedMaxSubagents();
|
|
2253
|
+
saving = true;
|
|
2254
|
+
const save = inherited ? clearMaxSubagentsConfigForScope(ctx.cwd, scope) : setMaxSubagentsConfigForScope(ctx.cwd, maxSubagents, scope);
|
|
2255
|
+
save.then(() => {
|
|
2256
|
+
dirtySettings.delete(setting);
|
|
2257
|
+
const loaded = loadMaxSubagentsSettings(ctx.cwd, projectTrusted);
|
|
2258
|
+
this.setMaxSubagents(loaded.project ?? loaded.global);
|
|
2259
|
+
maxSubagentsSettings = {
|
|
2260
|
+
global: dirtySettings.has(0) ? pendingSettings.global : loaded.global,
|
|
2261
|
+
project: dirtySettings.has(1) ? pendingSettings.project : loaded.project
|
|
2262
|
+
};
|
|
2231
2263
|
editing = false;
|
|
2232
|
-
ctx.ui.notify(
|
|
2264
|
+
ctx.ui.notify(inherited ? "Maximum subagents (local) now uses the global value" : `Maximum subagents (${scope === "project" ? "local" : "global"}) set to ${maxSubagents}`, "info");
|
|
2265
|
+
}).catch((error) => ctx.ui.notify(`Could not save setting: ${formatError(error)}`, "error")).finally(() => {
|
|
2266
|
+
saving = false;
|
|
2233
2267
|
tui.requestRender();
|
|
2234
|
-
})
|
|
2268
|
+
});
|
|
2235
2269
|
return;
|
|
2236
2270
|
} else
|
|
2237
2271
|
return;
|
|
@@ -2242,7 +2276,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
2242
2276
|
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c"))
|
|
2243
2277
|
done();
|
|
2244
2278
|
else if (matchesKey(data, "left"))
|
|
2245
|
-
tab = "
|
|
2279
|
+
tab = "subagent";
|
|
2246
2280
|
else if (matchesKey(data, "right"))
|
|
2247
2281
|
tab = "settings";
|
|
2248
2282
|
else if (matchesKey(data, "up"))
|
|
@@ -2293,8 +2327,6 @@ ${this.primaryAgent.prompt}` };
|
|
|
2293
2327
|
}
|
|
2294
2328
|
async selectPrimaryAgent(name, ctx) {
|
|
2295
2329
|
const catalog = this.getAgentCatalog(ctx);
|
|
2296
|
-
for (const warning of catalog.warnings)
|
|
2297
|
-
ctx.ui.notify(warning, "warning");
|
|
2298
2330
|
for (const diagnostic of catalog.diagnostics)
|
|
2299
2331
|
ctx.ui.notify(diagnostic, "warning");
|
|
2300
2332
|
if (catalog.diagnostics.length > 0)
|
|
@@ -2316,8 +2348,6 @@ ${this.primaryAgent.prompt}` };
|
|
|
2316
2348
|
return;
|
|
2317
2349
|
}
|
|
2318
2350
|
const catalog = this.getAgentCatalog(ctx);
|
|
2319
|
-
for (const warning of catalog.warnings)
|
|
2320
|
-
ctx.ui.notify(warning, "warning");
|
|
2321
2351
|
for (const diagnostic of catalog.diagnostics)
|
|
2322
2352
|
ctx.ui.notify(diagnostic, "warning");
|
|
2323
2353
|
if (catalog.diagnostics.length > 0)
|
|
@@ -2428,8 +2458,6 @@ ${theme.fg("dim", `↳ /agents ${details.taskId.slice(0, 8)} to inspect`)}`;
|
|
|
2428
2458
|
if (!input.prompt.trim())
|
|
2429
2459
|
throw new Error("Task prompt cannot be empty");
|
|
2430
2460
|
const catalog = this.loadCatalog(ctx.cwd, getAgentDir3(), isProjectTrusted(ctx));
|
|
2431
|
-
for (const warning of catalog.warnings)
|
|
2432
|
-
ctx.ui.notify(warning, "warning");
|
|
2433
2461
|
for (const diagnostic of catalog.diagnostics)
|
|
2434
2462
|
ctx.ui.notify(diagnostic, "warning");
|
|
2435
2463
|
if (catalog.diagnostics.length > 0) {
|
|
@@ -2467,7 +2495,7 @@ ${catalog.diagnostics.join(`
|
|
|
2467
2495
|
if (task.agent !== agent.name) {
|
|
2468
2496
|
throw new Error(`Task ${task.id} belongs to agent ${task.agent}, not ${agent.name}`);
|
|
2469
2497
|
}
|
|
2470
|
-
if (!this.running.has(task.id) && (!task.sessionDir || !
|
|
2498
|
+
if (!this.running.has(task.id) && (!task.sessionDir || !existsSync3(task.sessionDir)) && (!task.sessionFile || !existsSync3(task.sessionFile))) {
|
|
2471
2499
|
throw new Error(`Task ${task.id} session is unavailable`);
|
|
2472
2500
|
}
|
|
2473
2501
|
} else {
|
|
@@ -3128,8 +3156,6 @@ ${renderTaskResult(task.id, failed ? "error" : "completed", value)}`;
|
|
|
3128
3156
|
this.primaryRules = undefined;
|
|
3129
3157
|
this.primaryConfigurationError = false;
|
|
3130
3158
|
const catalog = this.loadCatalog(ctx.cwd, getAgentDir3(), isProjectTrusted(ctx));
|
|
3131
|
-
for (const warning of catalog.warnings)
|
|
3132
|
-
ctx.ui.notify(warning, "warning");
|
|
3133
3159
|
this.maxSubagents = catalog.maxSubagents;
|
|
3134
3160
|
this.semaphore = new Semaphore(Math.max(1, this.maxSubagents));
|
|
3135
3161
|
if (catalog.diagnostics.length > 0) {
|
|
@@ -3327,21 +3353,21 @@ var NETWORK_PERMISSION_OPTIONS = [
|
|
|
3327
3353
|
function loadSandboxConfig(cwd, includeProject) {
|
|
3328
3354
|
const projectConfigPath = join6(cwd, ".pi", "sandbox.json");
|
|
3329
3355
|
const globalConfigPath = join6(getAgentDir4(), "sandbox.json");
|
|
3330
|
-
if (!
|
|
3356
|
+
if (!existsSync4(globalConfigPath)) {
|
|
3331
3357
|
const templatePath = join6(packageDir2, "sandbox.json");
|
|
3332
3358
|
mkdirSync3(dirname4(globalConfigPath), { recursive: true });
|
|
3333
|
-
writeFileSync3(globalConfigPath,
|
|
3359
|
+
writeFileSync3(globalConfigPath, readFileSync5(templatePath, "utf-8"), "utf-8");
|
|
3334
3360
|
}
|
|
3335
|
-
let globalConfig = JSON.parse(
|
|
3361
|
+
let globalConfig = JSON.parse(readFileSync5(join6(packageDir2, "sandbox.json"), "utf-8"));
|
|
3336
3362
|
try {
|
|
3337
|
-
const override = JSON.parse(
|
|
3363
|
+
const override = JSON.parse(readFileSync5(globalConfigPath, "utf-8"));
|
|
3338
3364
|
globalConfig = deepMerge(globalConfig, override);
|
|
3339
3365
|
} catch (error) {
|
|
3340
3366
|
console.error(`Warning: Could not parse ${globalConfigPath}: ${error}`);
|
|
3341
3367
|
}
|
|
3342
|
-
if (includeProject &&
|
|
3368
|
+
if (includeProject && existsSync4(projectConfigPath)) {
|
|
3343
3369
|
try {
|
|
3344
|
-
const projectConfig = JSON.parse(
|
|
3370
|
+
const projectConfig = JSON.parse(readFileSync5(projectConfigPath, "utf-8"));
|
|
3345
3371
|
return deepMerge(globalConfig, projectConfig);
|
|
3346
3372
|
} catch (error) {
|
|
3347
3373
|
console.error(`Warning: Could not parse ${projectConfigPath}: ${error}`);
|
|
@@ -3421,10 +3447,10 @@ function getConfigPaths(cwd) {
|
|
|
3421
3447
|
return getPiConfigPaths(cwd, "sandbox.json");
|
|
3422
3448
|
}
|
|
3423
3449
|
function readOrEmptyConfig(configPath) {
|
|
3424
|
-
if (!
|
|
3450
|
+
if (!existsSync4(configPath))
|
|
3425
3451
|
return {};
|
|
3426
3452
|
try {
|
|
3427
|
-
return JSON.parse(
|
|
3453
|
+
return JSON.parse(readFileSync5(configPath, "utf-8"));
|
|
3428
3454
|
} catch {
|
|
3429
3455
|
return {};
|
|
3430
3456
|
}
|
|
@@ -3436,7 +3462,7 @@ function writeConfigFile(configPath, config) {
|
|
|
3436
3462
|
}
|
|
3437
3463
|
function getSandboxConfigWriteTarget(cwd, includeProject = true) {
|
|
3438
3464
|
const { globalPath, projectPath } = getConfigPaths(cwd);
|
|
3439
|
-
if (includeProject &&
|
|
3465
|
+
if (includeProject && existsSync4(projectPath)) {
|
|
3440
3466
|
return { scope: "project", path: projectPath };
|
|
3441
3467
|
}
|
|
3442
3468
|
return { scope: "global", path: globalPath };
|
|
@@ -3512,11 +3538,11 @@ function canonicalizePath(filePath, cwd) {
|
|
|
3512
3538
|
} catch {
|
|
3513
3539
|
const tail = [];
|
|
3514
3540
|
let probe = abs;
|
|
3515
|
-
while (!
|
|
3541
|
+
while (!existsSync4(probe)) {
|
|
3516
3542
|
const parent = dirname4(probe);
|
|
3517
3543
|
if (parent === probe)
|
|
3518
3544
|
return abs;
|
|
3519
|
-
tail.unshift(
|
|
3545
|
+
tail.unshift(basename2(probe));
|
|
3520
3546
|
probe = parent;
|
|
3521
3547
|
}
|
|
3522
3548
|
try {
|
|
@@ -3987,7 +4013,7 @@ function createLandstripIntegration(options = {}) {
|
|
|
3987
4013
|
if (!isAbsolute3(requestedEntry)) {
|
|
3988
4014
|
throw new Error("Worker extension entry must be an absolute path or file URL");
|
|
3989
4015
|
}
|
|
3990
|
-
if (!
|
|
4016
|
+
if (!existsSync4(requestedEntry)) {
|
|
3991
4017
|
throw new Error(`Worker extension entry does not exist: ${requestedEntry}`);
|
|
3992
4018
|
}
|
|
3993
4019
|
const entry = realpathSync2(requestedEntry);
|
|
@@ -4029,7 +4055,7 @@ function createLandstripIntegration(options = {}) {
|
|
|
4029
4055
|
const paths = mergeAllowances(config.filesystem.allowRead, sessionAllowedReadPaths, allowances?.readPaths);
|
|
4030
4056
|
if (process.platform !== "win32")
|
|
4031
4057
|
return paths;
|
|
4032
|
-
return paths.filter((path) => !path.startsWith("/") && (path.includes("*") ||
|
|
4058
|
+
return paths.filter((path) => !path.startsWith("/") && (path.includes("*") || existsSync4(expandPath(path, cwd))));
|
|
4033
4059
|
}
|
|
4034
4060
|
function getEffectiveAllowWrite(config, allowances) {
|
|
4035
4061
|
const paths = mergeAllowances(config.filesystem.allowWrite, sessionAllowedWritePaths, allowances?.writePaths);
|
|
@@ -4564,7 +4590,7 @@ function createLandstripIntegration(options = {}) {
|
|
|
4564
4590
|
function createLandstripBashOps(ctx, callbacks = {}, allowances = { domains: [], readPaths: [], writePaths: [], targets: [] }) {
|
|
4565
4591
|
return {
|
|
4566
4592
|
async exec(command, cwd, { onData, signal, timeout, env }) {
|
|
4567
|
-
if (!
|
|
4593
|
+
if (!existsSync4(cwd))
|
|
4568
4594
|
throw new Error(`Working directory does not exist: ${cwd}`);
|
|
4569
4595
|
const { shell, args } = getShellConfig(SettingsManager.create(cwd).getShellPath());
|
|
4570
4596
|
const config = loadConfig(cwd);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-landstrip",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.2",
|
|
4
4
|
"description": "Sandbox-aware Pi subagents and interactive command permissions",
|
|
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.2",
|
|
43
43
|
"jsonc-parser": "^3.3.1",
|
|
44
44
|
"minimatch": "^10.2.5",
|
|
45
45
|
"yaml": "^2.9.0"
|