pi-landstrip 0.18.41 → 0.18.42
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/dist/index.ts +88 -162
- package/package.json +2 -2
package/dist/index.ts
CHANGED
|
@@ -272,17 +272,15 @@ function isAgentColor(value) {
|
|
|
272
272
|
return true;
|
|
273
273
|
return OPENCODE_THEME_COLORS.has(value);
|
|
274
274
|
}
|
|
275
|
-
function hexFg(hex, text) {
|
|
276
|
-
const r = Number.parseInt(hex.slice(1, 3), 16);
|
|
277
|
-
const g = Number.parseInt(hex.slice(3, 5), 16);
|
|
278
|
-
const b = Number.parseInt(hex.slice(5, 7), 16);
|
|
279
|
-
return `\x1B[38;2;${r};${g};${b}m${text}\x1B[39m`;
|
|
280
|
-
}
|
|
281
275
|
function colorizeAgentText(color, text, themeFg, fallback = "accent") {
|
|
282
276
|
if (!color)
|
|
283
277
|
return themeFg ? themeFg(fallback, text) : text;
|
|
284
|
-
if (color.startsWith("#"))
|
|
285
|
-
|
|
278
|
+
if (color.startsWith("#")) {
|
|
279
|
+
const r = Number.parseInt(color.slice(1, 3), 16);
|
|
280
|
+
const g = Number.parseInt(color.slice(3, 5), 16);
|
|
281
|
+
const b = Number.parseInt(color.slice(5, 7), 16);
|
|
282
|
+
return `\x1B[38;2;${r};${g};${b}m${text}\x1B[39m`;
|
|
283
|
+
}
|
|
286
284
|
if (themeFg)
|
|
287
285
|
return themeFg(THEME_COLOR_ALIASES[color] ?? fallback, text);
|
|
288
286
|
const ansi = NAMED_COLOR_ANSI[color];
|
|
@@ -420,9 +418,6 @@ var MAX_SUBAGENTS = 16;
|
|
|
420
418
|
var LANDSTRIP_KEYS = ["maxSubagents", "agent", "permission", "toolFilesystemPolicy"];
|
|
421
419
|
var JSON_FORMAT = { formattingOptions: { insertSpaces: true, tabSize: 2, eol: `
|
|
422
420
|
` } };
|
|
423
|
-
function isLandstripKey(key) {
|
|
424
|
-
return LANDSTRIP_KEYS.includes(key);
|
|
425
|
-
}
|
|
426
421
|
function ensureFinalNewline(content) {
|
|
427
422
|
return content.endsWith(`
|
|
428
423
|
`) ? content : `${content}
|
|
@@ -465,8 +460,9 @@ function parseLandstripConfig(value, path, kind) {
|
|
|
465
460
|
if (!isRecord2(value))
|
|
466
461
|
throw new Error(`${path}: ${label} must be a JSON object`);
|
|
467
462
|
for (const key of Object.keys(value)) {
|
|
468
|
-
if (!
|
|
463
|
+
if (!LANDSTRIP_KEYS.includes(key)) {
|
|
469
464
|
throw new Error(`${path}: ${label} has an unknown field ${key}`);
|
|
465
|
+
}
|
|
470
466
|
}
|
|
471
467
|
const field = (name) => kind === "settings" ? `landstrip.${name}` : name;
|
|
472
468
|
const config = {};
|
|
@@ -760,11 +756,6 @@ function listMarkdownFiles(root) {
|
|
|
760
756
|
visit(root);
|
|
761
757
|
return results;
|
|
762
758
|
}
|
|
763
|
-
function agentNameFromPath(dir, filePath) {
|
|
764
|
-
const relativePath = relative(dir, filePath).split(sep).join("/");
|
|
765
|
-
const extension = extname(relativePath);
|
|
766
|
-
return extension.length > 0 ? relativePath.slice(0, -extension.length) : relativePath;
|
|
767
|
-
}
|
|
768
759
|
function parseFrontmatter(content) {
|
|
769
760
|
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n([\s\S]*))?$/);
|
|
770
761
|
if (!match)
|
|
@@ -863,7 +854,9 @@ function loadPiMarkdownAgents(options) {
|
|
|
863
854
|
try {
|
|
864
855
|
const content = readFileSync3(filePath, "utf8");
|
|
865
856
|
const raw = parseMarkdownAgentRaw(content);
|
|
866
|
-
const
|
|
857
|
+
const relativePath = relative(directory.path, filePath).split(sep).join("/");
|
|
858
|
+
const extension = extname(relativePath);
|
|
859
|
+
const name = extension.length > 0 ? relativePath.slice(0, -extension.length) : relativePath;
|
|
867
860
|
if (!name) {
|
|
868
861
|
diagnostics.push(`${filePath}: agent name is empty`);
|
|
869
862
|
continue;
|
|
@@ -897,9 +890,6 @@ var AGENT_FIELDS = new Set([
|
|
|
897
890
|
function normalizeAbsolutePath(value) {
|
|
898
891
|
return /^(?:[A-Za-z]:[\\/]|\\\\)/.test(value) ? value.replaceAll("\\", "/") : value;
|
|
899
892
|
}
|
|
900
|
-
function expandPattern(pattern) {
|
|
901
|
-
return normalizeAbsolutePath(expandHomePath(pattern));
|
|
902
|
-
}
|
|
903
893
|
function permissionEntries(permission, value) {
|
|
904
894
|
if (value === "allow" || value === "ask" || value === "deny") {
|
|
905
895
|
return [{ permission, pattern: "*", action: value }];
|
|
@@ -911,7 +901,7 @@ function permissionEntries(permission, value) {
|
|
|
911
901
|
if (action !== "allow" && action !== "ask" && action !== "deny") {
|
|
912
902
|
throw new Error(`permission ${permission} pattern ${pattern} has an invalid action`);
|
|
913
903
|
}
|
|
914
|
-
return { permission, pattern:
|
|
904
|
+
return { permission, pattern: normalizeAbsolutePath(expandHomePath(pattern)), action };
|
|
915
905
|
});
|
|
916
906
|
}
|
|
917
907
|
function normalizePermissions(value) {
|
|
@@ -1134,17 +1124,9 @@ function valueAtPath(root, path) {
|
|
|
1134
1124
|
}
|
|
1135
1125
|
return value;
|
|
1136
1126
|
}
|
|
1137
|
-
function configKindForPath(path) {
|
|
1138
|
-
return basename(path) === "landstrip.json" ? "dedicated" : "settings";
|
|
1139
|
-
}
|
|
1140
1127
|
function agentJsonPath(kind, name) {
|
|
1141
1128
|
return kind === "settings" ? ["landstrip", "agent", name] : ["agent", name];
|
|
1142
1129
|
}
|
|
1143
|
-
function editorSnippet(name, raw, kind) {
|
|
1144
|
-
const agent = { agent: { [name]: raw } };
|
|
1145
|
-
return `${JSON.stringify(kind === "settings" ? { landstrip: agent } : agent, null, 2)}
|
|
1146
|
-
`;
|
|
1147
|
-
}
|
|
1148
1130
|
function parseEditorSnippet(content, name, kind) {
|
|
1149
1131
|
const root = parseJsonDocument(content, "Edited agent");
|
|
1150
1132
|
const parentPath = kind === "settings" ? ["landstrip"] : [];
|
|
@@ -1245,36 +1227,27 @@ function relocateGlobalPrompt(raw, agent) {
|
|
|
1245
1227
|
}
|
|
1246
1228
|
return { ...raw, prompt: expandFileReferences(raw.prompt, dirname2(agent.origin.path)) };
|
|
1247
1229
|
}
|
|
1248
|
-
function safeRelative(root, path) {
|
|
1249
|
-
const value = relative2(root, path);
|
|
1250
|
-
return value && value !== ".." && !value.startsWith(`..${sep2}`) ? value : undefined;
|
|
1251
|
-
}
|
|
1252
1230
|
function projectMarkdownPath(cwd, agentDir, agent) {
|
|
1253
1231
|
const origin = agent.origin;
|
|
1254
1232
|
if (origin.source === "local" && origin.path)
|
|
1255
1233
|
return origin.path;
|
|
1256
1234
|
if (origin.kind === "pi-markdown" && origin.path) {
|
|
1257
|
-
const
|
|
1235
|
+
const relativePath = relative2(join5(agentDir, "agents"), origin.path);
|
|
1236
|
+
const suffix = relativePath && relativePath !== ".." && !relativePath.startsWith(`..${sep2}`) ? relativePath : `${agent.name}.md`;
|
|
1258
1237
|
return join5(cwd, ".pi", "agents", suffix);
|
|
1259
1238
|
}
|
|
1260
1239
|
throw new Error(`Agent ${agent.name} has no Markdown source`);
|
|
1261
1240
|
}
|
|
1262
|
-
function projectConfigTarget(cwd, agentDir) {
|
|
1263
|
-
return getLandstripConfigTarget(cwd, "project", agentDir);
|
|
1264
|
-
}
|
|
1265
1241
|
function assertProjectConfigTarget(cwd, agentDir, target) {
|
|
1266
|
-
const current =
|
|
1242
|
+
const current = getLandstripConfigTarget(cwd, "project", agentDir);
|
|
1267
1243
|
if (current.path !== target.path || current.kind !== target.kind) {
|
|
1268
1244
|
throw new Error("Landstrip project configuration source changed while updating it");
|
|
1269
1245
|
}
|
|
1270
1246
|
}
|
|
1271
|
-
function projectConfigHasAgent(cwd, name, agentDir) {
|
|
1272
|
-
const target = projectConfigTarget(cwd, agentDir);
|
|
1273
|
-
return readAgentNode(target.path, target.kind, name) !== undefined;
|
|
1274
|
-
}
|
|
1275
1247
|
function canDeleteProjectAgent(cwd, agent, agentDir = getAgentDir3()) {
|
|
1276
1248
|
try {
|
|
1277
|
-
|
|
1249
|
+
const target = getLandstripConfigTarget(cwd, "project", agentDir);
|
|
1250
|
+
if (readAgentNode(target.path, target.kind, agent.name) !== undefined)
|
|
1278
1251
|
return true;
|
|
1279
1252
|
} catch {
|
|
1280
1253
|
return false;
|
|
@@ -1283,7 +1256,7 @@ function canDeleteProjectAgent(cwd, agent, agentDir = getAgentDir3()) {
|
|
|
1283
1256
|
return Boolean(origin?.source === "local" && origin.path && origin.kind === "pi-markdown");
|
|
1284
1257
|
}
|
|
1285
1258
|
async function deleteProjectAgent(cwd, agent, agentDir = getAgentDir3()) {
|
|
1286
|
-
const target =
|
|
1259
|
+
const target = getLandstripConfigTarget(cwd, "project", agentDir);
|
|
1287
1260
|
const containers = target.kind === "settings" ? [["landstrip", "agent"], ["landstrip"]] : [["agent"]];
|
|
1288
1261
|
const deletedOverride = await deleteJsonNode(target.path, agentJsonPath(target.kind, agent.name), containers, () => assertProjectConfigTarget(cwd, agentDir, target), target.kind === "dedicated");
|
|
1289
1262
|
const origin = agent.origin;
|
|
@@ -1312,12 +1285,13 @@ function prepareProjectAgentEditor(cwd, agent, agentDir = getAgentDir3()) {
|
|
|
1312
1285
|
};
|
|
1313
1286
|
}
|
|
1314
1287
|
const sourcePath = origin?.path;
|
|
1315
|
-
const sourceRaw = origin?.kind === "config" && sourcePath ? readAgentNode(sourcePath,
|
|
1288
|
+
const sourceRaw = origin?.kind === "config" && sourcePath ? readAgentNode(sourcePath, basename(sourcePath) === "landstrip.json" ? "dedicated" : "settings", agent.name) : undefined;
|
|
1316
1289
|
const raw = relocateGlobalPrompt(sourceRaw ? sourceRaw : agent.raw ? { ...agent.raw } : normalizedAgentRaw(agent), agent);
|
|
1317
|
-
const target =
|
|
1290
|
+
const target = getLandstripConfigTarget(cwd, "project", agentDir);
|
|
1318
1291
|
return {
|
|
1319
1292
|
title: `Edit @${agent.name}`,
|
|
1320
|
-
content:
|
|
1293
|
+
content: `${JSON.stringify(target.kind === "settings" ? { landstrip: { agent: { [agent.name]: raw } } } : { agent: { [agent.name]: raw } }, null, 2)}
|
|
1294
|
+
`,
|
|
1321
1295
|
async save(content) {
|
|
1322
1296
|
const edited = parseEditorSnippet(content, agent.name, target.kind);
|
|
1323
1297
|
validateAgentRaw(agent.name, edited);
|
|
@@ -1362,9 +1336,6 @@ function parseArgv(args) {
|
|
|
1362
1336
|
trailing: /\s$/.test(args)
|
|
1363
1337
|
};
|
|
1364
1338
|
}
|
|
1365
|
-
function sandboxOverview(ctx, integration) {
|
|
1366
|
-
return integration.sandboxCallbacks?.overview?.(ctx.cwd, isProjectTrusted(ctx));
|
|
1367
|
-
}
|
|
1368
1339
|
function taskItems(tasks, prefix) {
|
|
1369
1340
|
return tasks.map((task) => ({
|
|
1370
1341
|
value: `${prefix}${task.id}`,
|
|
@@ -1383,7 +1354,7 @@ var SUBCOMMANDS = [
|
|
|
1383
1354
|
summary: "Inspect sandbox & agent runtime status",
|
|
1384
1355
|
pane: (argv) => argv.rest ? undefined : "overview",
|
|
1385
1356
|
run: (_argv, { ctx, runtime, integration }) => {
|
|
1386
|
-
const overview =
|
|
1357
|
+
const overview = integration.sandboxCallbacks?.overview?.(ctx.cwd, isProjectTrusted(ctx));
|
|
1387
1358
|
const sandbox = !overview ? "unavailable" : !overview.enabled ? "disabled" : overview.running ? "active" : "enabled (inactive)";
|
|
1388
1359
|
const primary = runtime.primaryAgentName();
|
|
1389
1360
|
const tasks = runtime.getTasks();
|
|
@@ -1399,14 +1370,14 @@ var SUBCOMMANDS = [
|
|
|
1399
1370
|
{
|
|
1400
1371
|
name: "sandbox",
|
|
1401
1372
|
summary: "Inspect or toggle sandbox OS isolation",
|
|
1402
|
-
pane: (argv, { ctx, integration }) => !argv.rest &&
|
|
1373
|
+
pane: (argv, { ctx, integration }) => !argv.rest && integration.sandboxCallbacks?.overview?.(ctx.cwd, isProjectTrusted(ctx)) ? "overview" : undefined,
|
|
1403
1374
|
complete: () => [
|
|
1404
1375
|
{ value: "sandbox on", label: "on", description: "Enable OS sandbox isolation" },
|
|
1405
1376
|
{ value: "sandbox off", label: "off", description: "Disable OS sandbox isolation" }
|
|
1406
1377
|
],
|
|
1407
1378
|
run: async (argv, { ctx, integration }) => {
|
|
1408
1379
|
const callbacks = integration.sandboxCallbacks;
|
|
1409
|
-
const overview =
|
|
1380
|
+
const overview = integration.sandboxCallbacks?.overview?.(ctx.cwd, isProjectTrusted(ctx));
|
|
1410
1381
|
if (!callbacks || !overview) {
|
|
1411
1382
|
ctx.ui.notify("Sandbox controls are unavailable", "warning");
|
|
1412
1383
|
return;
|
|
@@ -1456,7 +1427,7 @@ Scope: ${scope} configuration`);
|
|
|
1456
1427
|
if (!runtime || !ctx)
|
|
1457
1428
|
return [];
|
|
1458
1429
|
try {
|
|
1459
|
-
return
|
|
1430
|
+
return [...runtime.getAgentCatalog(ctx).agents.values()].filter((agent) => !agent.hidden).map((agent) => ({
|
|
1460
1431
|
value: `agents @${agent.name}`,
|
|
1461
1432
|
label: `@${agent.name}`,
|
|
1462
1433
|
description: agent.description ?? (agent.model ? `Model: ${agent.model}` : "Primary agent")
|
|
@@ -1467,7 +1438,7 @@ Scope: ${scope} configuration`);
|
|
|
1467
1438
|
},
|
|
1468
1439
|
run: async (argv, { ctx, runtime }) => {
|
|
1469
1440
|
if (!argv.rest) {
|
|
1470
|
-
const names =
|
|
1441
|
+
const names = [...runtime.getAgentCatalog(ctx).agents.values()].filter((agent) => !agent.hidden).map((agent) => `@${agent.name}`).join(", ");
|
|
1471
1442
|
ctx.ui.notify(`Available agents: ${names || "none"}`, "info");
|
|
1472
1443
|
return;
|
|
1473
1444
|
}
|
|
@@ -1482,7 +1453,7 @@ Scope: ${scope} configuration`);
|
|
|
1482
1453
|
summary: "Inspect configured process subagents",
|
|
1483
1454
|
pane: (argv) => argv.rest ? undefined : "subagent",
|
|
1484
1455
|
run: (_argv, { ctx, runtime }) => {
|
|
1485
|
-
const names =
|
|
1456
|
+
const names = [...runtime.getAgentCatalog(ctx).agents.values()].filter((agent) => !agent.hidden).filter((agent) => agent.mode === "subagent").map((agent) => `@${agent.name}`).join(", ");
|
|
1486
1457
|
ctx.ui.notify(`Subagents: ${names || "none"}`, "info");
|
|
1487
1458
|
}
|
|
1488
1459
|
},
|
|
@@ -1547,9 +1518,6 @@ Scope: ${scope} configuration`);
|
|
|
1547
1518
|
}
|
|
1548
1519
|
}
|
|
1549
1520
|
];
|
|
1550
|
-
function visibleAgents(catalog) {
|
|
1551
|
-
return [...catalog.agents.values()].filter((agent) => !agent.hidden);
|
|
1552
|
-
}
|
|
1553
1521
|
function itemText(item) {
|
|
1554
1522
|
return item.search ?? item.label;
|
|
1555
1523
|
}
|
|
@@ -2314,18 +2282,14 @@ function extractCwdFromTaskJsonFile(filePath) {
|
|
|
2314
2282
|
return {};
|
|
2315
2283
|
}
|
|
2316
2284
|
}
|
|
2317
|
-
function defaultSessionDirForCwd(cwd, agentDir = getAgentDir4()) {
|
|
2318
|
-
const resolvedCwd = resolve2(cwd);
|
|
2319
|
-
const safePath = `--${resolvedCwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
2320
|
-
return join6(agentDir, "sessions", safePath);
|
|
2321
|
-
}
|
|
2322
2285
|
function isSubagentSessionDirForCwd(parentSessionDir, targetCwd) {
|
|
2323
2286
|
const files = findFilesRecursively(parentSessionDir);
|
|
2324
2287
|
if (files.length === 0) {
|
|
2325
2288
|
return true;
|
|
2326
2289
|
}
|
|
2327
2290
|
const resolvedTarget = resolve2(targetCwd);
|
|
2328
|
-
const
|
|
2291
|
+
const safePath = `--${resolvedTarget.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
2292
|
+
const targetSessionDir = resolve2(join6(getAgentDir4(), "sessions", safePath));
|
|
2329
2293
|
let hasTargetCwd = false;
|
|
2330
2294
|
let hasOtherCwd = false;
|
|
2331
2295
|
for (const file of files) {
|
|
@@ -2399,9 +2363,6 @@ function taskProgress(task) {
|
|
|
2399
2363
|
progress.push(`retry ${task.retryAttempt}`);
|
|
2400
2364
|
return progress;
|
|
2401
2365
|
}
|
|
2402
|
-
function taskOutput(details, fallback) {
|
|
2403
|
-
return details.error ?? details.output ?? fallback;
|
|
2404
|
-
}
|
|
2405
2366
|
function workerDialogTitle(task, title) {
|
|
2406
2367
|
return `@${task.agent} · ${task.description} · ${task.id.slice(0, 8)}
|
|
2407
2368
|
${title}`;
|
|
@@ -4051,7 +4012,7 @@ ${descriptions}` : ""),
|
|
|
4051
4012
|
if (metrics.length > 0)
|
|
4052
4013
|
text += `
|
|
4053
4014
|
${theme.fg("dim", metrics.join(" · "))}`;
|
|
4054
|
-
const output =
|
|
4015
|
+
const output = details.error ?? details.output ?? "";
|
|
4055
4016
|
if (output) {
|
|
4056
4017
|
const lines = output.trimEnd().split(`
|
|
4057
4018
|
`);
|
|
@@ -5202,13 +5163,10 @@ function windowsStatus() {
|
|
|
5202
5163
|
}
|
|
5203
5164
|
return status;
|
|
5204
5165
|
}
|
|
5205
|
-
function activeWindowsImplementation() {
|
|
5206
|
-
return windowsStatus().active;
|
|
5207
|
-
}
|
|
5208
5166
|
function shouldStartProxy(config) {
|
|
5209
5167
|
if (config.network.allowNetwork)
|
|
5210
5168
|
return false;
|
|
5211
|
-
return process.platform !== "win32" ||
|
|
5169
|
+
return process.platform !== "win32" || windowsStatus().active === "restrictedUser" || config.windows.allowLoopback;
|
|
5212
5170
|
}
|
|
5213
5171
|
function restrictedUserProxyPortRange() {
|
|
5214
5172
|
if (process.platform !== "win32")
|
|
@@ -5221,9 +5179,6 @@ function restrictedUserProxyPortRange() {
|
|
|
5221
5179
|
}
|
|
5222
5180
|
return { low: status.proxyPortLow, high: status.proxyPortHigh };
|
|
5223
5181
|
}
|
|
5224
|
-
function getConfigPaths(cwd) {
|
|
5225
|
-
return getPiConfigPaths(cwd, "sandbox.json");
|
|
5226
|
-
}
|
|
5227
5182
|
function readOrEmptyConfig(configPath) {
|
|
5228
5183
|
if (!existsSync5(configPath))
|
|
5229
5184
|
return {};
|
|
@@ -5239,11 +5194,11 @@ function writeConfigFile(configPath, config) {
|
|
|
5239
5194
|
`, "utf-8");
|
|
5240
5195
|
}
|
|
5241
5196
|
function getSandboxConfigWriteTarget(cwd, includeProject = true) {
|
|
5242
|
-
const { globalPath, projectPath } =
|
|
5197
|
+
const { globalPath, projectPath } = getPiConfigPaths(cwd, "sandbox.json");
|
|
5243
5198
|
return includeProject ? { scope: "project", path: projectPath } : { scope: "global", path: globalPath };
|
|
5244
5199
|
}
|
|
5245
5200
|
async function writeSandboxConfigEnabled(cwd, enabled, scope) {
|
|
5246
|
-
const paths =
|
|
5201
|
+
const paths = getPiConfigPaths(cwd, "sandbox.json");
|
|
5247
5202
|
const path = scope === "project" ? paths.projectPath : paths.globalPath;
|
|
5248
5203
|
await withFileMutationQueue3(path, async () => {
|
|
5249
5204
|
const config = readOrEmptyConfig(path);
|
|
@@ -5255,7 +5210,7 @@ async function writeSandboxConfigEnabled(cwd, enabled, scope) {
|
|
|
5255
5210
|
});
|
|
5256
5211
|
}
|
|
5257
5212
|
function loadSandboxEnabledSettings(cwd, includeProject) {
|
|
5258
|
-
const { projectPath } =
|
|
5213
|
+
const { projectPath } = getPiConfigPaths(cwd, "sandbox.json");
|
|
5259
5214
|
const global = loadSandboxConfig(cwd, false).enabled;
|
|
5260
5215
|
if (!includeProject || !existsSync5(projectPath))
|
|
5261
5216
|
return { global };
|
|
@@ -5272,25 +5227,6 @@ async function addConfigValue(configPath, value, readValues, writeValues) {
|
|
|
5272
5227
|
writeConfigFile(configPath, config);
|
|
5273
5228
|
});
|
|
5274
5229
|
}
|
|
5275
|
-
function addDomainToConfig(configPath, domain) {
|
|
5276
|
-
return addConfigValue(configPath, domain, (config) => config.network?.allowedDomains ?? [], (config, allowedDomains) => {
|
|
5277
|
-
config.network = {
|
|
5278
|
-
...config.network,
|
|
5279
|
-
allowedDomains,
|
|
5280
|
-
deniedDomains: config.network?.deniedDomains ?? []
|
|
5281
|
-
};
|
|
5282
|
-
});
|
|
5283
|
-
}
|
|
5284
|
-
function addReadPathToConfig(configPath, pathToAdd) {
|
|
5285
|
-
return addConfigValue(configPath, pathToAdd, (config) => config.filesystem?.allowRead ?? [], (config, allowRead) => {
|
|
5286
|
-
config.filesystem = { ...config.filesystem, allowRead };
|
|
5287
|
-
});
|
|
5288
|
-
}
|
|
5289
|
-
function addWritePathToConfig(configPath, pathToAdd) {
|
|
5290
|
-
return addConfigValue(configPath, pathToAdd, (config) => config.filesystem?.allowWrite ?? [], (config, allowWrite) => {
|
|
5291
|
-
config.filesystem = { ...config.filesystem, allowWrite };
|
|
5292
|
-
});
|
|
5293
|
-
}
|
|
5294
5230
|
function mergeAllowances(base, session, execution) {
|
|
5295
5231
|
return [...base, ...session, ...execution ?? []];
|
|
5296
5232
|
}
|
|
@@ -5456,28 +5392,10 @@ ${option.hint ?? "This changes persisted sandbox policy."}`, { signal });
|
|
|
5456
5392
|
}
|
|
5457
5393
|
return option.action;
|
|
5458
5394
|
}
|
|
5459
|
-
function promptDomainBlock(ctx, domain, signal) {
|
|
5460
|
-
return showPermissionPrompt(ctx, `Network blocked: "${domain}" is not in the allowed domains list`, PERMISSION_OPTIONS, signal);
|
|
5461
|
-
}
|
|
5462
5395
|
function promptReadBlock(ctx, filePath, reason, signal) {
|
|
5463
5396
|
const title = reason ? `Read blocked: "${filePath}" is in denyRead (${reason})` : `Read blocked: "${filePath}" is not in allowRead`;
|
|
5464
5397
|
return showPermissionPrompt(ctx, title, PERMISSION_OPTIONS, signal);
|
|
5465
5398
|
}
|
|
5466
|
-
function promptWriteBlock(ctx, filePath, signal) {
|
|
5467
|
-
return showPermissionPrompt(ctx, `Write blocked: "${filePath}" is not in allowWrite`, PERMISSION_OPTIONS, signal);
|
|
5468
|
-
}
|
|
5469
|
-
function promptFilesystemToolBlock(ctx, accesses, authorizationNote, signal) {
|
|
5470
|
-
const paths = accesses.map(({ operation, path }) => `${operation}: ${path}`).join(`
|
|
5471
|
-
`);
|
|
5472
|
-
const note = authorizationNote ? `
|
|
5473
|
-
|
|
5474
|
-
${authorizationNote}` : "";
|
|
5475
|
-
return showPermissionPrompt(ctx, `Filesystem policy blocked this tool call:
|
|
5476
|
-
${paths}${note}`, PERMISSION_OPTIONS, signal);
|
|
5477
|
-
}
|
|
5478
|
-
function promptNetworkBlock(ctx, operation, target, signal) {
|
|
5479
|
-
return showPermissionPrompt(ctx, `Network blocked: ${operation} "${target}"`, NETWORK_PERMISSION_OPTIONS, signal);
|
|
5480
|
-
}
|
|
5481
5399
|
function writeEnvFile(env, proxyPort, proxyToken) {
|
|
5482
5400
|
const lines = [];
|
|
5483
5401
|
for (const [key, value] of Object.entries(env)) {
|
|
@@ -5569,11 +5487,6 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
5569
5487
|
function loadConfig(cwd) {
|
|
5570
5488
|
return loadSandboxConfig(cwd, projectConfigTrusted);
|
|
5571
5489
|
}
|
|
5572
|
-
function createPlainBashTool(cwd) {
|
|
5573
|
-
return createBashToolDefinition(cwd, {
|
|
5574
|
-
shellPath: SettingsManager.create(cwd).getShellPath()
|
|
5575
|
-
});
|
|
5576
|
-
}
|
|
5577
5490
|
let sandboxEnabled = false;
|
|
5578
5491
|
let sandboxReady = false;
|
|
5579
5492
|
let sandboxState = "unavailable";
|
|
@@ -5709,16 +5622,6 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
5709
5622
|
function getWorkerExtensions() {
|
|
5710
5623
|
return [...workerExtensions].map(([id, extension]) => ({ id, entry: extension.entry }));
|
|
5711
5624
|
}
|
|
5712
|
-
function resetSessionAllowances() {
|
|
5713
|
-
sessionAllowedDomains.length = 0;
|
|
5714
|
-
sessionAllowedReadPaths.length = 0;
|
|
5715
|
-
sessionAllowedWritePaths.length = 0;
|
|
5716
|
-
sessionAllowedTargets.length = 0;
|
|
5717
|
-
unsandboxedWorkerWarningShown = false;
|
|
5718
|
-
}
|
|
5719
|
-
function getEffectiveAllowedDomains(config, allowances) {
|
|
5720
|
-
return mergeAllowances(config.network.allowedDomains, sessionAllowedDomains, allowances?.domains);
|
|
5721
|
-
}
|
|
5722
5625
|
function getEffectiveAllowRead(config, cwd, allowances) {
|
|
5723
5626
|
const paths = mergeAllowances(config.filesystem.allowRead, sessionAllowedReadPaths, allowances?.readPaths);
|
|
5724
5627
|
if (process.platform !== "win32")
|
|
@@ -5760,11 +5663,17 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
5760
5663
|
};
|
|
5761
5664
|
}
|
|
5762
5665
|
async function applyDomainChoice(choice, domain, cwd, allowances) {
|
|
5763
|
-
const { globalPath, projectPath } =
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5666
|
+
const { globalPath, projectPath } = getPiConfigPaths(cwd, "sandbox.json");
|
|
5667
|
+
const configPath = choice === "project" ? projectPath : choice === "global" ? globalPath : undefined;
|
|
5668
|
+
if (configPath) {
|
|
5669
|
+
await addConfigValue(configPath, domain, (config) => config.network?.allowedDomains ?? [], (config, allowedDomains) => {
|
|
5670
|
+
config.network = {
|
|
5671
|
+
...config.network,
|
|
5672
|
+
allowedDomains,
|
|
5673
|
+
deniedDomains: config.network?.deniedDomains ?? []
|
|
5674
|
+
};
|
|
5675
|
+
});
|
|
5676
|
+
}
|
|
5768
5677
|
const target = choice === "once" ? allowances?.domains : sessionAllowedDomains;
|
|
5769
5678
|
if (target && !target.includes(domain))
|
|
5770
5679
|
target.push(domain);
|
|
@@ -5774,24 +5683,28 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
5774
5683
|
notify(ctx, `${verb} allowed (${choice}): ${scope}`, "info");
|
|
5775
5684
|
}
|
|
5776
5685
|
async function applyReadChoice(ctx, choice, filePath, cwd, allowances) {
|
|
5777
|
-
const { globalPath, projectPath } =
|
|
5686
|
+
const { globalPath, projectPath } = getPiConfigPaths(cwd, "sandbox.json");
|
|
5778
5687
|
const scope = sessionScopeFor(filePath, cwd);
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5688
|
+
const configPath = choice === "project" ? projectPath : choice === "global" ? globalPath : undefined;
|
|
5689
|
+
if (configPath) {
|
|
5690
|
+
await addConfigValue(configPath, scope, (config) => config.filesystem?.allowRead ?? [], (config, allowRead) => {
|
|
5691
|
+
config.filesystem = { ...config.filesystem, allowRead };
|
|
5692
|
+
});
|
|
5693
|
+
}
|
|
5783
5694
|
const target = choice === "once" ? allowances?.readPaths : sessionAllowedReadPaths;
|
|
5784
5695
|
if (target && !target.includes(scope))
|
|
5785
5696
|
target.push(scope);
|
|
5786
5697
|
noteScope(ctx, "Read", choice, filePath, scope);
|
|
5787
5698
|
}
|
|
5788
5699
|
async function applyWriteChoice(ctx, choice, filePath, cwd, allowances) {
|
|
5789
|
-
const { globalPath, projectPath } =
|
|
5700
|
+
const { globalPath, projectPath } = getPiConfigPaths(cwd, "sandbox.json");
|
|
5790
5701
|
const scope = sessionScopeFor(filePath, cwd);
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5702
|
+
const configPath = choice === "project" ? projectPath : choice === "global" ? globalPath : undefined;
|
|
5703
|
+
if (configPath) {
|
|
5704
|
+
await addConfigValue(configPath, scope, (config) => config.filesystem?.allowWrite ?? [], (config, allowWrite) => {
|
|
5705
|
+
config.filesystem = { ...config.filesystem, allowWrite };
|
|
5706
|
+
});
|
|
5707
|
+
}
|
|
5795
5708
|
const target = choice === "once" ? allowances?.writePaths : sessionAllowedWritePaths;
|
|
5796
5709
|
if (target && !target.includes(scope))
|
|
5797
5710
|
target.push(scope);
|
|
@@ -5827,7 +5740,13 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
5827
5740
|
return permissionPrompts.resolve(current, async (promptSignal) => {
|
|
5828
5741
|
const config = loadConfig(ctx.cwd);
|
|
5829
5742
|
const blocked = normalized.filter((access) => access.operation === "read" ? !readAllowed(access.path, getEffectiveAllowRead(config, ctx.cwd), getEffectiveDenyRead(config, ctx.cwd), ctx.cwd) : shouldPromptForWrite(access.path, getEffectiveAllowWrite(config), ctx.cwd));
|
|
5830
|
-
const
|
|
5743
|
+
const paths = blocked.map(({ operation, path }) => `${operation}: ${path}`).join(`
|
|
5744
|
+
`);
|
|
5745
|
+
const note = options2.authorizationNote ? `
|
|
5746
|
+
|
|
5747
|
+
${options2.authorizationNote}` : "";
|
|
5748
|
+
const choice = await showPermissionPrompt(ctx, `Filesystem policy blocked this tool call:
|
|
5749
|
+
${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
|
|
5831
5750
|
if (choice === "abort" || promptSignal.aborted) {
|
|
5832
5751
|
return { allowed: false, prompted: true, reason: "Filesystem access denied" };
|
|
5833
5752
|
}
|
|
@@ -5846,14 +5765,15 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
5846
5765
|
const config = loadConfig(cwd);
|
|
5847
5766
|
if (domainMatchesAny(domain, config.network.deniedDomains))
|
|
5848
5767
|
return false;
|
|
5849
|
-
|
|
5768
|
+
const allowedDomains = mergeAllowances(config.network.allowedDomains, sessionAllowedDomains, allowances?.domains);
|
|
5769
|
+
if (domainMatchesAny(domain, allowedDomains))
|
|
5850
5770
|
return true;
|
|
5851
5771
|
return;
|
|
5852
5772
|
};
|
|
5853
5773
|
if (!ctx.hasUI)
|
|
5854
5774
|
return current() ?? false;
|
|
5855
5775
|
return permissionPrompts.resolve(current, async (promptSignal) => {
|
|
5856
|
-
const choice = await
|
|
5776
|
+
const choice = await showPermissionPrompt(ctx, `Network blocked: "${domain}" is not in the allowed domains list`, PERMISSION_OPTIONS, promptSignal);
|
|
5857
5777
|
if (choice === "abort" || promptSignal.aborted)
|
|
5858
5778
|
return false;
|
|
5859
5779
|
await applyDomainChoice(choice, domain, cwd, allowances);
|
|
@@ -5929,7 +5849,7 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
5929
5849
|
if (!ctx.hasUI || !promptOnBlock)
|
|
5930
5850
|
return { action: "deny", reason: "unprompted" };
|
|
5931
5851
|
return permissionPrompts.resolve(current2, async (promptSignal) => {
|
|
5932
|
-
const choice = await
|
|
5852
|
+
const choice = await showPermissionPrompt(ctx, `Network blocked: ${trap.operation} "${trap.target}"`, NETWORK_PERMISSION_OPTIONS, promptSignal);
|
|
5933
5853
|
if (choice === "abort" || promptSignal.aborted)
|
|
5934
5854
|
return { action: "deny", reason: "rejected" };
|
|
5935
5855
|
const targets = choice === "once" ? allowances.targets : sessionAllowedTargets;
|
|
@@ -5954,7 +5874,7 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
5954
5874
|
return { action: "deny", reason: "unprompted" };
|
|
5955
5875
|
return permissionPrompts.resolve(current, async (promptSignal) => {
|
|
5956
5876
|
const config = loadConfig(cwd);
|
|
5957
|
-
const choice = trap.operation === "read" ? await promptReadBlock(ctx, path, matchesPattern(path, config.filesystem.denyRead, cwd) ? "granting allowRead will override it" : undefined, promptSignal) : await
|
|
5877
|
+
const choice = trap.operation === "read" ? await promptReadBlock(ctx, path, matchesPattern(path, config.filesystem.denyRead, cwd) ? "granting allowRead will override it" : undefined, promptSignal) : await showPermissionPrompt(ctx, `Write blocked: "${path}" is not in allowWrite`, PERMISSION_OPTIONS, promptSignal);
|
|
5958
5878
|
if (choice === "abort" || promptSignal.aborted)
|
|
5959
5879
|
return { action: "deny", reason: "rejected" };
|
|
5960
5880
|
if (trap.operation === "read") {
|
|
@@ -6554,7 +6474,7 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
6554
6474
|
const retryWithAccess = async (operation, blockedPath) => {
|
|
6555
6475
|
if (!ctx.hasUI)
|
|
6556
6476
|
return null;
|
|
6557
|
-
const { globalPath, projectPath } =
|
|
6477
|
+
const { globalPath, projectPath } = getPiConfigPaths(ctx.cwd, "sandbox.json");
|
|
6558
6478
|
const current = () => {
|
|
6559
6479
|
const config2 = loadConfig(ctx.cwd);
|
|
6560
6480
|
if (operation === "write" && matchesPattern(blockedPath, config2.filesystem.denyWrite, ctx.cwd)) {
|
|
@@ -6565,7 +6485,7 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
6565
6485
|
};
|
|
6566
6486
|
const access = await permissionPrompts.resolve(current, async (promptSignal) => {
|
|
6567
6487
|
const config2 = loadConfig(ctx.cwd);
|
|
6568
|
-
const choice = operation === "read" ? await promptReadBlock(ctx, blockedPath, matchesPattern(blockedPath, config2.filesystem.denyRead, ctx.cwd) ? "granting allowRead will override it" : undefined, promptSignal) : await
|
|
6488
|
+
const choice = operation === "read" ? await promptReadBlock(ctx, blockedPath, matchesPattern(blockedPath, config2.filesystem.denyRead, ctx.cwd) ? "granting allowRead will override it" : undefined, promptSignal) : await showPermissionPrompt(ctx, `Write blocked: "${blockedPath}" is not in allowWrite`, PERMISSION_OPTIONS, promptSignal);
|
|
6569
6489
|
if (choice === "abort" || promptSignal.aborted)
|
|
6570
6490
|
return false;
|
|
6571
6491
|
if (operation === "read") {
|
|
@@ -6664,7 +6584,7 @@ ${message}
|
|
|
6664
6584
|
return;
|
|
6665
6585
|
let implementation;
|
|
6666
6586
|
try {
|
|
6667
|
-
implementation =
|
|
6587
|
+
implementation = windowsStatus().active;
|
|
6668
6588
|
} catch (error) {
|
|
6669
6589
|
notify(ctx, formatError(error), "error");
|
|
6670
6590
|
return;
|
|
@@ -6684,7 +6604,7 @@ ${message}
|
|
|
6684
6604
|
const theme = ctx.ui.theme;
|
|
6685
6605
|
const dot = theme.fg("success", "●");
|
|
6686
6606
|
const label = theme.fg("text", "Sandbox");
|
|
6687
|
-
const windowsImplementation = process.platform === "win32" ?
|
|
6607
|
+
const windowsImplementation = process.platform === "win32" ? windowsStatus().active : undefined;
|
|
6688
6608
|
let networkLabel;
|
|
6689
6609
|
let networkColor;
|
|
6690
6610
|
if (config.network.allowNetwork) {
|
|
@@ -6759,7 +6679,9 @@ ${message}
|
|
|
6759
6679
|
return true;
|
|
6760
6680
|
}
|
|
6761
6681
|
function createBashTool(cwd) {
|
|
6762
|
-
const localBash =
|
|
6682
|
+
const localBash = createBashToolDefinition(cwd, {
|
|
6683
|
+
shellPath: SettingsManager.create(cwd).getShellPath()
|
|
6684
|
+
});
|
|
6763
6685
|
return {
|
|
6764
6686
|
...localBash,
|
|
6765
6687
|
label: "bash (landstrip)",
|
|
@@ -6811,7 +6733,11 @@ ${message}
|
|
|
6811
6733
|
pi.on("session_start", async (_event, ctx) => {
|
|
6812
6734
|
activeContext = ctx;
|
|
6813
6735
|
permissionPrompts.reset();
|
|
6814
|
-
|
|
6736
|
+
sessionAllowedDomains.length = 0;
|
|
6737
|
+
sessionAllowedReadPaths.length = 0;
|
|
6738
|
+
sessionAllowedWritePaths.length = 0;
|
|
6739
|
+
sessionAllowedTargets.length = 0;
|
|
6740
|
+
unsandboxedWorkerWarningShown = false;
|
|
6815
6741
|
const trustContext = ctx;
|
|
6816
6742
|
projectConfigTrusted = trustContext.isProjectTrusted?.() ?? false;
|
|
6817
6743
|
if (getNoSandboxFlag()) {
|
|
@@ -6849,10 +6775,10 @@ ${message}
|
|
|
6849
6775
|
enableSandbox(ctx);
|
|
6850
6776
|
}
|
|
6851
6777
|
}
|
|
6852
|
-
function
|
|
6778
|
+
function sandboxOverview(cwd, includeProject) {
|
|
6853
6779
|
const config = loadSandboxConfig(cwd, includeProject);
|
|
6854
|
-
const { globalPath, projectPath } =
|
|
6855
|
-
const windowsImplementation = process.platform === "win32" ?
|
|
6780
|
+
const { globalPath, projectPath } = getPiConfigPaths(cwd, "sandbox.json");
|
|
6781
|
+
const windowsImplementation = process.platform === "win32" ? windowsStatus().active : undefined;
|
|
6856
6782
|
return {
|
|
6857
6783
|
enabled: config.enabled,
|
|
6858
6784
|
running: sandboxEnabled && sandboxReady,
|
|
@@ -6873,7 +6799,7 @@ ${message}
|
|
|
6873
6799
|
load: loadSandboxEnabledSettings,
|
|
6874
6800
|
setEnabled: (ctx, enabled, scope) => applySandboxSetting(ctx, enabled, scope),
|
|
6875
6801
|
clearProject: (ctx) => applySandboxSetting(ctx, undefined, "project"),
|
|
6876
|
-
overview:
|
|
6802
|
+
overview: sandboxOverview
|
|
6877
6803
|
};
|
|
6878
6804
|
const integration = {
|
|
6879
6805
|
version: LANDSTRIP_RUNTIME_VERSION,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-landstrip",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.42",
|
|
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.18.
|
|
42
|
+
"@landstrip/landstrip-api": "^0.18.42",
|
|
43
43
|
"jsonc-parser": "^3.3.1",
|
|
44
44
|
"minimatch": "^10.2.5",
|
|
45
45
|
"yaml": "^2.9.0"
|