pi-landstrip 0.18.40 → 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 +97 -164
- 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
|
`);
|
|
@@ -4293,6 +4254,7 @@ ${catalog.diagnostics.join(`
|
|
|
4293
4254
|
throw new Error("Task cancelled");
|
|
4294
4255
|
let turns = 0;
|
|
4295
4256
|
let streamedText = "";
|
|
4257
|
+
let workerError;
|
|
4296
4258
|
worker.rpc.onEvent((event) => {
|
|
4297
4259
|
if (event.type === "message_update" && isRecord2(event.assistantMessageEvent)) {
|
|
4298
4260
|
const messageEvent = event.assistantMessageEvent;
|
|
@@ -4301,7 +4263,10 @@ ${catalog.diagnostics.join(`
|
|
|
4301
4263
|
update(streamedText);
|
|
4302
4264
|
}
|
|
4303
4265
|
}
|
|
4304
|
-
if (event.type === "message_end") {
|
|
4266
|
+
if (event.type === "message_end" && isRecord2(event.message)) {
|
|
4267
|
+
if (event.message.role === "assistant") {
|
|
4268
|
+
workerError = event.message.stopReason === "error" ? typeof event.message.errorMessage === "string" ? event.message.errorMessage : "Subagent model request failed" : undefined;
|
|
4269
|
+
}
|
|
4305
4270
|
const usage = taskUsageFromMessage(event.message);
|
|
4306
4271
|
if (usage) {
|
|
4307
4272
|
task.usage = addTaskUsage(task.usage, usage);
|
|
@@ -4362,6 +4327,8 @@ ${catalog.diagnostics.join(`
|
|
|
4362
4327
|
await worker.rpc.request("follow_up", { message: pendingPrompt });
|
|
4363
4328
|
}
|
|
4364
4329
|
const output = await promise;
|
|
4330
|
+
if (workerError)
|
|
4331
|
+
throw new Error(workerError);
|
|
4365
4332
|
if (signal.aborted)
|
|
4366
4333
|
throw new Error("Task cancelled");
|
|
4367
4334
|
task.state = "completed";
|
|
@@ -4458,6 +4425,7 @@ ${catalog.diagnostics.join(`
|
|
|
4458
4425
|
if (!sessionWritePath)
|
|
4459
4426
|
throw new Error("Subagent task has no session directory or file");
|
|
4460
4427
|
const authPath = join6(agentDir, "auth.json");
|
|
4428
|
+
const settingsPath = join6(agentDir, "settings.json");
|
|
4461
4429
|
const cliEntry = invocation.args[0] ?? invocation.command;
|
|
4462
4430
|
const cliRoot = dependencyRoot(cliEntry) ?? dirname3(dirname3(cliEntry));
|
|
4463
4431
|
const extensionRoot = dependencyRoot(packageDir);
|
|
@@ -4495,7 +4463,7 @@ ${catalog.diagnostics.join(`
|
|
|
4495
4463
|
temp
|
|
4496
4464
|
].filter((path) => path !== undefined))
|
|
4497
4465
|
],
|
|
4498
|
-
writePaths: [sessionWritePath, temp, authPath, `${authPath}.lock`],
|
|
4466
|
+
writePaths: [sessionWritePath, temp, authPath, `${authPath}.lock`, `${settingsPath}.lock`],
|
|
4499
4467
|
signal
|
|
4500
4468
|
});
|
|
4501
4469
|
if (signal.aborted)
|
|
@@ -5195,13 +5163,10 @@ function windowsStatus() {
|
|
|
5195
5163
|
}
|
|
5196
5164
|
return status;
|
|
5197
5165
|
}
|
|
5198
|
-
function activeWindowsImplementation() {
|
|
5199
|
-
return windowsStatus().active;
|
|
5200
|
-
}
|
|
5201
5166
|
function shouldStartProxy(config) {
|
|
5202
5167
|
if (config.network.allowNetwork)
|
|
5203
5168
|
return false;
|
|
5204
|
-
return process.platform !== "win32" ||
|
|
5169
|
+
return process.platform !== "win32" || windowsStatus().active === "restrictedUser" || config.windows.allowLoopback;
|
|
5205
5170
|
}
|
|
5206
5171
|
function restrictedUserProxyPortRange() {
|
|
5207
5172
|
if (process.platform !== "win32")
|
|
@@ -5214,9 +5179,6 @@ function restrictedUserProxyPortRange() {
|
|
|
5214
5179
|
}
|
|
5215
5180
|
return { low: status.proxyPortLow, high: status.proxyPortHigh };
|
|
5216
5181
|
}
|
|
5217
|
-
function getConfigPaths(cwd) {
|
|
5218
|
-
return getPiConfigPaths(cwd, "sandbox.json");
|
|
5219
|
-
}
|
|
5220
5182
|
function readOrEmptyConfig(configPath) {
|
|
5221
5183
|
if (!existsSync5(configPath))
|
|
5222
5184
|
return {};
|
|
@@ -5232,11 +5194,11 @@ function writeConfigFile(configPath, config) {
|
|
|
5232
5194
|
`, "utf-8");
|
|
5233
5195
|
}
|
|
5234
5196
|
function getSandboxConfigWriteTarget(cwd, includeProject = true) {
|
|
5235
|
-
const { globalPath, projectPath } =
|
|
5197
|
+
const { globalPath, projectPath } = getPiConfigPaths(cwd, "sandbox.json");
|
|
5236
5198
|
return includeProject ? { scope: "project", path: projectPath } : { scope: "global", path: globalPath };
|
|
5237
5199
|
}
|
|
5238
5200
|
async function writeSandboxConfigEnabled(cwd, enabled, scope) {
|
|
5239
|
-
const paths =
|
|
5201
|
+
const paths = getPiConfigPaths(cwd, "sandbox.json");
|
|
5240
5202
|
const path = scope === "project" ? paths.projectPath : paths.globalPath;
|
|
5241
5203
|
await withFileMutationQueue3(path, async () => {
|
|
5242
5204
|
const config = readOrEmptyConfig(path);
|
|
@@ -5248,7 +5210,7 @@ async function writeSandboxConfigEnabled(cwd, enabled, scope) {
|
|
|
5248
5210
|
});
|
|
5249
5211
|
}
|
|
5250
5212
|
function loadSandboxEnabledSettings(cwd, includeProject) {
|
|
5251
|
-
const { projectPath } =
|
|
5213
|
+
const { projectPath } = getPiConfigPaths(cwd, "sandbox.json");
|
|
5252
5214
|
const global = loadSandboxConfig(cwd, false).enabled;
|
|
5253
5215
|
if (!includeProject || !existsSync5(projectPath))
|
|
5254
5216
|
return { global };
|
|
@@ -5265,25 +5227,6 @@ async function addConfigValue(configPath, value, readValues, writeValues) {
|
|
|
5265
5227
|
writeConfigFile(configPath, config);
|
|
5266
5228
|
});
|
|
5267
5229
|
}
|
|
5268
|
-
function addDomainToConfig(configPath, domain) {
|
|
5269
|
-
return addConfigValue(configPath, domain, (config) => config.network?.allowedDomains ?? [], (config, allowedDomains) => {
|
|
5270
|
-
config.network = {
|
|
5271
|
-
...config.network,
|
|
5272
|
-
allowedDomains,
|
|
5273
|
-
deniedDomains: config.network?.deniedDomains ?? []
|
|
5274
|
-
};
|
|
5275
|
-
});
|
|
5276
|
-
}
|
|
5277
|
-
function addReadPathToConfig(configPath, pathToAdd) {
|
|
5278
|
-
return addConfigValue(configPath, pathToAdd, (config) => config.filesystem?.allowRead ?? [], (config, allowRead) => {
|
|
5279
|
-
config.filesystem = { ...config.filesystem, allowRead };
|
|
5280
|
-
});
|
|
5281
|
-
}
|
|
5282
|
-
function addWritePathToConfig(configPath, pathToAdd) {
|
|
5283
|
-
return addConfigValue(configPath, pathToAdd, (config) => config.filesystem?.allowWrite ?? [], (config, allowWrite) => {
|
|
5284
|
-
config.filesystem = { ...config.filesystem, allowWrite };
|
|
5285
|
-
});
|
|
5286
|
-
}
|
|
5287
5230
|
function mergeAllowances(base, session, execution) {
|
|
5288
5231
|
return [...base, ...session, ...execution ?? []];
|
|
5289
5232
|
}
|
|
@@ -5449,28 +5392,10 @@ ${option.hint ?? "This changes persisted sandbox policy."}`, { signal });
|
|
|
5449
5392
|
}
|
|
5450
5393
|
return option.action;
|
|
5451
5394
|
}
|
|
5452
|
-
function promptDomainBlock(ctx, domain, signal) {
|
|
5453
|
-
return showPermissionPrompt(ctx, `Network blocked: "${domain}" is not in the allowed domains list`, PERMISSION_OPTIONS, signal);
|
|
5454
|
-
}
|
|
5455
5395
|
function promptReadBlock(ctx, filePath, reason, signal) {
|
|
5456
5396
|
const title = reason ? `Read blocked: "${filePath}" is in denyRead (${reason})` : `Read blocked: "${filePath}" is not in allowRead`;
|
|
5457
5397
|
return showPermissionPrompt(ctx, title, PERMISSION_OPTIONS, signal);
|
|
5458
5398
|
}
|
|
5459
|
-
function promptWriteBlock(ctx, filePath, signal) {
|
|
5460
|
-
return showPermissionPrompt(ctx, `Write blocked: "${filePath}" is not in allowWrite`, PERMISSION_OPTIONS, signal);
|
|
5461
|
-
}
|
|
5462
|
-
function promptFilesystemToolBlock(ctx, accesses, authorizationNote, signal) {
|
|
5463
|
-
const paths = accesses.map(({ operation, path }) => `${operation}: ${path}`).join(`
|
|
5464
|
-
`);
|
|
5465
|
-
const note = authorizationNote ? `
|
|
5466
|
-
|
|
5467
|
-
${authorizationNote}` : "";
|
|
5468
|
-
return showPermissionPrompt(ctx, `Filesystem policy blocked this tool call:
|
|
5469
|
-
${paths}${note}`, PERMISSION_OPTIONS, signal);
|
|
5470
|
-
}
|
|
5471
|
-
function promptNetworkBlock(ctx, operation, target, signal) {
|
|
5472
|
-
return showPermissionPrompt(ctx, `Network blocked: ${operation} "${target}"`, NETWORK_PERMISSION_OPTIONS, signal);
|
|
5473
|
-
}
|
|
5474
5399
|
function writeEnvFile(env, proxyPort, proxyToken) {
|
|
5475
5400
|
const lines = [];
|
|
5476
5401
|
for (const [key, value] of Object.entries(env)) {
|
|
@@ -5562,11 +5487,6 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
5562
5487
|
function loadConfig(cwd) {
|
|
5563
5488
|
return loadSandboxConfig(cwd, projectConfigTrusted);
|
|
5564
5489
|
}
|
|
5565
|
-
function createPlainBashTool(cwd) {
|
|
5566
|
-
return createBashToolDefinition(cwd, {
|
|
5567
|
-
shellPath: SettingsManager.create(cwd).getShellPath()
|
|
5568
|
-
});
|
|
5569
|
-
}
|
|
5570
5490
|
let sandboxEnabled = false;
|
|
5571
5491
|
let sandboxReady = false;
|
|
5572
5492
|
let sandboxState = "unavailable";
|
|
@@ -5702,16 +5622,6 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
5702
5622
|
function getWorkerExtensions() {
|
|
5703
5623
|
return [...workerExtensions].map(([id, extension]) => ({ id, entry: extension.entry }));
|
|
5704
5624
|
}
|
|
5705
|
-
function resetSessionAllowances() {
|
|
5706
|
-
sessionAllowedDomains.length = 0;
|
|
5707
|
-
sessionAllowedReadPaths.length = 0;
|
|
5708
|
-
sessionAllowedWritePaths.length = 0;
|
|
5709
|
-
sessionAllowedTargets.length = 0;
|
|
5710
|
-
unsandboxedWorkerWarningShown = false;
|
|
5711
|
-
}
|
|
5712
|
-
function getEffectiveAllowedDomains(config, allowances) {
|
|
5713
|
-
return mergeAllowances(config.network.allowedDomains, sessionAllowedDomains, allowances?.domains);
|
|
5714
|
-
}
|
|
5715
5625
|
function getEffectiveAllowRead(config, cwd, allowances) {
|
|
5716
5626
|
const paths = mergeAllowances(config.filesystem.allowRead, sessionAllowedReadPaths, allowances?.readPaths);
|
|
5717
5627
|
if (process.platform !== "win32")
|
|
@@ -5753,11 +5663,17 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
5753
5663
|
};
|
|
5754
5664
|
}
|
|
5755
5665
|
async function applyDomainChoice(choice, domain, cwd, allowances) {
|
|
5756
|
-
const { globalPath, projectPath } =
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
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
|
+
}
|
|
5761
5677
|
const target = choice === "once" ? allowances?.domains : sessionAllowedDomains;
|
|
5762
5678
|
if (target && !target.includes(domain))
|
|
5763
5679
|
target.push(domain);
|
|
@@ -5767,24 +5683,28 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
5767
5683
|
notify(ctx, `${verb} allowed (${choice}): ${scope}`, "info");
|
|
5768
5684
|
}
|
|
5769
5685
|
async function applyReadChoice(ctx, choice, filePath, cwd, allowances) {
|
|
5770
|
-
const { globalPath, projectPath } =
|
|
5686
|
+
const { globalPath, projectPath } = getPiConfigPaths(cwd, "sandbox.json");
|
|
5771
5687
|
const scope = sessionScopeFor(filePath, cwd);
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
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
|
+
}
|
|
5776
5694
|
const target = choice === "once" ? allowances?.readPaths : sessionAllowedReadPaths;
|
|
5777
5695
|
if (target && !target.includes(scope))
|
|
5778
5696
|
target.push(scope);
|
|
5779
5697
|
noteScope(ctx, "Read", choice, filePath, scope);
|
|
5780
5698
|
}
|
|
5781
5699
|
async function applyWriteChoice(ctx, choice, filePath, cwd, allowances) {
|
|
5782
|
-
const { globalPath, projectPath } =
|
|
5700
|
+
const { globalPath, projectPath } = getPiConfigPaths(cwd, "sandbox.json");
|
|
5783
5701
|
const scope = sessionScopeFor(filePath, cwd);
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
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
|
+
}
|
|
5788
5708
|
const target = choice === "once" ? allowances?.writePaths : sessionAllowedWritePaths;
|
|
5789
5709
|
if (target && !target.includes(scope))
|
|
5790
5710
|
target.push(scope);
|
|
@@ -5820,7 +5740,13 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
5820
5740
|
return permissionPrompts.resolve(current, async (promptSignal) => {
|
|
5821
5741
|
const config = loadConfig(ctx.cwd);
|
|
5822
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));
|
|
5823
|
-
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);
|
|
5824
5750
|
if (choice === "abort" || promptSignal.aborted) {
|
|
5825
5751
|
return { allowed: false, prompted: true, reason: "Filesystem access denied" };
|
|
5826
5752
|
}
|
|
@@ -5839,14 +5765,15 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
5839
5765
|
const config = loadConfig(cwd);
|
|
5840
5766
|
if (domainMatchesAny(domain, config.network.deniedDomains))
|
|
5841
5767
|
return false;
|
|
5842
|
-
|
|
5768
|
+
const allowedDomains = mergeAllowances(config.network.allowedDomains, sessionAllowedDomains, allowances?.domains);
|
|
5769
|
+
if (domainMatchesAny(domain, allowedDomains))
|
|
5843
5770
|
return true;
|
|
5844
5771
|
return;
|
|
5845
5772
|
};
|
|
5846
5773
|
if (!ctx.hasUI)
|
|
5847
5774
|
return current() ?? false;
|
|
5848
5775
|
return permissionPrompts.resolve(current, async (promptSignal) => {
|
|
5849
|
-
const choice = await
|
|
5776
|
+
const choice = await showPermissionPrompt(ctx, `Network blocked: "${domain}" is not in the allowed domains list`, PERMISSION_OPTIONS, promptSignal);
|
|
5850
5777
|
if (choice === "abort" || promptSignal.aborted)
|
|
5851
5778
|
return false;
|
|
5852
5779
|
await applyDomainChoice(choice, domain, cwd, allowances);
|
|
@@ -5922,7 +5849,7 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
5922
5849
|
if (!ctx.hasUI || !promptOnBlock)
|
|
5923
5850
|
return { action: "deny", reason: "unprompted" };
|
|
5924
5851
|
return permissionPrompts.resolve(current2, async (promptSignal) => {
|
|
5925
|
-
const choice = await
|
|
5852
|
+
const choice = await showPermissionPrompt(ctx, `Network blocked: ${trap.operation} "${trap.target}"`, NETWORK_PERMISSION_OPTIONS, promptSignal);
|
|
5926
5853
|
if (choice === "abort" || promptSignal.aborted)
|
|
5927
5854
|
return { action: "deny", reason: "rejected" };
|
|
5928
5855
|
const targets = choice === "once" ? allowances.targets : sessionAllowedTargets;
|
|
@@ -5947,7 +5874,7 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
5947
5874
|
return { action: "deny", reason: "unprompted" };
|
|
5948
5875
|
return permissionPrompts.resolve(current, async (promptSignal) => {
|
|
5949
5876
|
const config = loadConfig(cwd);
|
|
5950
|
-
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);
|
|
5951
5878
|
if (choice === "abort" || promptSignal.aborted)
|
|
5952
5879
|
return { action: "deny", reason: "rejected" };
|
|
5953
5880
|
if (trap.operation === "read") {
|
|
@@ -6547,7 +6474,7 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
6547
6474
|
const retryWithAccess = async (operation, blockedPath) => {
|
|
6548
6475
|
if (!ctx.hasUI)
|
|
6549
6476
|
return null;
|
|
6550
|
-
const { globalPath, projectPath } =
|
|
6477
|
+
const { globalPath, projectPath } = getPiConfigPaths(ctx.cwd, "sandbox.json");
|
|
6551
6478
|
const current = () => {
|
|
6552
6479
|
const config2 = loadConfig(ctx.cwd);
|
|
6553
6480
|
if (operation === "write" && matchesPattern(blockedPath, config2.filesystem.denyWrite, ctx.cwd)) {
|
|
@@ -6558,7 +6485,7 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
6558
6485
|
};
|
|
6559
6486
|
const access = await permissionPrompts.resolve(current, async (promptSignal) => {
|
|
6560
6487
|
const config2 = loadConfig(ctx.cwd);
|
|
6561
|
-
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);
|
|
6562
6489
|
if (choice === "abort" || promptSignal.aborted)
|
|
6563
6490
|
return false;
|
|
6564
6491
|
if (operation === "read") {
|
|
@@ -6657,7 +6584,7 @@ ${message}
|
|
|
6657
6584
|
return;
|
|
6658
6585
|
let implementation;
|
|
6659
6586
|
try {
|
|
6660
|
-
implementation =
|
|
6587
|
+
implementation = windowsStatus().active;
|
|
6661
6588
|
} catch (error) {
|
|
6662
6589
|
notify(ctx, formatError(error), "error");
|
|
6663
6590
|
return;
|
|
@@ -6677,7 +6604,7 @@ ${message}
|
|
|
6677
6604
|
const theme = ctx.ui.theme;
|
|
6678
6605
|
const dot = theme.fg("success", "●");
|
|
6679
6606
|
const label = theme.fg("text", "Sandbox");
|
|
6680
|
-
const windowsImplementation = process.platform === "win32" ?
|
|
6607
|
+
const windowsImplementation = process.platform === "win32" ? windowsStatus().active : undefined;
|
|
6681
6608
|
let networkLabel;
|
|
6682
6609
|
let networkColor;
|
|
6683
6610
|
if (config.network.allowNetwork) {
|
|
@@ -6752,7 +6679,9 @@ ${message}
|
|
|
6752
6679
|
return true;
|
|
6753
6680
|
}
|
|
6754
6681
|
function createBashTool(cwd) {
|
|
6755
|
-
const localBash =
|
|
6682
|
+
const localBash = createBashToolDefinition(cwd, {
|
|
6683
|
+
shellPath: SettingsManager.create(cwd).getShellPath()
|
|
6684
|
+
});
|
|
6756
6685
|
return {
|
|
6757
6686
|
...localBash,
|
|
6758
6687
|
label: "bash (landstrip)",
|
|
@@ -6804,7 +6733,11 @@ ${message}
|
|
|
6804
6733
|
pi.on("session_start", async (_event, ctx) => {
|
|
6805
6734
|
activeContext = ctx;
|
|
6806
6735
|
permissionPrompts.reset();
|
|
6807
|
-
|
|
6736
|
+
sessionAllowedDomains.length = 0;
|
|
6737
|
+
sessionAllowedReadPaths.length = 0;
|
|
6738
|
+
sessionAllowedWritePaths.length = 0;
|
|
6739
|
+
sessionAllowedTargets.length = 0;
|
|
6740
|
+
unsandboxedWorkerWarningShown = false;
|
|
6808
6741
|
const trustContext = ctx;
|
|
6809
6742
|
projectConfigTrusted = trustContext.isProjectTrusted?.() ?? false;
|
|
6810
6743
|
if (getNoSandboxFlag()) {
|
|
@@ -6842,10 +6775,10 @@ ${message}
|
|
|
6842
6775
|
enableSandbox(ctx);
|
|
6843
6776
|
}
|
|
6844
6777
|
}
|
|
6845
|
-
function
|
|
6778
|
+
function sandboxOverview(cwd, includeProject) {
|
|
6846
6779
|
const config = loadSandboxConfig(cwd, includeProject);
|
|
6847
|
-
const { globalPath, projectPath } =
|
|
6848
|
-
const windowsImplementation = process.platform === "win32" ?
|
|
6780
|
+
const { globalPath, projectPath } = getPiConfigPaths(cwd, "sandbox.json");
|
|
6781
|
+
const windowsImplementation = process.platform === "win32" ? windowsStatus().active : undefined;
|
|
6849
6782
|
return {
|
|
6850
6783
|
enabled: config.enabled,
|
|
6851
6784
|
running: sandboxEnabled && sandboxReady,
|
|
@@ -6866,7 +6799,7 @@ ${message}
|
|
|
6866
6799
|
load: loadSandboxEnabledSettings,
|
|
6867
6800
|
setEnabled: (ctx, enabled, scope) => applySandboxSetting(ctx, enabled, scope),
|
|
6868
6801
|
clearProject: (ctx) => applySandboxSetting(ctx, undefined, "project"),
|
|
6869
|
-
overview:
|
|
6802
|
+
overview: sandboxOverview
|
|
6870
6803
|
};
|
|
6871
6804
|
const integration = {
|
|
6872
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"
|