pi-landstrip 0.17.36 → 0.17.38
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 +20 -10
- package/dist/index.ts +121 -157
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -208,17 +208,25 @@ 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`
|
|
212
|
-
entry, plus optional `landstrip.opencode` import flags:
|
|
211
|
+
`settings.json` only needs the normal package entry for the extension:
|
|
213
212
|
|
|
214
213
|
```json
|
|
215
214
|
{
|
|
216
|
-
"packages": ["npm:pi-landstrip"]
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
215
|
+
"packages": ["npm:pi-landstrip"]
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Optional OpenCode import flags live in `subagents.json` as top-level `opencode`:
|
|
220
|
+
|
|
221
|
+
```json
|
|
222
|
+
{
|
|
223
|
+
"maxSubagents": 0,
|
|
224
|
+
"opencode": {
|
|
225
|
+
"showGlobalAgents": false,
|
|
226
|
+
"showLocalAgents": false
|
|
227
|
+
},
|
|
228
|
+
"subagents": {
|
|
229
|
+
"agent": {}
|
|
222
230
|
}
|
|
223
231
|
}
|
|
224
232
|
```
|
|
@@ -236,8 +244,8 @@ from `subagents.json` (built-in, global, and local) take precedence over OpenCod
|
|
|
236
244
|
imports with the same name; conflicts are silent. Local OpenCode agents are
|
|
237
245
|
skipped when the project is untrusted, regardless of the flag.
|
|
238
246
|
|
|
239
|
-
`subagents.json` accepts
|
|
240
|
-
fields belong in `sandbox.json`.
|
|
247
|
+
`subagents.json` accepts top-level `maxSubagents`, `opencode`, and `subagents`;
|
|
248
|
+
sandbox fields belong in `sandbox.json`.
|
|
241
249
|
|
|
242
250
|
The Windows sandbox fields are:
|
|
243
251
|
|
|
@@ -331,6 +339,8 @@ follows, then remove the old fields:
|
|
|
331
339
|
- Convert legacy `tools` booleans to explicit `permission` rules.
|
|
332
340
|
- Move Markdown agent prompts into each `subagents.agent.<name>.prompt` string.
|
|
333
341
|
- Put the worker limit in top-level `maxSubagents`; zero disables delegation.
|
|
342
|
+
- Move `landstrip.opencode` flags from Pi `settings.json` to top-level
|
|
343
|
+
`opencode` in `subagents.json`.
|
|
334
344
|
- Use `~/.pi/agent/subagents.json` for global configuration and
|
|
335
345
|
`.pi/subagents.json` for trusted project configuration.
|
|
336
346
|
- Leave sandbox policy in `~/.pi/agent/sandbox.json` and `.pi/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 existsSync5,
|
|
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,12 +19,12 @@ 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 basename3, dirname as dirname3, isAbsolute as isAbsolute2, join as
|
|
22
|
+
import { basename as basename3, dirname as dirname3, isAbsolute as isAbsolute2, join as join6, 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 getAgentDir4,
|
|
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 existsSync4,
|
|
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 as basename2, dirname as dirname2, isAbsolute, join as
|
|
84
|
+
import { basename as basename2, dirname as dirname2, isAbsolute, join as join5, 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 getAgentDir3,
|
|
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 existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
96
|
+
import { join as join4 } from "node:path";
|
|
97
97
|
import { minimatch } from "minimatch";
|
|
98
|
-
import { getAgentDir as
|
|
98
|
+
import { getAgentDir as getAgentDir2 } from "@earendil-works/pi-coding-agent";
|
|
99
99
|
|
|
100
100
|
// config.ts
|
|
101
101
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
@@ -160,6 +160,38 @@ class AsyncQueue {
|
|
|
160
160
|
// config.ts
|
|
161
161
|
var MAX_SUBAGENTS = 16;
|
|
162
162
|
var packageDir = dirname(fileURLToPath(import.meta.url));
|
|
163
|
+
var DEFAULT_OPENCODE = {
|
|
164
|
+
showGlobalAgents: false,
|
|
165
|
+
showLocalAgents: false
|
|
166
|
+
};
|
|
167
|
+
var OPENCODE_KEYS = new Set(["showGlobalAgents", "showLocalAgents"]);
|
|
168
|
+
function readBooleanField(section, key, path) {
|
|
169
|
+
if (!(key in section))
|
|
170
|
+
return;
|
|
171
|
+
const value = section[key];
|
|
172
|
+
if (typeof value === "boolean")
|
|
173
|
+
return value;
|
|
174
|
+
throw new Error(`${path}.${key} must be a boolean`);
|
|
175
|
+
}
|
|
176
|
+
function normalizeOpenCode(value, path) {
|
|
177
|
+
if (value === undefined)
|
|
178
|
+
return {};
|
|
179
|
+
if (!isRecord2(value))
|
|
180
|
+
throw new Error(`${path} must be a JSON object`);
|
|
181
|
+
for (const key of Object.keys(value)) {
|
|
182
|
+
if (!OPENCODE_KEYS.has(key)) {
|
|
183
|
+
throw new Error(`${path}: unknown field ${key}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const result = {};
|
|
187
|
+
const showGlobalAgents = readBooleanField(value, "showGlobalAgents", path);
|
|
188
|
+
if (showGlobalAgents !== undefined)
|
|
189
|
+
result.showGlobalAgents = showGlobalAgents;
|
|
190
|
+
const showLocalAgents = readBooleanField(value, "showLocalAgents", path);
|
|
191
|
+
if (showLocalAgents !== undefined)
|
|
192
|
+
result.showLocalAgents = showLocalAgents;
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
163
195
|
function readConfig(path) {
|
|
164
196
|
if (!existsSync(path))
|
|
165
197
|
return {};
|
|
@@ -173,11 +205,27 @@ function readConfig(path) {
|
|
|
173
205
|
if (!isRecord2(value))
|
|
174
206
|
throw new Error(`${path} must contain a JSON object`);
|
|
175
207
|
for (const key of Object.keys(value)) {
|
|
176
|
-
if (key !== "maxSubagents" && key !== "subagents") {
|
|
208
|
+
if (key !== "maxSubagents" && key !== "subagents" && key !== "opencode") {
|
|
177
209
|
throw new Error(`${path}: unknown top-level field ${key}`);
|
|
178
210
|
}
|
|
179
211
|
}
|
|
180
|
-
|
|
212
|
+
const config = {};
|
|
213
|
+
if ("maxSubagents" in value) {
|
|
214
|
+
if (typeof value.maxSubagents !== "number") {
|
|
215
|
+
throw new Error(`${path}: maxSubagents must be a number`);
|
|
216
|
+
}
|
|
217
|
+
config.maxSubagents = value.maxSubagents;
|
|
218
|
+
}
|
|
219
|
+
if ("subagents" in value) {
|
|
220
|
+
if (!isRecord2(value.subagents)) {
|
|
221
|
+
throw new Error(`${path}: subagents must be an object`);
|
|
222
|
+
}
|
|
223
|
+
config.subagents = value.subagents;
|
|
224
|
+
}
|
|
225
|
+
if ("opencode" in value) {
|
|
226
|
+
config.opencode = normalizeOpenCode(value.opencode, `${path}: opencode`);
|
|
227
|
+
}
|
|
228
|
+
return config;
|
|
181
229
|
}
|
|
182
230
|
function mergeValue(base, override) {
|
|
183
231
|
if (override === undefined)
|
|
@@ -239,7 +287,11 @@ function loadLandstripConfig(cwd, includeProject = true, agentDir = getAgentDir(
|
|
|
239
287
|
}
|
|
240
288
|
if (!isRecord2(config.subagents))
|
|
241
289
|
throw new Error("subagents must be an object");
|
|
242
|
-
|
|
290
|
+
const opencode = {
|
|
291
|
+
...DEFAULT_OPENCODE,
|
|
292
|
+
...config.opencode
|
|
293
|
+
};
|
|
294
|
+
return { ...config, opencode, agentSources };
|
|
243
295
|
}
|
|
244
296
|
|
|
245
297
|
// opencode-agents.ts
|
|
@@ -413,96 +465,6 @@ function loadOpenCodeAgents(options) {
|
|
|
413
465
|
return { agents, diagnostics };
|
|
414
466
|
}
|
|
415
467
|
|
|
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
|
-
|
|
506
468
|
// agents.ts
|
|
507
469
|
var AGENT_FIELDS = new Set([
|
|
508
470
|
"name",
|
|
@@ -551,14 +513,14 @@ function normalizePermissions(value) {
|
|
|
551
513
|
return Object.entries(value).flatMap(([permission, rules]) => permissionEntries(permission, rules));
|
|
552
514
|
}
|
|
553
515
|
function legacyConfigWarnings(piAgentDir) {
|
|
554
|
-
const path =
|
|
555
|
-
if (!
|
|
516
|
+
const path = join4(piAgentDir, "settings.json");
|
|
517
|
+
if (!existsSync3(path))
|
|
556
518
|
return [];
|
|
557
519
|
try {
|
|
558
|
-
const value = JSON.parse(
|
|
520
|
+
const value = JSON.parse(readFileSync3(path, "utf8"));
|
|
559
521
|
if (!isRecord2(value))
|
|
560
522
|
return [];
|
|
561
|
-
const fields = ["agent", "permission", "subagents", "maxSubagents"].filter((field) => value[field] !== undefined);
|
|
523
|
+
const fields = ["agent", "permission", "subagents", "maxSubagents", "landstrip"].filter((field) => value[field] !== undefined);
|
|
562
524
|
if (fields.length === 0)
|
|
563
525
|
return [];
|
|
564
526
|
return [
|
|
@@ -618,29 +580,31 @@ function normalizeAgent(name, raw, source) {
|
|
|
618
580
|
providerOptions
|
|
619
581
|
};
|
|
620
582
|
}
|
|
621
|
-
function loadAgentCatalog(cwd, piAgentDir =
|
|
583
|
+
function loadAgentCatalog(cwd, piAgentDir = getAgentDir2(), includeProject = true) {
|
|
622
584
|
const warnings = [
|
|
623
585
|
...legacyConfigWarnings(piAgentDir),
|
|
624
|
-
...includeProject ? legacyConfigWarnings(
|
|
586
|
+
...includeProject ? legacyConfigWarnings(join4(cwd, ".pi")) : []
|
|
625
587
|
];
|
|
626
588
|
const diagnostics = [];
|
|
627
589
|
let maxSubagents = 0;
|
|
628
590
|
let subagents = {};
|
|
629
591
|
let agentSources = new Map;
|
|
592
|
+
let showGlobalAgents = false;
|
|
593
|
+
let showLocalAgents = false;
|
|
630
594
|
try {
|
|
631
595
|
const config = loadLandstripConfig(cwd, includeProject, piAgentDir);
|
|
632
596
|
maxSubagents = config.maxSubagents;
|
|
633
597
|
subagents = config.subagents;
|
|
634
598
|
agentSources = new Map(config.agentSources);
|
|
599
|
+
showGlobalAgents = config.opencode.showGlobalAgents;
|
|
600
|
+
showLocalAgents = config.opencode.showLocalAgents;
|
|
635
601
|
} catch (error) {
|
|
636
602
|
diagnostics.push(formatError(error));
|
|
637
603
|
}
|
|
638
|
-
const { settings, warnings: settingsWarnings } = resolveLandstripSettings(cwd, includeProject, piAgentDir);
|
|
639
|
-
warnings.push(...settingsWarnings);
|
|
640
604
|
const openCode = loadOpenCodeAgents({
|
|
641
605
|
cwd,
|
|
642
|
-
includeGlobal:
|
|
643
|
-
includeProject:
|
|
606
|
+
includeGlobal: showGlobalAgents,
|
|
607
|
+
includeProject: showLocalAgents && includeProject
|
|
644
608
|
});
|
|
645
609
|
diagnostics.push(...openCode.diagnostics);
|
|
646
610
|
const normalized = new Map;
|
|
@@ -1183,8 +1147,8 @@ function resolvePiPackage() {
|
|
|
1183
1147
|
const entry = fileURLToPath2(import.meta.resolve("@earendil-works/pi-coding-agent"));
|
|
1184
1148
|
let dir2 = dirname2(entry);
|
|
1185
1149
|
for (;; ) {
|
|
1186
|
-
const pkgPath =
|
|
1187
|
-
if (
|
|
1150
|
+
const pkgPath = join5(dir2, "package.json");
|
|
1151
|
+
if (existsSync4(pkgPath)) {
|
|
1188
1152
|
const pkg = readPiPackage(pkgPath);
|
|
1189
1153
|
if (pkg)
|
|
1190
1154
|
return pkg;
|
|
@@ -1197,8 +1161,8 @@ function resolvePiPackage() {
|
|
|
1197
1161
|
} catch {}
|
|
1198
1162
|
let dir = packageDir2;
|
|
1199
1163
|
for (;; ) {
|
|
1200
|
-
const pkgPath =
|
|
1201
|
-
if (
|
|
1164
|
+
const pkgPath = join5(dir, "node_modules", "@earendil-works", "pi-coding-agent", "package.json");
|
|
1165
|
+
if (existsSync4(pkgPath)) {
|
|
1202
1166
|
const pkg = readPiPackage(pkgPath);
|
|
1203
1167
|
if (pkg)
|
|
1204
1168
|
return pkg;
|
|
@@ -1211,7 +1175,7 @@ function resolvePiPackage() {
|
|
|
1211
1175
|
}
|
|
1212
1176
|
function readPiPackage(pkgPath) {
|
|
1213
1177
|
try {
|
|
1214
|
-
const value = JSON.parse(
|
|
1178
|
+
const value = JSON.parse(readFileSync4(pkgPath, "utf8"));
|
|
1215
1179
|
if (!isRecord2(value) || value.name !== "@earendil-works/pi-coding-agent" || typeof value.version !== "string") {
|
|
1216
1180
|
return;
|
|
1217
1181
|
}
|
|
@@ -1221,8 +1185,8 @@ function readPiPackage(pkgPath) {
|
|
|
1221
1185
|
const binFile = typeof value.bin === "string" ? value.bin : isRecord2(value.bin) && typeof value.bin.pi === "string" ? value.bin.pi : undefined;
|
|
1222
1186
|
if (!binFile)
|
|
1223
1187
|
return;
|
|
1224
|
-
const cliEntry =
|
|
1225
|
-
if (!
|
|
1188
|
+
const cliEntry = join5(dirname2(pkgPath), binFile);
|
|
1189
|
+
if (!existsSync4(cliEntry))
|
|
1226
1190
|
return;
|
|
1227
1191
|
cachedPiPackage = { cliEntry, version: parts.slice(0, 3) };
|
|
1228
1192
|
return cachedPiPackage;
|
|
@@ -1296,7 +1260,7 @@ function messageContentText(message) {
|
|
|
1296
1260
|
`);
|
|
1297
1261
|
}
|
|
1298
1262
|
function taskTranscript(task) {
|
|
1299
|
-
if (!task.sessionFile || !
|
|
1263
|
+
if (!task.sessionFile || !existsSync4(task.sessionFile)) {
|
|
1300
1264
|
const fallback = task.error ?? task.output ?? "Child session is not available yet.";
|
|
1301
1265
|
return fallback.split(`
|
|
1302
1266
|
`);
|
|
@@ -1347,7 +1311,7 @@ function agentBootstrapPaths(agentDir) {
|
|
|
1347
1311
|
"bin",
|
|
1348
1312
|
"npm",
|
|
1349
1313
|
"git"
|
|
1350
|
-
].map((path) =>
|
|
1314
|
+
].map((path) => join5(agentDir, path));
|
|
1351
1315
|
}
|
|
1352
1316
|
|
|
1353
1317
|
class Semaphore {
|
|
@@ -1760,7 +1724,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
1760
1724
|
});
|
|
1761
1725
|
}
|
|
1762
1726
|
getAgentCatalog(ctx) {
|
|
1763
|
-
return this.loadCatalog(ctx.cwd,
|
|
1727
|
+
return this.loadCatalog(ctx.cwd, getAgentDir3(), isProjectTrusted(ctx));
|
|
1764
1728
|
}
|
|
1765
1729
|
getPrimaryAgent() {
|
|
1766
1730
|
return this.primaryAgent;
|
|
@@ -1974,7 +1938,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
1974
1938
|
}
|
|
1975
1939
|
createTaskTool(parentTask, callerRules, boundCtx) {
|
|
1976
1940
|
const cwd = boundCtx?.cwd ?? process.cwd();
|
|
1977
|
-
const catalog = this.loadCatalog(cwd,
|
|
1941
|
+
const catalog = this.loadCatalog(cwd, getAgentDir3(), boundCtx ? isProjectTrusted(boundCtx) : false);
|
|
1978
1942
|
const agents = availableSubagents(catalog).filter((agent) => !agent.hidden && permissionDecision(callerRules ?? catalog.permissions, "task", agent.name) !== "deny");
|
|
1979
1943
|
const descriptions = agents.map((agent) => `${agent.name}: ${agent.description ?? "No description"}`).join(`
|
|
1980
1944
|
`);
|
|
@@ -2059,7 +2023,7 @@ ${theme.fg("dim", `↳ /subagents ${details.taskId.slice(0, 8)} to inspect`)}`;
|
|
|
2059
2023
|
async execute(input, ctx, signal, parentTask, callerRules, update) {
|
|
2060
2024
|
if (!input.prompt.trim())
|
|
2061
2025
|
throw new Error("Task prompt cannot be empty");
|
|
2062
|
-
const catalog = this.loadCatalog(ctx.cwd,
|
|
2026
|
+
const catalog = this.loadCatalog(ctx.cwd, getAgentDir3(), isProjectTrusted(ctx));
|
|
2063
2027
|
for (const warning of catalog.warnings)
|
|
2064
2028
|
ctx.ui.notify(warning, "warning");
|
|
2065
2029
|
for (const diagnostic of catalog.diagnostics)
|
|
@@ -2099,7 +2063,7 @@ ${catalog.diagnostics.join(`
|
|
|
2099
2063
|
if (task.agent !== agent.name) {
|
|
2100
2064
|
throw new Error(`Task ${task.id} belongs to agent ${task.agent}, not ${agent.name}`);
|
|
2101
2065
|
}
|
|
2102
|
-
if (!this.running.has(task.id) && (!task.sessionDir || !
|
|
2066
|
+
if (!this.running.has(task.id) && (!task.sessionDir || !existsSync4(task.sessionDir)) && (!task.sessionFile || !existsSync4(task.sessionFile))) {
|
|
2103
2067
|
throw new Error(`Task ${task.id} session is unavailable`);
|
|
2104
2068
|
}
|
|
2105
2069
|
} else {
|
|
@@ -2209,7 +2173,7 @@ ${catalog.diagnostics.join(`
|
|
|
2209
2173
|
createRecord(input, ctx, parentTask, agent, depth) {
|
|
2210
2174
|
const parentSession = parentTask?.sessionFile ?? ctx.sessionManager.getSessionFile();
|
|
2211
2175
|
const id = randomUUID();
|
|
2212
|
-
const sessionDir =
|
|
2176
|
+
const sessionDir = join5(getAgentDir3(), "sessions", "pi-landstrip", ctx.sessionManager.getSessionId(), id);
|
|
2213
2177
|
mkdirSync2(sessionDir, { recursive: true });
|
|
2214
2178
|
const task = {
|
|
2215
2179
|
version: 1,
|
|
@@ -2399,7 +2363,7 @@ ${catalog.diagnostics.join(`
|
|
|
2399
2363
|
"--mode",
|
|
2400
2364
|
"rpc",
|
|
2401
2365
|
"--extension",
|
|
2402
|
-
|
|
2366
|
+
join5(packageDir2, "index.ts"),
|
|
2403
2367
|
...workerExtensions.flatMap((extension) => ["--extension", extension.entry]),
|
|
2404
2368
|
...task.sessionFile ? ["--session", task.sessionFile] : task.sessionDir ? ["--session-dir", task.sessionDir] : [],
|
|
2405
2369
|
"--model",
|
|
@@ -2414,8 +2378,8 @@ ${catalog.diagnostics.join(`
|
|
|
2414
2378
|
];
|
|
2415
2379
|
const config = { rules, task, taskEnabled, steps: agent.steps };
|
|
2416
2380
|
const publicContext = this.taskContext(task, ctx);
|
|
2417
|
-
const temp = mkdtempSync(
|
|
2418
|
-
const agentDir =
|
|
2381
|
+
const temp = mkdtempSync(join5(tmpdir(), `pi-landstrip-task-${task.id}-`));
|
|
2382
|
+
const agentDir = getAgentDir3();
|
|
2419
2383
|
let launch;
|
|
2420
2384
|
let rpc;
|
|
2421
2385
|
const abortWorker = () => void rpc?.stop().catch(() => {
|
|
@@ -2446,9 +2410,9 @@ ${catalog.diagnostics.join(`
|
|
|
2446
2410
|
...new Set([
|
|
2447
2411
|
ctx.cwd,
|
|
2448
2412
|
...agentBootstrapPaths(agentDir),
|
|
2449
|
-
|
|
2413
|
+
join5(homedir3(), ".agents", "skills"),
|
|
2450
2414
|
packageDir2,
|
|
2451
|
-
|
|
2415
|
+
join5(packageDir2, "node_modules"),
|
|
2452
2416
|
invocation.command,
|
|
2453
2417
|
cliRoot,
|
|
2454
2418
|
extensionRoot,
|
|
@@ -2670,7 +2634,7 @@ ${renderTaskResult(task.id, "error", message)}`);
|
|
|
2670
2634
|
if (!directory)
|
|
2671
2635
|
throw new Error(`Task ${task.id} has no artifact directory`);
|
|
2672
2636
|
mkdirSync2(directory, { recursive: true });
|
|
2673
|
-
const artifactPath =
|
|
2637
|
+
const artifactPath = join5(directory, `${kind}.txt`);
|
|
2674
2638
|
const bounded = boundTaskOutput(value, artifactPath);
|
|
2675
2639
|
if (kind === "output")
|
|
2676
2640
|
task.outputFile = artifactPath;
|
|
@@ -2760,7 +2724,7 @@ ${renderTaskResult(task.id, failed ? "error" : "completed", value)}`;
|
|
|
2760
2724
|
this.primaryAgent = undefined;
|
|
2761
2725
|
this.primaryRules = undefined;
|
|
2762
2726
|
this.primaryConfigurationError = false;
|
|
2763
|
-
const catalog = this.loadCatalog(ctx.cwd,
|
|
2727
|
+
const catalog = this.loadCatalog(ctx.cwd, getAgentDir3(), isProjectTrusted(ctx));
|
|
2764
2728
|
for (const warning of catalog.warnings)
|
|
2765
2729
|
ctx.ui.notify(warning, "warning");
|
|
2766
2730
|
this.maxSubagents = catalog.maxSubagents;
|
|
@@ -2916,23 +2880,23 @@ var NETWORK_PERMISSION_OPTIONS = [
|
|
|
2916
2880
|
{ label: "Abort (keep blocked)", key: "esc", action: "abort" }
|
|
2917
2881
|
];
|
|
2918
2882
|
function loadSandboxConfig(cwd, includeProject) {
|
|
2919
|
-
const projectConfigPath =
|
|
2920
|
-
const globalConfigPath =
|
|
2921
|
-
if (!
|
|
2922
|
-
const templatePath =
|
|
2883
|
+
const projectConfigPath = join6(cwd, ".pi", "sandbox.json");
|
|
2884
|
+
const globalConfigPath = join6(getAgentDir4(), "sandbox.json");
|
|
2885
|
+
if (!existsSync5(globalConfigPath)) {
|
|
2886
|
+
const templatePath = join6(packageDir3, "sandbox.json");
|
|
2923
2887
|
mkdirSync3(dirname3(globalConfigPath), { recursive: true });
|
|
2924
|
-
writeFileSync3(globalConfigPath,
|
|
2888
|
+
writeFileSync3(globalConfigPath, readFileSync5(templatePath, "utf-8"), "utf-8");
|
|
2925
2889
|
}
|
|
2926
|
-
let globalConfig = JSON.parse(
|
|
2890
|
+
let globalConfig = JSON.parse(readFileSync5(join6(packageDir3, "sandbox.json"), "utf-8"));
|
|
2927
2891
|
try {
|
|
2928
|
-
const override = JSON.parse(
|
|
2892
|
+
const override = JSON.parse(readFileSync5(globalConfigPath, "utf-8"));
|
|
2929
2893
|
globalConfig = deepMerge(globalConfig, override);
|
|
2930
2894
|
} catch (error) {
|
|
2931
2895
|
console.error(`Warning: Could not parse ${globalConfigPath}: ${error}`);
|
|
2932
2896
|
}
|
|
2933
|
-
if (includeProject &&
|
|
2897
|
+
if (includeProject && existsSync5(projectConfigPath)) {
|
|
2934
2898
|
try {
|
|
2935
|
-
const projectConfig = JSON.parse(
|
|
2899
|
+
const projectConfig = JSON.parse(readFileSync5(projectConfigPath, "utf-8"));
|
|
2936
2900
|
const merged = deepMerge(globalConfig, projectConfig);
|
|
2937
2901
|
merged.windows.backend = globalConfig.windows.backend;
|
|
2938
2902
|
return merged;
|
|
@@ -3007,10 +2971,10 @@ function getConfigPaths(cwd) {
|
|
|
3007
2971
|
return getPiConfigPaths(cwd, "sandbox.json");
|
|
3008
2972
|
}
|
|
3009
2973
|
function readOrEmptyConfig(configPath) {
|
|
3010
|
-
if (!
|
|
2974
|
+
if (!existsSync5(configPath))
|
|
3011
2975
|
return {};
|
|
3012
2976
|
try {
|
|
3013
|
-
return JSON.parse(
|
|
2977
|
+
return JSON.parse(readFileSync5(configPath, "utf-8"));
|
|
3014
2978
|
} catch {
|
|
3015
2979
|
return {};
|
|
3016
2980
|
}
|
|
@@ -3022,7 +2986,7 @@ function writeConfigFile(configPath, config) {
|
|
|
3022
2986
|
}
|
|
3023
2987
|
function getSandboxConfigWriteTarget(cwd, includeProject = true) {
|
|
3024
2988
|
const { globalPath, projectPath } = getConfigPaths(cwd);
|
|
3025
|
-
if (includeProject &&
|
|
2989
|
+
if (includeProject && existsSync5(projectPath)) {
|
|
3026
2990
|
return { scope: "project", path: projectPath };
|
|
3027
2991
|
}
|
|
3028
2992
|
return { scope: "global", path: globalPath };
|
|
@@ -3098,7 +3062,7 @@ function canonicalizePath(filePath, cwd) {
|
|
|
3098
3062
|
} catch {
|
|
3099
3063
|
const tail = [];
|
|
3100
3064
|
let probe = abs;
|
|
3101
|
-
while (!
|
|
3065
|
+
while (!existsSync5(probe)) {
|
|
3102
3066
|
const parent = dirname3(probe);
|
|
3103
3067
|
if (parent === probe)
|
|
3104
3068
|
return abs;
|
|
@@ -3129,7 +3093,7 @@ function matchesPattern(filePath, patterns, cwd) {
|
|
|
3129
3093
|
}
|
|
3130
3094
|
function normalizeBlockedPath(path, cwd) {
|
|
3131
3095
|
const nativePath = process.platform === "win32" && /^\/[a-zA-Z](?:\/|$)/.test(path) ? `${path[1]}:${path.slice(2)}` : path;
|
|
3132
|
-
return canonicalizePath(isAbsolute2(nativePath) ? nativePath :
|
|
3096
|
+
return canonicalizePath(isAbsolute2(nativePath) ? nativePath : join6(cwd, nativePath), cwd);
|
|
3133
3097
|
}
|
|
3134
3098
|
function pathUnderDirectory(filePath, dir) {
|
|
3135
3099
|
const normalizedFilePath = normalizePathSeparators(filePath);
|
|
@@ -3158,7 +3122,7 @@ function sessionScopeFor(filePath, baseDirectory) {
|
|
|
3158
3122
|
const first = rest.split("/")[0];
|
|
3159
3123
|
if (!first)
|
|
3160
3124
|
return filePath;
|
|
3161
|
-
return
|
|
3125
|
+
return join6(boundary, first);
|
|
3162
3126
|
}
|
|
3163
3127
|
}
|
|
3164
3128
|
if (pathUnderDirectory(dir, baseDirectory))
|
|
@@ -3431,8 +3395,8 @@ function writeEnvFile(env, proxyPort, proxyToken) {
|
|
|
3431
3395
|
lines.push("export NO_PROXY=''");
|
|
3432
3396
|
lines.push("export no_proxy=''");
|
|
3433
3397
|
}
|
|
3434
|
-
const dir = mkdtempSync2(
|
|
3435
|
-
const path =
|
|
3398
|
+
const dir = mkdtempSync2(join6(tmpdir2(), "pi-landstrip-env-"));
|
|
3399
|
+
const path = join6(dir, "env.sh");
|
|
3436
3400
|
writeFileSync3(path, lines.join(`
|
|
3437
3401
|
`), "utf-8");
|
|
3438
3402
|
return { dir, path };
|
|
@@ -3573,7 +3537,7 @@ function createLandstripIntegration(options = {}) {
|
|
|
3573
3537
|
if (!isAbsolute2(requestedEntry)) {
|
|
3574
3538
|
throw new Error("Worker extension entry must be an absolute path or file URL");
|
|
3575
3539
|
}
|
|
3576
|
-
if (!
|
|
3540
|
+
if (!existsSync5(requestedEntry)) {
|
|
3577
3541
|
throw new Error(`Worker extension entry does not exist: ${requestedEntry}`);
|
|
3578
3542
|
}
|
|
3579
3543
|
const entry = realpathSync2(requestedEntry);
|
|
@@ -3615,7 +3579,7 @@ function createLandstripIntegration(options = {}) {
|
|
|
3615
3579
|
const paths = mergeAllowances(config.filesystem.allowRead, sessionAllowedReadPaths, allowances?.readPaths);
|
|
3616
3580
|
if (process.platform !== "win32")
|
|
3617
3581
|
return paths;
|
|
3618
|
-
return paths.filter((path) => !path.startsWith("/") && (path.includes("*") ||
|
|
3582
|
+
return paths.filter((path) => !path.startsWith("/") && (path.includes("*") || existsSync5(expandPath(path, cwd))));
|
|
3619
3583
|
}
|
|
3620
3584
|
function getEffectiveAllowWrite(config, allowances) {
|
|
3621
3585
|
const paths = mergeAllowances(config.filesystem.allowWrite, sessionAllowedWritePaths, allowances?.writePaths);
|
|
@@ -3721,8 +3685,8 @@ function createLandstripIntegration(options = {}) {
|
|
|
3721
3685
|
};
|
|
3722
3686
|
}
|
|
3723
3687
|
function writePolicyFile(cwd, proxyPort, allowances) {
|
|
3724
|
-
const dir = mkdtempSync2(
|
|
3725
|
-
const path =
|
|
3688
|
+
const dir = mkdtempSync2(join6(tmpdir2(), "pi-landstrip-"));
|
|
3689
|
+
const path = join6(dir, "policy.json");
|
|
3726
3690
|
writeFileSync3(path, JSON.stringify(buildLandstripPolicy(cwd, proxyPort, allowances), null, 2) + `
|
|
3727
3691
|
`, "utf-8");
|
|
3728
3692
|
return { dir, path };
|
|
@@ -4149,7 +4113,7 @@ function createLandstripIntegration(options = {}) {
|
|
|
4149
4113
|
function createLandstripBashOps(ctx, callbacks = {}, allowances = { domains: [], readPaths: [], writePaths: [], targets: [] }) {
|
|
4150
4114
|
return {
|
|
4151
4115
|
async exec(command, cwd, { onData, signal, timeout, env }) {
|
|
4152
|
-
if (!
|
|
4116
|
+
if (!existsSync5(cwd))
|
|
4153
4117
|
throw new Error(`Working directory does not exist: ${cwd}`);
|
|
4154
4118
|
const { shell, args } = getShellConfig(SettingsManager.create(cwd).getShellPath());
|
|
4155
4119
|
const config = loadConfig(cwd);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-landstrip",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.38",
|
|
4
4
|
"description": "Sandbox-aware Pi subagents and interactive command permissions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"landstrip",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"ci:test": "npm run test"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@landstrip/landstrip": "^0.17.
|
|
43
|
+
"@landstrip/landstrip": "^0.17.38",
|
|
44
44
|
"minimatch": "^10.2.5",
|
|
45
45
|
"yaml": "^2.9.0"
|
|
46
46
|
},
|