pi-landstrip 0.19.12 → 0.19.14
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 +51 -71
- package/api.ts +134 -11
- package/dist/index.ts +63 -23
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -9,52 +9,38 @@ Requires Pi ≥0.82.0 and Node.js ≥22.19.0.
|
|
|
9
9
|
pi install npm:pi-landstrip
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
`--no-sandbox` or `enabled: false` explicitly permits unsandboxed execution.
|
|
12
|
+
Unusable sandboxing fails closed; `--no-sandbox` or `enabled: false` permits
|
|
13
|
+
unsandboxed execution.
|
|
15
14
|
|
|
16
15
|
## Permissions and configuration
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
`toolFilesystemPolicy: "sandbox"` applies filesystem policy checks, not OS isolation.
|
|
17
|
+
With sandboxing enabled, Bash (`bash`, `!`, `!!`) and workers are OS-isolated.
|
|
18
|
+
Primary file tools remain in trusted Pi; `toolFilesystemPolicy: "sandbox"`
|
|
19
|
+
adds policy checks, not isolation. Tool approval never bypasses hard denials.
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
the global or project file. Store only overrides:
|
|
21
|
+
Configuration: bundled [`sandbox.json`](sandbox.json) → `~/.pi/agent/sandbox.json`
|
|
22
|
+
→ trusted-project `.pi/sandbox.json`. Arrays combine; later scalars win.
|
|
23
|
+
Persistent approvals update global/project overrides. For policy-only shell reads:
|
|
27
24
|
|
|
28
25
|
```json
|
|
29
|
-
{
|
|
30
|
-
"shell": { "readAccess": "policy" },
|
|
31
|
-
"filesystem": { "allowWrite": ["."] },
|
|
32
|
-
"network": { "allowNetwork": false }
|
|
33
|
-
}
|
|
26
|
+
{ "shell": { "readAccess": "policy" } }
|
|
34
27
|
```
|
|
35
28
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
- `filesystem.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
auth are unsupported on Windows.
|
|
52
|
-
- `filesystem.denyWriteAlways` cannot be overridden by equal/nested write grants
|
|
53
|
-
or interactive prompts; write attempts are silently rejected. Non-empty
|
|
54
|
-
`denyWriteAlways` is unsupported on Windows.
|
|
55
|
-
- External skill, prompt, and theme directories may need explicit discovery
|
|
56
|
-
access. Prefer resource file paths: resolved-file grants do not cover enclosing
|
|
57
|
-
directories.
|
|
29
|
+
Shell reads default to `"host"`; workers always use policy reads. See
|
|
30
|
+
[landstrip(1)](../landstrip/man/man1/landstrip.1) for native policy, traps,
|
|
31
|
+
and platform limits, including Windows read grants and hard-denial restrictions.
|
|
32
|
+
|
|
33
|
+
Worker boundaries:
|
|
34
|
+
|
|
35
|
+
- Bootstrap grants override bundled `denyRead`, never global/trusted-project
|
|
36
|
+
denies. Denied startup resources prevent launch. Legacy `/home` or `/Users`
|
|
37
|
+
denies are explicit overrides; remove them only if unintended.
|
|
38
|
+
- Hard read denials also apply to shell host reads. Private workers protect
|
|
39
|
+
parent auth-file/lock roots with `denyReadAlways` and `denyWrite`;
|
|
40
|
+
private worker authentication is unsupported on Windows.
|
|
41
|
+
- `filesystem.denyWriteAlways` rejects write attempts without prompting.
|
|
42
|
+
- External skills/prompts/themes may need directory discovery grants;
|
|
43
|
+
resolved-file grants do not include parent directories.
|
|
58
44
|
|
|
59
45
|
## Agents and tasks
|
|
60
46
|
|
|
@@ -69,49 +55,43 @@ Configure `~/.pi/agent/landstrip.json`, trusted-project `.pi/landstrip.json`, or
|
|
|
69
55
|
}
|
|
70
56
|
```
|
|
71
57
|
|
|
72
|
-
`maxSubagents
|
|
73
|
-
|
|
58
|
+
`maxSubagents`: 0–16. Agent definitions can override model, prompt, mode, options,
|
|
59
|
+
and permissions.
|
|
74
60
|
|
|
75
|
-
`/landstrip`
|
|
76
|
-
|
|
61
|
+
Use `/landstrip` for the management pane and `/landstrip help` for commands.
|
|
62
|
+
Task IDs accept unique prefixes; bare IDs open logs. `/landstrip tasks kill <id>`
|
|
63
|
+
terminates a task. `Ctrl+Shift+A` cycles primary agents while idle.
|
|
64
|
+
The `task` tool accepts `background: true` for immediate return and `task_id`
|
|
65
|
+
to continue a saved task. Press `Enter` in running/queued logs to steer workers.
|
|
77
66
|
|
|
78
|
-
|
|
79
|
-
| --------------------------------- | ------------------------------- |
|
|
80
|
-
| `/landstrip status` | Inspect sandbox, agents, tasks. |
|
|
81
|
-
| `/landstrip settings` | Set concurrency/file policy. |
|
|
82
|
-
| `/landstrip sandbox [on\|off]` | Inspect/toggle OS isolation. |
|
|
83
|
-
| `/landstrip agents [@name]` | List/select primary agents. |
|
|
84
|
-
| `/landstrip subagents` | List process subagents. |
|
|
85
|
-
| `/landstrip tasks [list]` | List task sessions. |
|
|
86
|
-
| `/landstrip tasks kill <task-id>` | Terminate a task. |
|
|
87
|
-
| `/landstrip logs [task-id]` | Open task logs. |
|
|
67
|
+
### Worker environment and authentication
|
|
88
68
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
`background: true` for immediate return and `task_id` to continue a saved task.
|
|
92
|
-
Press `Enter` in running/queued task logs to steer the worker.
|
|
69
|
+
Workers start `--offline`; install resources and refresh model catalogs in the
|
|
70
|
+
parent first. Provider endpoints still need network permission.
|
|
93
71
|
|
|
94
|
-
|
|
72
|
+
Only runtime paths, locale, certificate paths, and supported Pi config are
|
|
73
|
+
inherited—not parent API keys, tool tokens, proxy URLs, or arbitrary variables.
|
|
74
|
+
The parent refreshes selected-provider auth and sends it, along with selected
|
|
75
|
+
Bedrock/Vertex/Azure settings, over a private Unix pipe, not arguments or
|
|
76
|
+
environment. Configure tool logins separately; authenticated workers cannot
|
|
77
|
+
launch on Windows.
|
|
95
78
|
|
|
96
|
-
|
|
97
|
-
|
|
79
|
+
Ambient AWS profiles need an explicit region for non-ARN Bedrock models.
|
|
80
|
+
Custom credential-chain environment paths are unsupported; use scoped keys or
|
|
81
|
+
a Bedrock bearer token. Provider credential files need filesystem grants.
|
|
98
82
|
|
|
99
|
-
|
|
100
|
-
configuration—not parent API keys, tool tokens, proxy URLs, or arbitrary variables.
|
|
101
|
-
The parent refreshes selected-provider auth and sends it through a private Unix
|
|
102
|
-
pipe, never arguments or worker environment. Environment-based tool logins need
|
|
103
|
-
separate configuration; authenticated workers fail before spawning on Windows.
|
|
83
|
+
## Extension API
|
|
104
84
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
85
|
+
[`pi-landstrip/api`](api.ts) exports `createLandstrip`. `prepareProcess()` returns
|
|
86
|
+
`undefined` without a runtime. Dispose prepared processes separately from the
|
|
87
|
+
discovery handle; generic `spawn()` returns `ChildProcess`, RPC workers use
|
|
88
|
+
piped streams.
|
|
109
89
|
|
|
110
90
|
## Development
|
|
111
91
|
|
|
112
|
-
From this package: `npm ci
|
|
113
|
-
|
|
92
|
+
From this package: `npm ci && npm run all` (Bun required).
|
|
93
|
+
`npm run ci:fmt` checks formatting without edits.
|
|
114
94
|
|
|
115
95
|
## License
|
|
116
96
|
|
|
117
|
-
Apache-2.0
|
|
97
|
+
[Apache-2.0](LICENSE).
|
package/api.ts
CHANGED
|
@@ -105,6 +105,11 @@ export interface LandstripPreparedProcess {
|
|
|
105
105
|
dispose(): Promise<void>;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
export interface Landstrip {
|
|
109
|
+
prepareProcess(options: LandstripProcessOptions): Promise<LandstripPreparedProcess | undefined>;
|
|
110
|
+
dispose(): void;
|
|
111
|
+
}
|
|
112
|
+
|
|
108
113
|
export interface LandstripRpcChildProcess {
|
|
109
114
|
readonly stdin: Writable;
|
|
110
115
|
readonly stdout: Readable;
|
|
@@ -126,7 +131,7 @@ export type LandstripSpawn = (
|
|
|
126
131
|
command: string,
|
|
127
132
|
args: readonly string[],
|
|
128
133
|
options: SpawnOptions,
|
|
129
|
-
) => ChildProcess
|
|
134
|
+
) => ChildProcess;
|
|
130
135
|
|
|
131
136
|
export type LandstripEvent =
|
|
132
137
|
| { readonly type: 'sandbox.changed'; readonly context: LandstripContextV2 }
|
|
@@ -184,35 +189,152 @@ function isRuntime(value: unknown): value is PiLandstripRuntimeV2 {
|
|
|
184
189
|
);
|
|
185
190
|
}
|
|
186
191
|
|
|
187
|
-
|
|
192
|
+
function isDisposableProcess(value: unknown): value is Pick<LandstripPreparedProcess, 'dispose'> {
|
|
193
|
+
return isRecord(value) && typeof value.dispose === 'function';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function isPreparedProcess(value: unknown): value is LandstripPreparedProcess {
|
|
197
|
+
return (
|
|
198
|
+
isRecord(value) &&
|
|
199
|
+
typeof value.command === 'string' &&
|
|
200
|
+
value.command.trim().length > 0 &&
|
|
201
|
+
Array.isArray(value.args) &&
|
|
202
|
+
[...value.args].every((arg: unknown) => typeof arg === 'string') &&
|
|
203
|
+
typeof value.cwd === 'string' &&
|
|
204
|
+
value.cwd.length > 0 &&
|
|
205
|
+
isRecord(value.env) &&
|
|
206
|
+
Object.values(value.env).every((entry) => entry === undefined || typeof entry === 'string') &&
|
|
207
|
+
typeof value.spawn === 'function' &&
|
|
208
|
+
isDisposableProcess(value)
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function discoverLandstrip(
|
|
188
213
|
pi: ExtensionAPI,
|
|
189
214
|
callback: (runtime: PiLandstripRuntimeV2) => void,
|
|
215
|
+
onInvalid?: (error: Error) => void,
|
|
190
216
|
): () => void {
|
|
191
217
|
const events = eventBus(pi);
|
|
192
218
|
if (!events) return () => undefined;
|
|
193
219
|
|
|
194
220
|
let current: PiLandstripRuntimeV2 | undefined;
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
221
|
+
let disposed = false;
|
|
222
|
+
const register = (value: unknown): void => {
|
|
223
|
+
if (disposed) return;
|
|
224
|
+
if (!isRuntime(value)) {
|
|
225
|
+
onInvalid?.(new Error('Invalid Landstrip runtime v2 declaration'));
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (value === current) return;
|
|
229
|
+
current = value;
|
|
230
|
+
callback(value);
|
|
199
231
|
};
|
|
200
232
|
const unsubscribe = events.on(RUNTIME_REGISTER_EVENT, (value) => {
|
|
201
|
-
if (
|
|
202
|
-
if (
|
|
233
|
+
if (disposed) return;
|
|
234
|
+
if (!isRecord(value) || value.version !== LANDSTRIP_RUNTIME_VERSION) {
|
|
235
|
+
onInvalid?.(new Error('Invalid Landstrip runtime v2 registration'));
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
register(value.runtime);
|
|
203
239
|
});
|
|
240
|
+
const dispose = (): void => {
|
|
241
|
+
if (disposed) return;
|
|
242
|
+
disposed = true;
|
|
243
|
+
unsubscribe();
|
|
244
|
+
};
|
|
204
245
|
try {
|
|
205
246
|
events.emit(RUNTIME_DISCOVER_EVENT, {
|
|
206
247
|
version: LANDSTRIP_RUNTIME_VERSION,
|
|
207
248
|
register,
|
|
208
249
|
} satisfies RuntimeDiscovery);
|
|
209
|
-
return
|
|
250
|
+
return dispose;
|
|
210
251
|
} catch (error) {
|
|
211
|
-
|
|
252
|
+
dispose();
|
|
212
253
|
throw error;
|
|
213
254
|
}
|
|
214
255
|
}
|
|
215
256
|
|
|
257
|
+
export function useLandstrip(
|
|
258
|
+
pi: ExtensionAPI,
|
|
259
|
+
callback: (runtime: PiLandstripRuntimeV2) => void,
|
|
260
|
+
): () => void {
|
|
261
|
+
return discoverLandstrip(pi, callback);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function createLandstrip(pi: ExtensionAPI): Landstrip {
|
|
265
|
+
let runtime: PiLandstripRuntimeV2 | undefined;
|
|
266
|
+
let runtimeError: Error | undefined;
|
|
267
|
+
let disposed = false;
|
|
268
|
+
const stopDiscovery = discoverLandstrip(
|
|
269
|
+
pi,
|
|
270
|
+
(value) => {
|
|
271
|
+
runtime = value;
|
|
272
|
+
},
|
|
273
|
+
(error) => {
|
|
274
|
+
runtimeError ??= error;
|
|
275
|
+
},
|
|
276
|
+
);
|
|
277
|
+
if (runtimeError) {
|
|
278
|
+
stopDiscovery();
|
|
279
|
+
throw runtimeError;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
async prepareProcess(options) {
|
|
284
|
+
if (disposed) throw new Error('Landstrip handle has been disposed');
|
|
285
|
+
if (runtimeError) throw runtimeError;
|
|
286
|
+
options.signal?.throwIfAborted();
|
|
287
|
+
if (!runtime) return undefined;
|
|
288
|
+
|
|
289
|
+
const prepared: unknown = await runtime.prepareProcess(options);
|
|
290
|
+
if (!isDisposableProcess(prepared)) {
|
|
291
|
+
throw new Error('Landstrip returned a process without a disposal method');
|
|
292
|
+
}
|
|
293
|
+
let disposePromise: Promise<void> | undefined;
|
|
294
|
+
const dispose = (): Promise<void> => {
|
|
295
|
+
disposePromise ??= Promise.resolve().then(() => prepared.dispose());
|
|
296
|
+
return disposePromise;
|
|
297
|
+
};
|
|
298
|
+
try {
|
|
299
|
+
options.signal?.throwIfAborted();
|
|
300
|
+
if (disposed) throw new Error('Landstrip handle has been disposed');
|
|
301
|
+
if (runtimeError) throw runtimeError;
|
|
302
|
+
if (!isPreparedProcess(prepared)) {
|
|
303
|
+
throw new Error('Landstrip returned an invalid prepared process');
|
|
304
|
+
}
|
|
305
|
+
} catch (error) {
|
|
306
|
+
try {
|
|
307
|
+
await dispose();
|
|
308
|
+
} catch (cleanupError) {
|
|
309
|
+
throw new AggregateError(
|
|
310
|
+
[error, cleanupError],
|
|
311
|
+
'Landstrip process preparation cleanup failed',
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
throw error;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
command: prepared.command,
|
|
319
|
+
args: prepared.args,
|
|
320
|
+
cwd: prepared.cwd,
|
|
321
|
+
env: prepared.env,
|
|
322
|
+
spawn(command, args, spawnOptions) {
|
|
323
|
+
if (disposePromise) throw new Error('Prepared Landstrip process has been disposed');
|
|
324
|
+
return prepared.spawn(command, args, spawnOptions);
|
|
325
|
+
},
|
|
326
|
+
dispose,
|
|
327
|
+
};
|
|
328
|
+
},
|
|
329
|
+
dispose() {
|
|
330
|
+
if (disposed) return;
|
|
331
|
+
disposed = true;
|
|
332
|
+
stopDiscovery();
|
|
333
|
+
runtime = undefined;
|
|
334
|
+
},
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
216
338
|
function provideExtensionProvider(
|
|
217
339
|
pi: ExtensionAPI,
|
|
218
340
|
register: (runtime: PiLandstripRuntimeV2) => () => void,
|
|
@@ -283,7 +405,8 @@ export function contextFromEnvironment(
|
|
|
283
405
|
value.version !== LANDSTRIP_RUNTIME_VERSION ||
|
|
284
406
|
value.host !== 'pi' ||
|
|
285
407
|
value.role !== 'subagent' ||
|
|
286
|
-
|
|
408
|
+
typeof value.sandbox !== 'string' ||
|
|
409
|
+
!['enabled', 'disabled', 'unavailable'].includes(value.sandbox) ||
|
|
287
410
|
typeof value.cwd !== 'string' ||
|
|
288
411
|
!Number.isInteger(value.depth) ||
|
|
289
412
|
(value.depth as number) < 0 ||
|
package/dist/index.ts
CHANGED
|
@@ -5895,10 +5895,24 @@ function shellEnvironment(env, cwd, proxyPort, proxyToken) {
|
|
|
5895
5895
|
const result = { ...process.env, ...env, PWD: resolve4(cwd) };
|
|
5896
5896
|
return createProxyEnvironment(proxyPort, proxyToken, result);
|
|
5897
5897
|
}
|
|
5898
|
+
var WINDOWS_LAUNCHER_ENVIRONMENT = [
|
|
5899
|
+
"ProgramData",
|
|
5900
|
+
"LOCALAPPDATA",
|
|
5901
|
+
"SystemRoot",
|
|
5902
|
+
"windir",
|
|
5903
|
+
"ComSpec",
|
|
5904
|
+
"PATHEXT"
|
|
5905
|
+
];
|
|
5898
5906
|
function createLandstripLauncherEnvironment(providerEnv, hostEnv = process.env, platform = process.platform) {
|
|
5899
|
-
if (platform !== "win32"
|
|
5907
|
+
if (platform !== "win32")
|
|
5900
5908
|
return providerEnv;
|
|
5901
|
-
|
|
5909
|
+
const result = { ...providerEnv };
|
|
5910
|
+
for (const name of WINDOWS_LAUNCHER_ENVIRONMENT) {
|
|
5911
|
+
const value = hostEnv[name];
|
|
5912
|
+
if (value !== undefined)
|
|
5913
|
+
result[name] = value;
|
|
5914
|
+
}
|
|
5915
|
+
return result;
|
|
5902
5916
|
}
|
|
5903
5917
|
function createWindowsDenyRead(denyRead, volumeRoot) {
|
|
5904
5918
|
return [...new Set([...denyRead.filter((path) => !path.startsWith("/")), volumeRoot])];
|
|
@@ -6015,7 +6029,7 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
6015
6029
|
if (!provider)
|
|
6016
6030
|
return { decision: "abstain" };
|
|
6017
6031
|
const result = await provider.decide(request);
|
|
6018
|
-
if (typeof result !== "object" || result === null || !("decision" in result) || !["allow", "deny", "abstain"].includes(
|
|
6032
|
+
if (typeof result !== "object" || result === null || !("decision" in result) || typeof result.decision !== "string" || !["allow", "deny", "abstain"].includes(result.decision) || "reason" in result && result.reason !== undefined && typeof result.reason !== "string") {
|
|
6019
6033
|
throw new Error(`Permission ask provider "${provider.id}" returned an invalid decision`);
|
|
6020
6034
|
}
|
|
6021
6035
|
return result;
|
|
@@ -6376,7 +6390,7 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
|
|
|
6376
6390
|
}
|
|
6377
6391
|
});
|
|
6378
6392
|
}
|
|
6379
|
-
function
|
|
6393
|
+
function prepareUnsandboxedProcess(options) {
|
|
6380
6394
|
let spawned = false;
|
|
6381
6395
|
let disposed = false;
|
|
6382
6396
|
let disposePromise;
|
|
@@ -6412,7 +6426,7 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
|
|
|
6412
6426
|
}
|
|
6413
6427
|
};
|
|
6414
6428
|
}
|
|
6415
|
-
async function
|
|
6429
|
+
async function prepareManagedProcess(options) {
|
|
6416
6430
|
if (options.signal?.aborted)
|
|
6417
6431
|
throw new Error("Task cancelled");
|
|
6418
6432
|
if (!ensureSandboxState(options.ctx)) {
|
|
@@ -6423,7 +6437,7 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
|
|
|
6423
6437
|
warnUnsandboxed(options.ctx, "Subagent processes are running without Landstrip sandboxing", "warning");
|
|
6424
6438
|
unsandboxedWorkerWarningShown = true;
|
|
6425
6439
|
}
|
|
6426
|
-
return
|
|
6440
|
+
return prepareUnsandboxedProcess(options);
|
|
6427
6441
|
}
|
|
6428
6442
|
const allowances = {
|
|
6429
6443
|
permissionPromptLabel: options.permissionPromptLabel,
|
|
@@ -6461,7 +6475,28 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
|
|
|
6461
6475
|
let policy;
|
|
6462
6476
|
let trapSocket;
|
|
6463
6477
|
let childEnd;
|
|
6478
|
+
let preparedChild;
|
|
6464
6479
|
let disposed = false;
|
|
6480
|
+
const cleanup = async () => {
|
|
6481
|
+
const errors = [];
|
|
6482
|
+
for (const release of [
|
|
6483
|
+
() => stopPreparedChild(preparedChild),
|
|
6484
|
+
() => trapSocket?.destroy(),
|
|
6485
|
+
() => childEnd?.destroy(),
|
|
6486
|
+
() => {
|
|
6487
|
+
if (policy)
|
|
6488
|
+
rmSync2(policy.dir, { recursive: true, force: true });
|
|
6489
|
+
},
|
|
6490
|
+
() => proxy?.stop()
|
|
6491
|
+
]) {
|
|
6492
|
+
try {
|
|
6493
|
+
await release();
|
|
6494
|
+
} catch (error) {
|
|
6495
|
+
errors.push(error);
|
|
6496
|
+
}
|
|
6497
|
+
}
|
|
6498
|
+
return errors;
|
|
6499
|
+
};
|
|
6465
6500
|
try {
|
|
6466
6501
|
if (options.signal?.aborted)
|
|
6467
6502
|
throw new Error("Task cancelled");
|
|
@@ -6473,20 +6508,17 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
|
|
|
6473
6508
|
attachWorkerTrap(trapSocket, options.ctx, options.cwd, processAllowances, options.signal);
|
|
6474
6509
|
}
|
|
6475
6510
|
} catch (error) {
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
await proxy?.stop();
|
|
6511
|
+
const cleanupErrors = await cleanup();
|
|
6512
|
+
if (cleanupErrors.length > 0) {
|
|
6513
|
+
throw new AggregateError([error, ...cleanupErrors], "Landstrip process preparation cleanup failed");
|
|
6514
|
+
}
|
|
6481
6515
|
throw error;
|
|
6482
6516
|
}
|
|
6483
6517
|
const workerPolicy = policy;
|
|
6484
|
-
const workerTrapSocket = trapSocket;
|
|
6485
6518
|
const workerChildEnd = childEnd;
|
|
6486
6519
|
const workerEnv = createProxyEnvironment(proxy?.port ?? null, proxyToken, options.env ?? {});
|
|
6487
6520
|
let spawned = false;
|
|
6488
6521
|
let disposePromise;
|
|
6489
|
-
let preparedChild;
|
|
6490
6522
|
const spawnWorker = (_command, _args, spawnOptions) => {
|
|
6491
6523
|
if (disposed)
|
|
6492
6524
|
throw new Error("Prepared process has been disposed");
|
|
@@ -6521,9 +6553,9 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
|
|
|
6521
6553
|
detached: true,
|
|
6522
6554
|
stdio
|
|
6523
6555
|
});
|
|
6524
|
-
workerChildEnd?.destroy();
|
|
6525
|
-
patchProcessGroupKill(child);
|
|
6526
6556
|
preparedChild = child;
|
|
6557
|
+
patchProcessGroupKill(child);
|
|
6558
|
+
workerChildEnd?.destroy();
|
|
6527
6559
|
return child;
|
|
6528
6560
|
};
|
|
6529
6561
|
return {
|
|
@@ -6535,21 +6567,29 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
|
|
|
6535
6567
|
dispose() {
|
|
6536
6568
|
disposePromise ??= (async () => {
|
|
6537
6569
|
disposed = true;
|
|
6538
|
-
await
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
await proxy?.stop();
|
|
6570
|
+
const cleanupErrors = await cleanup();
|
|
6571
|
+
if (cleanupErrors.length > 0) {
|
|
6572
|
+
throw new AggregateError(cleanupErrors, "Landstrip process disposal cleanup failed");
|
|
6573
|
+
}
|
|
6543
6574
|
})();
|
|
6544
6575
|
return disposePromise;
|
|
6545
6576
|
}
|
|
6546
6577
|
};
|
|
6547
6578
|
}
|
|
6579
|
+
async function prepareRpcWorker(options) {
|
|
6580
|
+
const prepared = await prepareManagedProcess(options);
|
|
6581
|
+
return {
|
|
6582
|
+
...prepared,
|
|
6583
|
+
spawn(command, args, spawnOptions) {
|
|
6584
|
+
return prepared.spawn(command, args, spawnOptions);
|
|
6585
|
+
}
|
|
6586
|
+
};
|
|
6587
|
+
}
|
|
6548
6588
|
async function prepareProcess(options) {
|
|
6549
6589
|
if (process.platform === "win32" && (getNoSandboxFlag() || !loadConfig(options.cwd).enabled)) {
|
|
6550
6590
|
throw new Error("Generic process preparation requires sandboxing on Windows");
|
|
6551
6591
|
}
|
|
6552
|
-
return
|
|
6592
|
+
return prepareManagedProcess({
|
|
6553
6593
|
...options,
|
|
6554
6594
|
env: options.env ?? process.env,
|
|
6555
6595
|
readPaths: options.readPaths ?? [],
|
|
@@ -7003,7 +7043,7 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
|
|
|
7003
7043
|
const errorText = formatError(error);
|
|
7004
7044
|
const fallbackOutput = `${stderrOutput}
|
|
7005
7045
|
${errorText}`;
|
|
7006
|
-
const blockedWritePath = extractTrapBlockedPath(landstripErrorOutput, ctx.cwd, "write") ?? extractNativeWriteDeniedPath(fallbackOutput, ctx.cwd);
|
|
7046
|
+
const blockedWritePath = extractTrapBlockedPath(landstripErrorOutput, ctx.cwd, "write") ?? extractNativeWriteDeniedPath(fallbackOutput, ctx.cwd, params.command);
|
|
7007
7047
|
if (blockedWritePath) {
|
|
7008
7048
|
const retryResult = await retryWithAccess("write", blockedWritePath);
|
|
7009
7049
|
if (retryResult)
|
|
@@ -7028,7 +7068,7 @@ ${errorText}`;
|
|
|
7028
7068
|
${message}
|
|
7029
7069
|
` });
|
|
7030
7070
|
}
|
|
7031
|
-
const blockedWritePath = extractTrapBlockedPath(landstripErrorOutput, ctx.cwd, "write") ?? extractNativeWriteDeniedPath(stderrOutput, ctx.cwd);
|
|
7071
|
+
const blockedWritePath = extractTrapBlockedPath(landstripErrorOutput, ctx.cwd, "write") ?? extractNativeWriteDeniedPath(stderrOutput, ctx.cwd, params.command);
|
|
7032
7072
|
if (blockedWritePath) {
|
|
7033
7073
|
const retryResult = await retryWithAccess("write", blockedWritePath);
|
|
7034
7074
|
if (retryResult)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-landstrip",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.14",
|
|
4
4
|
"description": "Sandboxed Bash and process-backed agents for Pi",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"landstrip",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"ci:test": "npm run test"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@landstrip/landstrip-api": "^0.19.
|
|
42
|
+
"@landstrip/landstrip-api": "^0.19.14",
|
|
43
43
|
"jsonc-parser": "^3.3.1",
|
|
44
44
|
"minimatch": "^10.2.5",
|
|
45
45
|
"undici": "^8.9.0",
|