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.
Files changed (2) hide show
  1. package/dist/index.ts +88 -162
  2. 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
- return hexFg(color, text);
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 (!isLandstripKey(key))
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 name = agentNameFromPath(directory.path, filePath);
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: expandPattern(pattern), action };
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 suffix = safeRelative(join5(agentDir, "agents"), origin.path) ?? `${agent.name}.md`;
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 = projectConfigTarget(cwd, agentDir);
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
- if (projectConfigHasAgent(cwd, agent.name, agentDir))
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 = projectConfigTarget(cwd, agentDir);
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, configKindForPath(sourcePath), agent.name) : undefined;
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 = projectConfigTarget(cwd, agentDir);
1290
+ const target = getLandstripConfigTarget(cwd, "project", agentDir);
1318
1291
  return {
1319
1292
  title: `Edit @${agent.name}`,
1320
- content: editorSnippet(agent.name, raw, target.kind),
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 = sandboxOverview(ctx, integration);
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 && sandboxOverview(ctx, integration) ? "overview" : undefined,
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 = sandboxOverview(ctx, integration);
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 visibleAgents(runtime.getAgentCatalog(ctx)).map((agent) => ({
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 = visibleAgents(runtime.getAgentCatalog(ctx)).map((agent) => `@${agent.name}`).join(", ");
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 = visibleAgents(runtime.getAgentCatalog(ctx)).filter((agent) => agent.mode === "subagent").map((agent) => `@${agent.name}`).join(", ");
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 targetSessionDir = resolve2(defaultSessionDirForCwd(targetCwd));
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 = taskOutput(details, "");
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" || activeWindowsImplementation() === "restrictedUser" || config.windows.allowLoopback;
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 } = getConfigPaths(cwd);
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 = getConfigPaths(cwd);
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 } = getConfigPaths(cwd);
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 } = getConfigPaths(cwd);
5764
- if (choice === "project")
5765
- await addDomainToConfig(projectPath, domain);
5766
- if (choice === "global")
5767
- await addDomainToConfig(globalPath, domain);
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 } = getConfigPaths(cwd);
5686
+ const { globalPath, projectPath } = getPiConfigPaths(cwd, "sandbox.json");
5778
5687
  const scope = sessionScopeFor(filePath, cwd);
5779
- if (choice === "project")
5780
- await addReadPathToConfig(projectPath, scope);
5781
- if (choice === "global")
5782
- await addReadPathToConfig(globalPath, scope);
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 } = getConfigPaths(cwd);
5700
+ const { globalPath, projectPath } = getPiConfigPaths(cwd, "sandbox.json");
5790
5701
  const scope = sessionScopeFor(filePath, cwd);
5791
- if (choice === "project")
5792
- await addWritePathToConfig(projectPath, scope);
5793
- if (choice === "global")
5794
- await addWritePathToConfig(globalPath, scope);
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 choice = await promptFilesystemToolBlock(ctx, blocked, options2.authorizationNote, promptSignal);
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
- if (domainMatchesAny(domain, getEffectiveAllowedDomains(config, allowances)))
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 promptDomainBlock(ctx, domain, promptSignal);
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 promptNetworkBlock(ctx, trap.operation, trap.target, promptSignal);
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 promptWriteBlock(ctx, path, promptSignal);
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 } = getConfigPaths(ctx.cwd);
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 promptWriteBlock(ctx, blockedPath, promptSignal);
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 = activeWindowsImplementation();
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" ? activeWindowsImplementation() : undefined;
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 = createPlainBashTool(cwd);
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
- resetSessionAllowances();
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 sandboxOverview2(cwd, includeProject) {
6778
+ function sandboxOverview(cwd, includeProject) {
6853
6779
  const config = loadSandboxConfig(cwd, includeProject);
6854
- const { globalPath, projectPath } = getConfigPaths(cwd);
6855
- const windowsImplementation = process.platform === "win32" ? activeWindowsImplementation() : undefined;
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: sandboxOverview2
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.41",
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.41",
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"