pi-landstrip 0.17.35 → 0.17.36
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 +28 -3
- package/dist/index.ts +369 -81
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ The extension includes a shared [sandbox policy](./sandbox.json) and separate
|
|
|
16
16
|
[agent configuration](./subagents.json). Global and trusted-project files can
|
|
17
17
|
override both.
|
|
18
18
|
|
|
19
|
-
Process-backed subagents require Pi >= 0.
|
|
19
|
+
Process-backed subagents require Pi >= 0.82.0, and Node.js >= 22.19.0.
|
|
20
20
|
|
|
21
21
|
## Installation
|
|
22
22
|
|
|
@@ -208,8 +208,33 @@ merged over the packaged [defaults](./subagents.json). The packaged subagent typ
|
|
|
208
208
|
are `explore`, `general`, and the OpenCode-compatible `scout` reconnaissance agent.
|
|
209
209
|
|
|
210
210
|
Sandbox policy remains in `~/.pi/agent/sandbox.json` and `.pi/sandbox.json`. Pi's
|
|
211
|
-
`settings.json` contains
|
|
212
|
-
entry.
|
|
211
|
+
`settings.json` contains normal Pi settings, such as the `pi-landstrip` package
|
|
212
|
+
entry, plus optional `landstrip.opencode` import flags:
|
|
213
|
+
|
|
214
|
+
```json
|
|
215
|
+
{
|
|
216
|
+
"packages": ["npm:pi-landstrip"],
|
|
217
|
+
"landstrip": {
|
|
218
|
+
"opencode": {
|
|
219
|
+
"showGlobalAgents": false,
|
|
220
|
+
"showLocalAgents": false
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Both flags default to `false`. When enabled:
|
|
227
|
+
|
|
228
|
+
- `showGlobalAgents` imports Markdown agents from the OpenCode global config
|
|
229
|
+
directory (`$OPENCODE_CONFIG_DIR`, or `$XDG_CONFIG_HOME/opencode`, or
|
|
230
|
+
`~/.config/opencode`) under `agent/` and `agents/`.
|
|
231
|
+
- `showLocalAgents` imports Markdown agents from the trusted project's
|
|
232
|
+
`.opencode/agent/` and `.opencode/agents/` directories.
|
|
233
|
+
|
|
234
|
+
OpenCode local agents override OpenCode global agents. Pi agent definitions
|
|
235
|
+
from `subagents.json` (built-in, global, and local) take precedence over OpenCode
|
|
236
|
+
imports with the same name; conflicts are silent. Local OpenCode agents are
|
|
237
|
+
skipped when the project is untrusted, regardless of the flag.
|
|
213
238
|
|
|
214
239
|
`subagents.json` accepts only top-level `maxSubagents` and `subagents`; sandbox
|
|
215
240
|
fields belong in `sandbox.json`.
|
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 existsSync6,
|
|
7
7
|
mkdirSync as mkdirSync3,
|
|
8
8
|
mkdtempSync as mkdtempSync2,
|
|
9
|
-
readFileSync as
|
|
9
|
+
readFileSync as readFileSync6,
|
|
10
10
|
realpathSync as realpathSync2,
|
|
11
11
|
rmSync as rmSync2,
|
|
12
12
|
writeFileSync as writeFileSync3
|
|
@@ -18,13 +18,13 @@ import {
|
|
|
18
18
|
isIP,
|
|
19
19
|
Socket as NetSocket
|
|
20
20
|
} from "node:net";
|
|
21
|
-
import { homedir as
|
|
22
|
-
import { basename as
|
|
21
|
+
import { homedir as homedir4, tmpdir as tmpdir2 } from "node:os";
|
|
22
|
+
import { basename as basename3, dirname as dirname3, isAbsolute as isAbsolute2, join as join7, parse, resolve as resolve2 } from "node:path";
|
|
23
23
|
import { StringDecoder as StringDecoder2 } from "node:string_decoder";
|
|
24
24
|
import { fileURLToPath as fileURLToPath3, URL } from "node:url";
|
|
25
25
|
import {
|
|
26
26
|
createBashToolDefinition,
|
|
27
|
-
getAgentDir as
|
|
27
|
+
getAgentDir as getAgentDir5,
|
|
28
28
|
getShellConfig,
|
|
29
29
|
SettingsManager,
|
|
30
30
|
withFileMutationQueue as withFileMutationQueue2
|
|
@@ -70,21 +70,21 @@ function encodeLandstripContext(context) {
|
|
|
70
70
|
// subagents.ts
|
|
71
71
|
import { randomUUID } from "node:crypto";
|
|
72
72
|
import {
|
|
73
|
-
existsSync as
|
|
73
|
+
existsSync as existsSync5,
|
|
74
74
|
lstatSync,
|
|
75
75
|
mkdirSync as mkdirSync2,
|
|
76
76
|
mkdtempSync,
|
|
77
|
-
readFileSync as
|
|
77
|
+
readFileSync as readFileSync5,
|
|
78
78
|
readlinkSync,
|
|
79
79
|
realpathSync,
|
|
80
80
|
rmSync,
|
|
81
81
|
writeFileSync as writeFileSync2
|
|
82
82
|
} from "node:fs";
|
|
83
|
-
import { homedir as
|
|
84
|
-
import { basename, dirname as dirname2, isAbsolute, join as
|
|
83
|
+
import { homedir as homedir3, tmpdir } from "node:os";
|
|
84
|
+
import { basename as basename2, dirname as dirname2, isAbsolute, join as join6, relative as relative2, resolve, sep as sep2 } from "node:path";
|
|
85
85
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
86
86
|
import {
|
|
87
|
-
getAgentDir as
|
|
87
|
+
getAgentDir as getAgentDir4,
|
|
88
88
|
keyHint,
|
|
89
89
|
SessionManager
|
|
90
90
|
} from "@earendil-works/pi-coding-agent";
|
|
@@ -92,10 +92,10 @@ import { matchesKey, Text, truncateToWidth as truncateToWidth2 } from "@earendil
|
|
|
92
92
|
import { Type } from "typebox";
|
|
93
93
|
|
|
94
94
|
// agents.ts
|
|
95
|
-
import { existsSync as
|
|
96
|
-
import { join as
|
|
95
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
96
|
+
import { join as join5 } from "node:path";
|
|
97
97
|
import { minimatch } from "minimatch";
|
|
98
|
-
import { getAgentDir as
|
|
98
|
+
import { getAgentDir as getAgentDir3 } from "@earendil-works/pi-coding-agent";
|
|
99
99
|
|
|
100
100
|
// config.ts
|
|
101
101
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
@@ -242,6 +242,267 @@ function loadLandstripConfig(cwd, includeProject = true, agentDir = getAgentDir(
|
|
|
242
242
|
return { ...config, agentSources };
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
+
// opencode-agents.ts
|
|
246
|
+
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
247
|
+
import { homedir as homedir2 } from "node:os";
|
|
248
|
+
import { basename, extname, join as join3, relative, sep } from "node:path";
|
|
249
|
+
import { parse as parseYaml } from "yaml";
|
|
250
|
+
var AGENT_DIR_PREFIXES = ["agent/", "agents/"];
|
|
251
|
+
function openCodeGlobalConfigDir() {
|
|
252
|
+
if (process.env.OPENCODE_CONFIG_DIR)
|
|
253
|
+
return process.env.OPENCODE_CONFIG_DIR;
|
|
254
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
255
|
+
return join3(xdg && xdg.length > 0 ? xdg : join3(homedir2(), ".config"), "opencode");
|
|
256
|
+
}
|
|
257
|
+
function listMarkdownFiles(root) {
|
|
258
|
+
if (!existsSync2(root))
|
|
259
|
+
return [];
|
|
260
|
+
const results = [];
|
|
261
|
+
const visit = (dir) => {
|
|
262
|
+
let entries;
|
|
263
|
+
try {
|
|
264
|
+
entries = readdirSync(dir);
|
|
265
|
+
} catch {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
for (const entry of entries) {
|
|
269
|
+
const path = join3(dir, entry);
|
|
270
|
+
let stats;
|
|
271
|
+
try {
|
|
272
|
+
stats = statSync(path);
|
|
273
|
+
} catch {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (stats.isDirectory())
|
|
277
|
+
visit(path);
|
|
278
|
+
else if (stats.isFile() && extname(entry).toLowerCase() === ".md")
|
|
279
|
+
results.push(path);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
visit(root);
|
|
283
|
+
return results;
|
|
284
|
+
}
|
|
285
|
+
function agentNameFromPath(dir, filePath) {
|
|
286
|
+
const relativePath = relative(dir, filePath).split(sep).join("/");
|
|
287
|
+
for (const prefix of AGENT_DIR_PREFIXES) {
|
|
288
|
+
if (relativePath.startsWith(prefix)) {
|
|
289
|
+
const candidate = relativePath.slice(prefix.length);
|
|
290
|
+
const extension2 = extname(candidate);
|
|
291
|
+
return extension2.length > 0 ? candidate.slice(0, -extension2.length) : candidate;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
const base = basename(filePath);
|
|
295
|
+
const extension = extname(base);
|
|
296
|
+
return extension.length > 0 ? base.slice(0, -extension.length) : base;
|
|
297
|
+
}
|
|
298
|
+
function parseFrontmatter(content) {
|
|
299
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n([\s\S]*))?$/);
|
|
300
|
+
if (!match)
|
|
301
|
+
return { data: {}, body: content };
|
|
302
|
+
const parsed = parseYaml(match[1] ?? "");
|
|
303
|
+
if (parsed === null || parsed === undefined)
|
|
304
|
+
return { data: {}, body: match[2] ?? "" };
|
|
305
|
+
if (!isRecord2(parsed))
|
|
306
|
+
throw new Error("frontmatter must be a YAML mapping");
|
|
307
|
+
return { data: { ...parsed }, body: match[2] ?? "" };
|
|
308
|
+
}
|
|
309
|
+
function convertToolsToPermission(tools) {
|
|
310
|
+
if (!isRecord2(tools))
|
|
311
|
+
throw new Error("tools must be a map of booleans");
|
|
312
|
+
const permission = {};
|
|
313
|
+
for (const [tool, enabled] of Object.entries(tools)) {
|
|
314
|
+
if (typeof enabled !== "boolean") {
|
|
315
|
+
throw new Error(`tools.${tool} must be a boolean`);
|
|
316
|
+
}
|
|
317
|
+
const action = enabled ? "allow" : "deny";
|
|
318
|
+
if (tool === "write" || tool === "edit" || tool === "patch") {
|
|
319
|
+
permission.edit = action;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
permission[tool] = action;
|
|
323
|
+
}
|
|
324
|
+
return permission;
|
|
325
|
+
}
|
|
326
|
+
function normalizeOpenCodeAgentRaw(data, prompt) {
|
|
327
|
+
const known = new Set([
|
|
328
|
+
"name",
|
|
329
|
+
"model",
|
|
330
|
+
"variant",
|
|
331
|
+
"prompt",
|
|
332
|
+
"description",
|
|
333
|
+
"temperature",
|
|
334
|
+
"top_p",
|
|
335
|
+
"mode",
|
|
336
|
+
"hidden",
|
|
337
|
+
"color",
|
|
338
|
+
"steps",
|
|
339
|
+
"maxSteps",
|
|
340
|
+
"options",
|
|
341
|
+
"permission",
|
|
342
|
+
"disable",
|
|
343
|
+
"tools"
|
|
344
|
+
]);
|
|
345
|
+
const options = isRecord2(data.options) ? { ...data.options } : {};
|
|
346
|
+
for (const [key, value] of Object.entries(data)) {
|
|
347
|
+
if (!known.has(key))
|
|
348
|
+
options[key] = value;
|
|
349
|
+
}
|
|
350
|
+
let permission = {};
|
|
351
|
+
if (data.tools !== undefined) {
|
|
352
|
+
permission = convertToolsToPermission(data.tools);
|
|
353
|
+
}
|
|
354
|
+
if (data.permission !== undefined) {
|
|
355
|
+
if (!isRecord2(data.permission))
|
|
356
|
+
throw new Error("permission must be an object");
|
|
357
|
+
permission = { ...permission, ...data.permission };
|
|
358
|
+
}
|
|
359
|
+
const steps = data.steps ?? data.maxSteps;
|
|
360
|
+
const raw = {
|
|
361
|
+
description: data.description,
|
|
362
|
+
prompt,
|
|
363
|
+
mode: data.mode,
|
|
364
|
+
model: data.model,
|
|
365
|
+
variant: data.variant,
|
|
366
|
+
hidden: data.hidden,
|
|
367
|
+
disable: data.disable,
|
|
368
|
+
color: data.color,
|
|
369
|
+
temperature: data.temperature,
|
|
370
|
+
top_p: data.top_p
|
|
371
|
+
};
|
|
372
|
+
if (steps !== undefined)
|
|
373
|
+
raw.steps = steps;
|
|
374
|
+
if (Object.keys(permission).length > 0)
|
|
375
|
+
raw.permission = permission;
|
|
376
|
+
if (Object.keys(options).length > 0)
|
|
377
|
+
raw.options = options;
|
|
378
|
+
for (const key of Object.keys(raw)) {
|
|
379
|
+
if (raw[key] === undefined)
|
|
380
|
+
delete raw[key];
|
|
381
|
+
}
|
|
382
|
+
return raw;
|
|
383
|
+
}
|
|
384
|
+
function loadAgentsFromDir(dir, source, agents, diagnostics) {
|
|
385
|
+
for (const subdir of ["agent", "agents"]) {
|
|
386
|
+
const root = join3(dir, subdir);
|
|
387
|
+
for (const filePath of listMarkdownFiles(root)) {
|
|
388
|
+
try {
|
|
389
|
+
const content = readFileSync2(filePath, "utf8");
|
|
390
|
+
const { data, body } = parseFrontmatter(content);
|
|
391
|
+
const name = agentNameFromPath(dir, filePath);
|
|
392
|
+
if (!name) {
|
|
393
|
+
diagnostics.push(`${filePath}: agent name is empty`);
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
const raw = normalizeOpenCodeAgentRaw(data, body.trim());
|
|
397
|
+
agents.set(name, { name, source, path: filePath, raw });
|
|
398
|
+
} catch (error) {
|
|
399
|
+
diagnostics.push(`${filePath}: ${formatError(error)}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function loadOpenCodeAgents(options) {
|
|
405
|
+
const agents = new Map;
|
|
406
|
+
const diagnostics = [];
|
|
407
|
+
if (options.includeGlobal) {
|
|
408
|
+
loadAgentsFromDir(options.globalConfigDir ?? openCodeGlobalConfigDir(), "global", agents, diagnostics);
|
|
409
|
+
}
|
|
410
|
+
if (options.includeProject) {
|
|
411
|
+
loadAgentsFromDir(join3(options.cwd, ".opencode"), "local", agents, diagnostics);
|
|
412
|
+
}
|
|
413
|
+
return { agents, diagnostics };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// settings.ts
|
|
417
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
418
|
+
import { join as join4 } from "node:path";
|
|
419
|
+
import { getAgentDir as getAgentDir2 } from "@earendil-works/pi-coding-agent";
|
|
420
|
+
var DEFAULT_OPENCODE_SETTINGS = {
|
|
421
|
+
showGlobalAgents: false,
|
|
422
|
+
showLocalAgents: false
|
|
423
|
+
};
|
|
424
|
+
var LANDSTRIP_SETTING_KEYS = new Set(["opencode"]);
|
|
425
|
+
var OPENCODE_SETTING_KEYS = new Set(["showGlobalAgents", "showLocalAgents"]);
|
|
426
|
+
function readJsonObject(path) {
|
|
427
|
+
if (!existsSync3(path))
|
|
428
|
+
return {};
|
|
429
|
+
try {
|
|
430
|
+
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
431
|
+
if (!isRecord2(parsed)) {
|
|
432
|
+
return { warning: `${path}: settings.json must contain a JSON object` };
|
|
433
|
+
}
|
|
434
|
+
return { value: parsed };
|
|
435
|
+
} catch (error) {
|
|
436
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
437
|
+
return { warning: `${path}: ${message}` };
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
function readBoolean(section, key, path, source, warnings) {
|
|
441
|
+
if (!(key in section))
|
|
442
|
+
return;
|
|
443
|
+
const value = section[key];
|
|
444
|
+
if (typeof value === "boolean")
|
|
445
|
+
return value;
|
|
446
|
+
warnings.push(`${source}: ${path} must be a boolean`);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
function readOpenCodeSection(section, source, warnings) {
|
|
450
|
+
if (!("opencode" in section))
|
|
451
|
+
return {};
|
|
452
|
+
if (!isRecord2(section.opencode)) {
|
|
453
|
+
warnings.push(`${source}: landstrip.opencode must be a JSON object`);
|
|
454
|
+
return {};
|
|
455
|
+
}
|
|
456
|
+
const opencode = section.opencode;
|
|
457
|
+
for (const key of Object.keys(opencode)) {
|
|
458
|
+
if (!OPENCODE_SETTING_KEYS.has(key)) {
|
|
459
|
+
warnings.push(`${source}: unknown landstrip.opencode setting ${key}`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
const settings = {};
|
|
463
|
+
const showGlobalAgents = readBoolean(opencode, "showGlobalAgents", "landstrip.opencode.showGlobalAgents", source, warnings);
|
|
464
|
+
if (showGlobalAgents !== undefined)
|
|
465
|
+
settings.showGlobalAgents = showGlobalAgents;
|
|
466
|
+
const showLocalAgents = readBoolean(opencode, "showLocalAgents", "landstrip.opencode.showLocalAgents", source, warnings);
|
|
467
|
+
if (showLocalAgents !== undefined)
|
|
468
|
+
settings.showLocalAgents = showLocalAgents;
|
|
469
|
+
return settings;
|
|
470
|
+
}
|
|
471
|
+
function readLandstripSection(root, source, warnings) {
|
|
472
|
+
if (!root || !("landstrip" in root))
|
|
473
|
+
return {};
|
|
474
|
+
if (!isRecord2(root.landstrip)) {
|
|
475
|
+
warnings.push(`${source}: landstrip must be a JSON object`);
|
|
476
|
+
return {};
|
|
477
|
+
}
|
|
478
|
+
const section = root.landstrip;
|
|
479
|
+
for (const key of Object.keys(section)) {
|
|
480
|
+
if (!LANDSTRIP_SETTING_KEYS.has(key)) {
|
|
481
|
+
warnings.push(`${source}: unknown landstrip setting ${key}`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return readOpenCodeSection(section, source, warnings);
|
|
485
|
+
}
|
|
486
|
+
function resolveLandstripSettings(cwd, includeProject = true, piAgentDir = getAgentDir2()) {
|
|
487
|
+
const warnings = [];
|
|
488
|
+
const globalPath = join4(piAgentDir, "settings.json");
|
|
489
|
+
const projectPath = join4(cwd, ".pi", "settings.json");
|
|
490
|
+
const globalFile = readJsonObject(globalPath);
|
|
491
|
+
if (globalFile.warning)
|
|
492
|
+
warnings.push(globalFile.warning);
|
|
493
|
+
const projectFile = includeProject ? readJsonObject(projectPath) : {};
|
|
494
|
+
if (projectFile.warning)
|
|
495
|
+
warnings.push(projectFile.warning);
|
|
496
|
+
const settings = {
|
|
497
|
+
opencode: {
|
|
498
|
+
...DEFAULT_OPENCODE_SETTINGS,
|
|
499
|
+
...readLandstripSection(globalFile.value, globalPath, warnings),
|
|
500
|
+
...includeProject ? readLandstripSection(projectFile.value, projectPath, warnings) : {}
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
return { settings, warnings };
|
|
504
|
+
}
|
|
505
|
+
|
|
245
506
|
// agents.ts
|
|
246
507
|
var AGENT_FIELDS = new Set([
|
|
247
508
|
"name",
|
|
@@ -290,11 +551,11 @@ function normalizePermissions(value) {
|
|
|
290
551
|
return Object.entries(value).flatMap(([permission, rules]) => permissionEntries(permission, rules));
|
|
291
552
|
}
|
|
292
553
|
function legacyConfigWarnings(piAgentDir) {
|
|
293
|
-
const path =
|
|
294
|
-
if (!
|
|
554
|
+
const path = join5(piAgentDir, "settings.json");
|
|
555
|
+
if (!existsSync4(path))
|
|
295
556
|
return [];
|
|
296
557
|
try {
|
|
297
|
-
const value = JSON.parse(
|
|
558
|
+
const value = JSON.parse(readFileSync4(path, "utf8"));
|
|
298
559
|
if (!isRecord2(value))
|
|
299
560
|
return [];
|
|
300
561
|
const fields = ["agent", "permission", "subagents", "maxSubagents"].filter((field) => value[field] !== undefined);
|
|
@@ -357,10 +618,10 @@ function normalizeAgent(name, raw, source) {
|
|
|
357
618
|
providerOptions
|
|
358
619
|
};
|
|
359
620
|
}
|
|
360
|
-
function loadAgentCatalog(cwd, piAgentDir =
|
|
621
|
+
function loadAgentCatalog(cwd, piAgentDir = getAgentDir3(), includeProject = true) {
|
|
361
622
|
const warnings = [
|
|
362
623
|
...legacyConfigWarnings(piAgentDir),
|
|
363
|
-
...includeProject ? legacyConfigWarnings(
|
|
624
|
+
...includeProject ? legacyConfigWarnings(join5(cwd, ".pi")) : []
|
|
364
625
|
];
|
|
365
626
|
const diagnostics = [];
|
|
366
627
|
let maxSubagents = 0;
|
|
@@ -374,7 +635,24 @@ function loadAgentCatalog(cwd, piAgentDir = getAgentDir2(), includeProject = tru
|
|
|
374
635
|
} catch (error) {
|
|
375
636
|
diagnostics.push(formatError(error));
|
|
376
637
|
}
|
|
638
|
+
const { settings, warnings: settingsWarnings } = resolveLandstripSettings(cwd, includeProject, piAgentDir);
|
|
639
|
+
warnings.push(...settingsWarnings);
|
|
640
|
+
const openCode = loadOpenCodeAgents({
|
|
641
|
+
cwd,
|
|
642
|
+
includeGlobal: settings.opencode.showGlobalAgents,
|
|
643
|
+
includeProject: settings.opencode.showLocalAgents && includeProject
|
|
644
|
+
});
|
|
645
|
+
diagnostics.push(...openCode.diagnostics);
|
|
377
646
|
const normalized = new Map;
|
|
647
|
+
for (const imported of openCode.agents.values()) {
|
|
648
|
+
try {
|
|
649
|
+
const agent = normalizeAgent(imported.name, imported.raw, imported.source);
|
|
650
|
+
if (agent)
|
|
651
|
+
normalized.set(imported.name, agent);
|
|
652
|
+
} catch (error) {
|
|
653
|
+
diagnostics.push(`${imported.path}: ${formatError(error)}`);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
378
656
|
if (subagents.agent !== undefined && !isRecord2(subagents.agent)) {
|
|
379
657
|
diagnostics.push("subagents.agent must be an object");
|
|
380
658
|
} else if (isRecord2(subagents.agent)) {
|
|
@@ -388,6 +666,8 @@ function loadAgentCatalog(cwd, piAgentDir = getAgentDir2(), includeProject = tru
|
|
|
388
666
|
const agent = normalizeAgent(name, value, agentSources.get(name) ?? "built-in");
|
|
389
667
|
if (agent)
|
|
390
668
|
normalized.set(name, agent);
|
|
669
|
+
else
|
|
670
|
+
normalized.delete(name);
|
|
391
671
|
} catch (error) {
|
|
392
672
|
diagnostics.push(formatError(error));
|
|
393
673
|
}
|
|
@@ -878,14 +1158,22 @@ var MAX_DEPTH = 3;
|
|
|
878
1158
|
var packageDir2 = dirname2(fileURLToPath2(import.meta.url));
|
|
879
1159
|
var MAX_TASK_OUTPUT_BYTES = 64 * 1024;
|
|
880
1160
|
var INSPECTOR_BODY_LINES = 16;
|
|
881
|
-
var
|
|
882
|
-
var MIN_SUPPORTED_PI_MINOR = 80;
|
|
883
|
-
var MIN_SUPPORTED_PI_PATCH = 6;
|
|
1161
|
+
var MIN_SUPPORTED_PI_VERSION = [0, 82, 0];
|
|
884
1162
|
var cachedPiPackage;
|
|
885
1163
|
var piPackageResolved = false;
|
|
886
1164
|
function isSupportedPiVersion(version) {
|
|
887
|
-
|
|
888
|
-
|
|
1165
|
+
if (version.length !== 3 || !version.every((part) => Number.isInteger(part) && part >= 0)) {
|
|
1166
|
+
return false;
|
|
1167
|
+
}
|
|
1168
|
+
for (let i = 0;i < 3; i++) {
|
|
1169
|
+
const part = version[i];
|
|
1170
|
+
const min = MIN_SUPPORTED_PI_VERSION[i];
|
|
1171
|
+
if (part > min)
|
|
1172
|
+
return true;
|
|
1173
|
+
if (part < min)
|
|
1174
|
+
return false;
|
|
1175
|
+
}
|
|
1176
|
+
return true;
|
|
889
1177
|
}
|
|
890
1178
|
function resolvePiPackage() {
|
|
891
1179
|
if (piPackageResolved)
|
|
@@ -895,8 +1183,8 @@ function resolvePiPackage() {
|
|
|
895
1183
|
const entry = fileURLToPath2(import.meta.resolve("@earendil-works/pi-coding-agent"));
|
|
896
1184
|
let dir2 = dirname2(entry);
|
|
897
1185
|
for (;; ) {
|
|
898
|
-
const pkgPath =
|
|
899
|
-
if (
|
|
1186
|
+
const pkgPath = join6(dir2, "package.json");
|
|
1187
|
+
if (existsSync5(pkgPath)) {
|
|
900
1188
|
const pkg = readPiPackage(pkgPath);
|
|
901
1189
|
if (pkg)
|
|
902
1190
|
return pkg;
|
|
@@ -909,8 +1197,8 @@ function resolvePiPackage() {
|
|
|
909
1197
|
} catch {}
|
|
910
1198
|
let dir = packageDir2;
|
|
911
1199
|
for (;; ) {
|
|
912
|
-
const pkgPath =
|
|
913
|
-
if (
|
|
1200
|
+
const pkgPath = join6(dir, "node_modules", "@earendil-works", "pi-coding-agent", "package.json");
|
|
1201
|
+
if (existsSync5(pkgPath)) {
|
|
914
1202
|
const pkg = readPiPackage(pkgPath);
|
|
915
1203
|
if (pkg)
|
|
916
1204
|
return pkg;
|
|
@@ -923,7 +1211,7 @@ function resolvePiPackage() {
|
|
|
923
1211
|
}
|
|
924
1212
|
function readPiPackage(pkgPath) {
|
|
925
1213
|
try {
|
|
926
|
-
const value = JSON.parse(
|
|
1214
|
+
const value = JSON.parse(readFileSync5(pkgPath, "utf8"));
|
|
927
1215
|
if (!isRecord2(value) || value.name !== "@earendil-works/pi-coding-agent" || typeof value.version !== "string") {
|
|
928
1216
|
return;
|
|
929
1217
|
}
|
|
@@ -933,8 +1221,8 @@ function readPiPackage(pkgPath) {
|
|
|
933
1221
|
const binFile = typeof value.bin === "string" ? value.bin : isRecord2(value.bin) && typeof value.bin.pi === "string" ? value.bin.pi : undefined;
|
|
934
1222
|
if (!binFile)
|
|
935
1223
|
return;
|
|
936
|
-
const cliEntry =
|
|
937
|
-
if (!
|
|
1224
|
+
const cliEntry = join6(dirname2(pkgPath), binFile);
|
|
1225
|
+
if (!existsSync5(cliEntry))
|
|
938
1226
|
return;
|
|
939
1227
|
cachedPiPackage = { cliEntry, version: parts.slice(0, 3) };
|
|
940
1228
|
return cachedPiPackage;
|
|
@@ -1008,7 +1296,7 @@ function messageContentText(message) {
|
|
|
1008
1296
|
`);
|
|
1009
1297
|
}
|
|
1010
1298
|
function taskTranscript(task) {
|
|
1011
|
-
if (!task.sessionFile || !
|
|
1299
|
+
if (!task.sessionFile || !existsSync5(task.sessionFile)) {
|
|
1012
1300
|
const fallback = task.error ?? task.output ?? "Child session is not available yet.";
|
|
1013
1301
|
return fallback.split(`
|
|
1014
1302
|
`);
|
|
@@ -1038,7 +1326,7 @@ function isProjectTrusted(ctx) {
|
|
|
1038
1326
|
return trustContext.isProjectTrusted?.() ?? false;
|
|
1039
1327
|
}
|
|
1040
1328
|
function dependencyRoot(path) {
|
|
1041
|
-
const marker = `${
|
|
1329
|
+
const marker = `${sep2}node_modules${sep2}`;
|
|
1042
1330
|
const index = path.lastIndexOf(marker);
|
|
1043
1331
|
return index < 0 ? undefined : path.slice(0, index + marker.length - 1);
|
|
1044
1332
|
}
|
|
@@ -1059,7 +1347,7 @@ function agentBootstrapPaths(agentDir) {
|
|
|
1059
1347
|
"bin",
|
|
1060
1348
|
"npm",
|
|
1061
1349
|
"git"
|
|
1062
|
-
].map((path) =>
|
|
1350
|
+
].map((path) => join6(agentDir, path));
|
|
1063
1351
|
}
|
|
1064
1352
|
|
|
1065
1353
|
class Semaphore {
|
|
@@ -1253,7 +1541,7 @@ function canonicalPermissionPath(path, seen = new Set) {
|
|
|
1253
1541
|
const parent = dirname2(existing);
|
|
1254
1542
|
if (parent === existing)
|
|
1255
1543
|
return path;
|
|
1256
|
-
missing.unshift(
|
|
1544
|
+
missing.unshift(basename2(existing));
|
|
1257
1545
|
existing = parent;
|
|
1258
1546
|
}
|
|
1259
1547
|
}
|
|
@@ -1275,11 +1563,11 @@ function permissionResource(tool, input, cwd) {
|
|
|
1275
1563
|
return "*";
|
|
1276
1564
|
const projectRoot = canonicalPermissionPath(resolve(cwd));
|
|
1277
1565
|
const absolutePath = canonicalPermissionPath(resolve(cwd, input.path));
|
|
1278
|
-
const projectPath =
|
|
1279
|
-
if (projectPath && !isAbsolute(projectPath) && !projectPath.startsWith(`..${
|
|
1280
|
-
return projectPath.split(
|
|
1566
|
+
const projectPath = relative2(projectRoot, absolutePath);
|
|
1567
|
+
if (projectPath && !isAbsolute(projectPath) && !projectPath.startsWith(`..${sep2}`) && projectPath !== "..") {
|
|
1568
|
+
return projectPath.split(sep2).join("/");
|
|
1281
1569
|
}
|
|
1282
|
-
return absolutePath.split(
|
|
1570
|
+
return absolutePath.split(sep2).join("/");
|
|
1283
1571
|
}
|
|
1284
1572
|
function permissionResources(tool, input, cwd) {
|
|
1285
1573
|
if (tool !== "apply_patch" || typeof input.patchText !== "string") {
|
|
@@ -1472,7 +1760,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
1472
1760
|
});
|
|
1473
1761
|
}
|
|
1474
1762
|
getAgentCatalog(ctx) {
|
|
1475
|
-
return this.loadCatalog(ctx.cwd,
|
|
1763
|
+
return this.loadCatalog(ctx.cwd, getAgentDir4(), isProjectTrusted(ctx));
|
|
1476
1764
|
}
|
|
1477
1765
|
getPrimaryAgent() {
|
|
1478
1766
|
return this.primaryAgent;
|
|
@@ -1686,7 +1974,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
1686
1974
|
}
|
|
1687
1975
|
createTaskTool(parentTask, callerRules, boundCtx) {
|
|
1688
1976
|
const cwd = boundCtx?.cwd ?? process.cwd();
|
|
1689
|
-
const catalog = this.loadCatalog(cwd,
|
|
1977
|
+
const catalog = this.loadCatalog(cwd, getAgentDir4(), boundCtx ? isProjectTrusted(boundCtx) : false);
|
|
1690
1978
|
const agents = availableSubagents(catalog).filter((agent) => !agent.hidden && permissionDecision(callerRules ?? catalog.permissions, "task", agent.name) !== "deny");
|
|
1691
1979
|
const descriptions = agents.map((agent) => `${agent.name}: ${agent.description ?? "No description"}`).join(`
|
|
1692
1980
|
`);
|
|
@@ -1771,7 +2059,7 @@ ${theme.fg("dim", `↳ /subagents ${details.taskId.slice(0, 8)} to inspect`)}`;
|
|
|
1771
2059
|
async execute(input, ctx, signal, parentTask, callerRules, update) {
|
|
1772
2060
|
if (!input.prompt.trim())
|
|
1773
2061
|
throw new Error("Task prompt cannot be empty");
|
|
1774
|
-
const catalog = this.loadCatalog(ctx.cwd,
|
|
2062
|
+
const catalog = this.loadCatalog(ctx.cwd, getAgentDir4(), isProjectTrusted(ctx));
|
|
1775
2063
|
for (const warning of catalog.warnings)
|
|
1776
2064
|
ctx.ui.notify(warning, "warning");
|
|
1777
2065
|
for (const diagnostic of catalog.diagnostics)
|
|
@@ -1811,7 +2099,7 @@ ${catalog.diagnostics.join(`
|
|
|
1811
2099
|
if (task.agent !== agent.name) {
|
|
1812
2100
|
throw new Error(`Task ${task.id} belongs to agent ${task.agent}, not ${agent.name}`);
|
|
1813
2101
|
}
|
|
1814
|
-
if (!this.running.has(task.id) && (!task.sessionDir || !
|
|
2102
|
+
if (!this.running.has(task.id) && (!task.sessionDir || !existsSync5(task.sessionDir)) && (!task.sessionFile || !existsSync5(task.sessionFile))) {
|
|
1815
2103
|
throw new Error(`Task ${task.id} session is unavailable`);
|
|
1816
2104
|
}
|
|
1817
2105
|
} else {
|
|
@@ -1921,7 +2209,7 @@ ${catalog.diagnostics.join(`
|
|
|
1921
2209
|
createRecord(input, ctx, parentTask, agent, depth) {
|
|
1922
2210
|
const parentSession = parentTask?.sessionFile ?? ctx.sessionManager.getSessionFile();
|
|
1923
2211
|
const id = randomUUID();
|
|
1924
|
-
const sessionDir =
|
|
2212
|
+
const sessionDir = join6(getAgentDir4(), "sessions", "pi-landstrip", ctx.sessionManager.getSessionId(), id);
|
|
1925
2213
|
mkdirSync2(sessionDir, { recursive: true });
|
|
1926
2214
|
const task = {
|
|
1927
2215
|
version: 1,
|
|
@@ -2111,7 +2399,7 @@ ${catalog.diagnostics.join(`
|
|
|
2111
2399
|
"--mode",
|
|
2112
2400
|
"rpc",
|
|
2113
2401
|
"--extension",
|
|
2114
|
-
|
|
2402
|
+
join6(packageDir2, "index.ts"),
|
|
2115
2403
|
...workerExtensions.flatMap((extension) => ["--extension", extension.entry]),
|
|
2116
2404
|
...task.sessionFile ? ["--session", task.sessionFile] : task.sessionDir ? ["--session-dir", task.sessionDir] : [],
|
|
2117
2405
|
"--model",
|
|
@@ -2126,8 +2414,8 @@ ${catalog.diagnostics.join(`
|
|
|
2126
2414
|
];
|
|
2127
2415
|
const config = { rules, task, taskEnabled, steps: agent.steps };
|
|
2128
2416
|
const publicContext = this.taskContext(task, ctx);
|
|
2129
|
-
const temp = mkdtempSync(
|
|
2130
|
-
const agentDir =
|
|
2417
|
+
const temp = mkdtempSync(join6(tmpdir(), `pi-landstrip-task-${task.id}-`));
|
|
2418
|
+
const agentDir = getAgentDir4();
|
|
2131
2419
|
let launch;
|
|
2132
2420
|
let rpc;
|
|
2133
2421
|
const abortWorker = () => void rpc?.stop().catch(() => {
|
|
@@ -2158,9 +2446,9 @@ ${catalog.diagnostics.join(`
|
|
|
2158
2446
|
...new Set([
|
|
2159
2447
|
ctx.cwd,
|
|
2160
2448
|
...agentBootstrapPaths(agentDir),
|
|
2161
|
-
|
|
2449
|
+
join6(homedir3(), ".agents", "skills"),
|
|
2162
2450
|
packageDir2,
|
|
2163
|
-
|
|
2451
|
+
join6(packageDir2, "node_modules"),
|
|
2164
2452
|
invocation.command,
|
|
2165
2453
|
cliRoot,
|
|
2166
2454
|
extensionRoot,
|
|
@@ -2258,7 +2546,7 @@ ${catalog.diagnostics.join(`
|
|
|
2258
2546
|
throw new Error("Unable to resolve the running Pi package; process-backed subagents are unavailable");
|
|
2259
2547
|
}
|
|
2260
2548
|
if (!isSupportedPiVersion(pkg.version)) {
|
|
2261
|
-
throw new Error(`Process-backed subagents require Pi >=0.
|
|
2549
|
+
throw new Error(`Process-backed subagents require Pi >=0.82.0; found ${pkg.version.join(".")}`);
|
|
2262
2550
|
}
|
|
2263
2551
|
}
|
|
2264
2552
|
async handleWorkerRequest(task, rules, ctx, request) {
|
|
@@ -2382,7 +2670,7 @@ ${renderTaskResult(task.id, "error", message)}`);
|
|
|
2382
2670
|
if (!directory)
|
|
2383
2671
|
throw new Error(`Task ${task.id} has no artifact directory`);
|
|
2384
2672
|
mkdirSync2(directory, { recursive: true });
|
|
2385
|
-
const artifactPath =
|
|
2673
|
+
const artifactPath = join6(directory, `${kind}.txt`);
|
|
2386
2674
|
const bounded = boundTaskOutput(value, artifactPath);
|
|
2387
2675
|
if (kind === "output")
|
|
2388
2676
|
task.outputFile = artifactPath;
|
|
@@ -2472,7 +2760,7 @@ ${renderTaskResult(task.id, failed ? "error" : "completed", value)}`;
|
|
|
2472
2760
|
this.primaryAgent = undefined;
|
|
2473
2761
|
this.primaryRules = undefined;
|
|
2474
2762
|
this.primaryConfigurationError = false;
|
|
2475
|
-
const catalog = this.loadCatalog(ctx.cwd,
|
|
2763
|
+
const catalog = this.loadCatalog(ctx.cwd, getAgentDir4(), isProjectTrusted(ctx));
|
|
2476
2764
|
for (const warning of catalog.warnings)
|
|
2477
2765
|
ctx.ui.notify(warning, "warning");
|
|
2478
2766
|
this.maxSubagents = catalog.maxSubagents;
|
|
@@ -2628,23 +2916,23 @@ var NETWORK_PERMISSION_OPTIONS = [
|
|
|
2628
2916
|
{ label: "Abort (keep blocked)", key: "esc", action: "abort" }
|
|
2629
2917
|
];
|
|
2630
2918
|
function loadSandboxConfig(cwd, includeProject) {
|
|
2631
|
-
const projectConfigPath =
|
|
2632
|
-
const globalConfigPath =
|
|
2633
|
-
if (!
|
|
2634
|
-
const templatePath =
|
|
2919
|
+
const projectConfigPath = join7(cwd, ".pi", "sandbox.json");
|
|
2920
|
+
const globalConfigPath = join7(getAgentDir5(), "sandbox.json");
|
|
2921
|
+
if (!existsSync6(globalConfigPath)) {
|
|
2922
|
+
const templatePath = join7(packageDir3, "sandbox.json");
|
|
2635
2923
|
mkdirSync3(dirname3(globalConfigPath), { recursive: true });
|
|
2636
|
-
writeFileSync3(globalConfigPath,
|
|
2924
|
+
writeFileSync3(globalConfigPath, readFileSync6(templatePath, "utf-8"), "utf-8");
|
|
2637
2925
|
}
|
|
2638
|
-
let globalConfig = JSON.parse(
|
|
2926
|
+
let globalConfig = JSON.parse(readFileSync6(join7(packageDir3, "sandbox.json"), "utf-8"));
|
|
2639
2927
|
try {
|
|
2640
|
-
const override = JSON.parse(
|
|
2928
|
+
const override = JSON.parse(readFileSync6(globalConfigPath, "utf-8"));
|
|
2641
2929
|
globalConfig = deepMerge(globalConfig, override);
|
|
2642
2930
|
} catch (error) {
|
|
2643
2931
|
console.error(`Warning: Could not parse ${globalConfigPath}: ${error}`);
|
|
2644
2932
|
}
|
|
2645
|
-
if (includeProject &&
|
|
2933
|
+
if (includeProject && existsSync6(projectConfigPath)) {
|
|
2646
2934
|
try {
|
|
2647
|
-
const projectConfig = JSON.parse(
|
|
2935
|
+
const projectConfig = JSON.parse(readFileSync6(projectConfigPath, "utf-8"));
|
|
2648
2936
|
const merged = deepMerge(globalConfig, projectConfig);
|
|
2649
2937
|
merged.windows.backend = globalConfig.windows.backend;
|
|
2650
2938
|
return merged;
|
|
@@ -2719,10 +3007,10 @@ function getConfigPaths(cwd) {
|
|
|
2719
3007
|
return getPiConfigPaths(cwd, "sandbox.json");
|
|
2720
3008
|
}
|
|
2721
3009
|
function readOrEmptyConfig(configPath) {
|
|
2722
|
-
if (!
|
|
3010
|
+
if (!existsSync6(configPath))
|
|
2723
3011
|
return {};
|
|
2724
3012
|
try {
|
|
2725
|
-
return JSON.parse(
|
|
3013
|
+
return JSON.parse(readFileSync6(configPath, "utf-8"));
|
|
2726
3014
|
} catch {
|
|
2727
3015
|
return {};
|
|
2728
3016
|
}
|
|
@@ -2734,7 +3022,7 @@ function writeConfigFile(configPath, config) {
|
|
|
2734
3022
|
}
|
|
2735
3023
|
function getSandboxConfigWriteTarget(cwd, includeProject = true) {
|
|
2736
3024
|
const { globalPath, projectPath } = getConfigPaths(cwd);
|
|
2737
|
-
if (includeProject &&
|
|
3025
|
+
if (includeProject && existsSync6(projectPath)) {
|
|
2738
3026
|
return { scope: "project", path: projectPath };
|
|
2739
3027
|
}
|
|
2740
3028
|
return { scope: "global", path: globalPath };
|
|
@@ -2810,11 +3098,11 @@ function canonicalizePath(filePath, cwd) {
|
|
|
2810
3098
|
} catch {
|
|
2811
3099
|
const tail = [];
|
|
2812
3100
|
let probe = abs;
|
|
2813
|
-
while (!
|
|
3101
|
+
while (!existsSync6(probe)) {
|
|
2814
3102
|
const parent = dirname3(probe);
|
|
2815
3103
|
if (parent === probe)
|
|
2816
3104
|
return abs;
|
|
2817
|
-
tail.unshift(
|
|
3105
|
+
tail.unshift(basename3(probe));
|
|
2818
3106
|
probe = parent;
|
|
2819
3107
|
}
|
|
2820
3108
|
try {
|
|
@@ -2835,26 +3123,26 @@ function matchesPattern(filePath, patterns, cwd) {
|
|
|
2835
3123
|
const escaped = absPattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*\/|\*\*|\*/g, (token) => token === "**/" ? "(?:.*/)?" : token === "**" ? ".*" : "[^/]*");
|
|
2836
3124
|
return new RegExp(`^${escaped}$`).test(abs);
|
|
2837
3125
|
}
|
|
2838
|
-
const
|
|
2839
|
-
return abs === absPattern || abs.startsWith(absPattern +
|
|
3126
|
+
const sep3 = absPattern.endsWith("/") ? "" : "/";
|
|
3127
|
+
return abs === absPattern || abs.startsWith(absPattern + sep3);
|
|
2840
3128
|
});
|
|
2841
3129
|
}
|
|
2842
3130
|
function normalizeBlockedPath(path, cwd) {
|
|
2843
3131
|
const nativePath = process.platform === "win32" && /^\/[a-zA-Z](?:\/|$)/.test(path) ? `${path[1]}:${path.slice(2)}` : path;
|
|
2844
|
-
return canonicalizePath(isAbsolute2(nativePath) ? nativePath :
|
|
3132
|
+
return canonicalizePath(isAbsolute2(nativePath) ? nativePath : join7(cwd, nativePath), cwd);
|
|
2845
3133
|
}
|
|
2846
3134
|
function pathUnderDirectory(filePath, dir) {
|
|
2847
3135
|
const normalizedFilePath = normalizePathSeparators(filePath);
|
|
2848
3136
|
const normalizedDir = normalizePathSeparators(dir);
|
|
2849
3137
|
if (normalizedFilePath === normalizedDir)
|
|
2850
3138
|
return true;
|
|
2851
|
-
const
|
|
2852
|
-
return normalizedFilePath.startsWith(normalizedDir +
|
|
3139
|
+
const sep3 = normalizedDir.endsWith("/") ? "" : "/";
|
|
3140
|
+
return normalizedFilePath.startsWith(normalizedDir + sep3);
|
|
2853
3141
|
}
|
|
2854
3142
|
function sessionScopeFor(filePath, baseDirectory) {
|
|
2855
3143
|
const dir = dirname3(filePath);
|
|
2856
3144
|
const normalizedDir = normalizePathSeparators(dir);
|
|
2857
|
-
const home =
|
|
3145
|
+
const home = homedir4();
|
|
2858
3146
|
const boundaries = new Set;
|
|
2859
3147
|
if (home)
|
|
2860
3148
|
boundaries.add(home);
|
|
@@ -2870,7 +3158,7 @@ function sessionScopeFor(filePath, baseDirectory) {
|
|
|
2870
3158
|
const first = rest.split("/")[0];
|
|
2871
3159
|
if (!first)
|
|
2872
3160
|
return filePath;
|
|
2873
|
-
return
|
|
3161
|
+
return join7(boundary, first);
|
|
2874
3162
|
}
|
|
2875
3163
|
}
|
|
2876
3164
|
if (pathUnderDirectory(dir, baseDirectory))
|
|
@@ -3143,8 +3431,8 @@ function writeEnvFile(env, proxyPort, proxyToken) {
|
|
|
3143
3431
|
lines.push("export NO_PROXY=''");
|
|
3144
3432
|
lines.push("export no_proxy=''");
|
|
3145
3433
|
}
|
|
3146
|
-
const dir = mkdtempSync2(
|
|
3147
|
-
const path =
|
|
3434
|
+
const dir = mkdtempSync2(join7(tmpdir2(), "pi-landstrip-env-"));
|
|
3435
|
+
const path = join7(dir, "env.sh");
|
|
3148
3436
|
writeFileSync3(path, lines.join(`
|
|
3149
3437
|
`), "utf-8");
|
|
3150
3438
|
return { dir, path };
|
|
@@ -3285,7 +3573,7 @@ function createLandstripIntegration(options = {}) {
|
|
|
3285
3573
|
if (!isAbsolute2(requestedEntry)) {
|
|
3286
3574
|
throw new Error("Worker extension entry must be an absolute path or file URL");
|
|
3287
3575
|
}
|
|
3288
|
-
if (!
|
|
3576
|
+
if (!existsSync6(requestedEntry)) {
|
|
3289
3577
|
throw new Error(`Worker extension entry does not exist: ${requestedEntry}`);
|
|
3290
3578
|
}
|
|
3291
3579
|
const entry = realpathSync2(requestedEntry);
|
|
@@ -3327,7 +3615,7 @@ function createLandstripIntegration(options = {}) {
|
|
|
3327
3615
|
const paths = mergeAllowances(config.filesystem.allowRead, sessionAllowedReadPaths, allowances?.readPaths);
|
|
3328
3616
|
if (process.platform !== "win32")
|
|
3329
3617
|
return paths;
|
|
3330
|
-
return paths.filter((path) => !path.startsWith("/") && (path.includes("*") ||
|
|
3618
|
+
return paths.filter((path) => !path.startsWith("/") && (path.includes("*") || existsSync6(expandPath(path, cwd))));
|
|
3331
3619
|
}
|
|
3332
3620
|
function getEffectiveAllowWrite(config, allowances) {
|
|
3333
3621
|
const paths = mergeAllowances(config.filesystem.allowWrite, sessionAllowedWritePaths, allowances?.writePaths);
|
|
@@ -3433,8 +3721,8 @@ function createLandstripIntegration(options = {}) {
|
|
|
3433
3721
|
};
|
|
3434
3722
|
}
|
|
3435
3723
|
function writePolicyFile(cwd, proxyPort, allowances) {
|
|
3436
|
-
const dir = mkdtempSync2(
|
|
3437
|
-
const path =
|
|
3724
|
+
const dir = mkdtempSync2(join7(tmpdir2(), "pi-landstrip-"));
|
|
3725
|
+
const path = join7(dir, "policy.json");
|
|
3438
3726
|
writeFileSync3(path, JSON.stringify(buildLandstripPolicy(cwd, proxyPort, allowances), null, 2) + `
|
|
3439
3727
|
`, "utf-8");
|
|
3440
3728
|
return { dir, path };
|
|
@@ -3861,7 +4149,7 @@ function createLandstripIntegration(options = {}) {
|
|
|
3861
4149
|
function createLandstripBashOps(ctx, callbacks = {}, allowances = { domains: [], readPaths: [], writePaths: [], targets: [] }) {
|
|
3862
4150
|
return {
|
|
3863
4151
|
async exec(command, cwd, { onData, signal, timeout, env }) {
|
|
3864
|
-
if (!
|
|
4152
|
+
if (!existsSync6(cwd))
|
|
3865
4153
|
throw new Error(`Working directory does not exist: ${cwd}`);
|
|
3866
4154
|
const { shell, args } = getShellConfig(SettingsManager.create(cwd).getShellPath());
|
|
3867
4155
|
const config = loadConfig(cwd);
|
|
@@ -4225,7 +4513,7 @@ ${message}
|
|
|
4225
4513
|
networkLabel = `${config.network.allowedDomains.length} domains`;
|
|
4226
4514
|
networkColor = "accent";
|
|
4227
4515
|
}
|
|
4228
|
-
const
|
|
4516
|
+
const sep3 = theme.fg("dim", "·");
|
|
4229
4517
|
const net = theme.fg(networkColor, networkLabel);
|
|
4230
4518
|
const write = theme.fg("accent", `${config.filesystem.allowWrite.length} write paths`);
|
|
4231
4519
|
const fields = [`${dot} ${label}`, net, write];
|
|
@@ -4235,7 +4523,7 @@ ${message}
|
|
|
4235
4523
|
const label2 = backend === "restrictedUser" ? "restricted user" : `${config.windows.appContainerMode} AppContainer`;
|
|
4236
4524
|
fields.splice(1, 0, theme.fg(standard ? "warning" : "accent", label2));
|
|
4237
4525
|
}
|
|
4238
|
-
setTuiStatus(ctx, "sandbox", fields.join(` ${
|
|
4526
|
+
setTuiStatus(ctx, "sandbox", fields.join(` ${sep3} `));
|
|
4239
4527
|
}
|
|
4240
4528
|
const headlessWarnings = new Set;
|
|
4241
4529
|
function warnUnsandboxed(ctx, message, level) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-landstrip",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.36",
|
|
4
4
|
"description": "Sandbox-aware Pi subagents and interactive command permissions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"landstrip",
|
|
@@ -40,14 +40,15 @@
|
|
|
40
40
|
"ci:test": "npm run test"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@landstrip/landstrip": "^0.17.
|
|
44
|
-
"minimatch": "^10.2.5"
|
|
43
|
+
"@landstrip/landstrip": "^0.17.36",
|
|
44
|
+
"minimatch": "^10.2.5",
|
|
45
|
+
"yaml": "^2.9.0"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|
|
47
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
48
|
+
"@earendil-works/pi-coding-agent": "^0.82.0",
|
|
48
49
|
"@earendil-works/pi-tui": "^0.81.1",
|
|
49
50
|
"@types/node": "^24.0.0",
|
|
50
|
-
"oxfmt": "^0.
|
|
51
|
+
"oxfmt": "^0.60.0",
|
|
51
52
|
"oxlint": "^1.68.0",
|
|
52
53
|
"typebox": "^1.3.6",
|
|
53
54
|
"typescript": "^5.0.0",
|