pi-landstrip 0.20.1 → 0.20.2

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 (3) hide show
  1. package/README.md +2 -3
  2. package/dist/index.ts +28 -110
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -15,8 +15,8 @@ unsandboxed execution.
15
15
  ## Permissions and configuration
16
16
 
17
17
  With sandboxing enabled, Bash (`bash`, `!`, `!!`) and workers are OS-isolated.
18
- Primary file tools remain in trusted Pi; `toolFilesystemPolicy: "sandbox"`
19
- adds policy checks, not isolation. Tool approval never bypasses hard denials.
18
+ Primary file tools run in trusted Pi and preflight paths against the sandbox
19
+ filesystem policy. Tool approval never bypasses hard denials.
20
20
 
21
21
  Configuration: bundled [`sandbox.json`](sandbox.json) → `~/.pi/agent/sandbox.json`
22
22
  → trusted-project `.pi/sandbox.json`. Arrays combine; later scalars win.
@@ -52,7 +52,6 @@ Configure `~/.pi/agent/landstrip.json`, trusted-project `.pi/landstrip.json`, or
52
52
  ```json
53
53
  {
54
54
  "maxSubagents": 2,
55
- "toolFilesystemPolicy": "sandbox",
56
55
  "permission": { "task": { "*": "deny", "review": "allow" } }
57
56
  }
58
57
  ```
package/dist/index.ts CHANGED
@@ -255,7 +255,6 @@ import {
255
255
  fuzzyFilter as fuzzyFilter2,
256
256
  matchesKey,
257
257
  Input,
258
- SelectList,
259
258
  SettingsList,
260
259
  Text,
261
260
  truncateToWidth,
@@ -488,7 +487,6 @@ class PermissionPromptCoordinator {
488
487
  // config.ts
489
488
  var BUILT_IN_LANDSTRIP_CONFIG = {
490
489
  maxSubagents: 0,
491
- toolFilesystemPolicy: "host",
492
490
  agent: {
493
491
  build: {
494
492
  description: "Implement and test requested changes",
@@ -561,7 +559,7 @@ function isProjectTrusted(ctx) {
561
559
  return trustContext.isProjectTrusted?.() ?? false;
562
560
  }
563
561
  var MAX_SUBAGENTS = 16;
564
- var LANDSTRIP_KEYS = ["maxSubagents", "agent", "permission", "toolFilesystemPolicy"];
562
+ var LANDSTRIP_KEYS = ["maxSubagents", "agent", "permission"];
565
563
  var JSON_FORMAT = { formattingOptions: { insertSpaces: true, tabSize: 2, eol: `
566
564
  ` } };
567
565
  function ensureFinalNewline(content) {
@@ -625,12 +623,6 @@ function parseLandstripConfig(value, path, kind) {
625
623
  }
626
624
  if ("permission" in value)
627
625
  config.permission = value.permission;
628
- if ("toolFilesystemPolicy" in value) {
629
- if (value.toolFilesystemPolicy !== "host" && value.toolFilesystemPolicy !== "sandbox") {
630
- throw new Error(`${path}: ${field("toolFilesystemPolicy")} must be host or sandbox`);
631
- }
632
- config.toolFilesystemPolicy = value.toolFilesystemPolicy;
633
- }
634
626
  return config;
635
627
  }
636
628
  function readLandstripConfigSource(cwd, scope, agentDir) {
@@ -745,23 +737,6 @@ async function setMaxSubagentsConfigForScope(cwd, maxSubagents, scope, agentDir
745
737
  async function clearMaxSubagentsConfigForScope(cwd, scope, agentDir = getAgentDir()) {
746
738
  await writeMaxSubagentsConfigForScope(cwd, undefined, scope, agentDir);
747
739
  }
748
- async function writeToolFilesystemPolicyConfigForScope(cwd, policy, scope, agentDir) {
749
- await updateLandstripConfigForScope(cwd, scope, agentDir, (landstrip) => {
750
- if (policy === undefined)
751
- delete landstrip.toolFilesystemPolicy;
752
- else
753
- landstrip.toolFilesystemPolicy = policy;
754
- });
755
- }
756
- async function setToolFilesystemPolicyConfigForScope(cwd, policy, scope, agentDir = getAgentDir()) {
757
- if (policy !== "host" && policy !== "sandbox") {
758
- throw new Error("toolFilesystemPolicy must be host or sandbox");
759
- }
760
- await writeToolFilesystemPolicyConfigForScope(cwd, policy, scope, agentDir);
761
- }
762
- async function clearToolFilesystemPolicyConfigForScope(cwd, scope, agentDir = getAgentDir()) {
763
- await writeToolFilesystemPolicyConfigForScope(cwd, undefined, scope, agentDir);
764
- }
765
740
  async function setAgentDisabledForScope(cwd, name, disabled, scope, agentDir = getAgentDir()) {
766
741
  await updateLandstripConfigForScope(cwd, scope, agentDir, (landstrip) => {
767
742
  if (landstrip.agent !== undefined && !isRecord2(landstrip.agent)) {
@@ -834,7 +809,6 @@ function loadLandstripConfig(cwd, includeProject = true, agentDir = getAgentDir(
834
809
  if (projectConfig)
835
810
  config = mergeValue(config, projectConfig);
836
811
  const maxSubagents = config.maxSubagents ?? 0;
837
- const toolFilesystemPolicy = config.toolFilesystemPolicy ?? "host";
838
812
  if (!Number.isInteger(maxSubagents) || maxSubagents < 0 || maxSubagents > MAX_SUBAGENTS) {
839
813
  throw new Error(`maxSubagents must be an integer from 0 to ${MAX_SUBAGENTS}`);
840
814
  }
@@ -844,7 +818,6 @@ function loadLandstripConfig(cwd, includeProject = true, agentDir = getAgentDir(
844
818
  ...config,
845
819
  maxSubagents,
846
820
  agent: config.agent,
847
- toolFilesystemPolicy,
848
821
  agentSources,
849
822
  agentPaths
850
823
  };
@@ -859,16 +832,6 @@ function loadMaxSubagentsSettings(cwd, includeProject = true, agentDir = getAgen
859
832
  }
860
833
  return { global, project };
861
834
  }
862
- function loadToolFilesystemPolicySettings(cwd, includeProject = true, agentDir = getAgentDir()) {
863
- const global = loadLandstripConfig(cwd, false, agentDir).toolFilesystemPolicy;
864
- if (!includeProject)
865
- return { global };
866
- const project = readLandstripConfigSource(cwd, "project", agentDir).config.toolFilesystemPolicy;
867
- if (project !== undefined && project !== "host" && project !== "sandbox") {
868
- throw new Error("toolFilesystemPolicy must be host or sandbox");
869
- }
870
- return { global, project };
871
- }
872
835
 
873
836
  // opencode-agents.ts
874
837
  import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3, statSync } from "node:fs";
@@ -1121,7 +1084,6 @@ function validateAgentRaw(name, raw) {
1121
1084
  function loadAgentCatalog(cwd, piAgentDir = getAgentDir2(), includeProject = true) {
1122
1085
  const diagnostics = [];
1123
1086
  let maxSubagents = 0;
1124
- let toolFilesystemPolicy = "host";
1125
1087
  let configuredAgents = {};
1126
1088
  let configuredPermission;
1127
1089
  let agentSources = new Map;
@@ -1129,7 +1091,6 @@ function loadAgentCatalog(cwd, piAgentDir = getAgentDir2(), includeProject = tru
1129
1091
  try {
1130
1092
  const config = loadLandstripConfig(cwd, includeProject, piAgentDir);
1131
1093
  maxSubagents = config.maxSubagents;
1132
- toolFilesystemPolicy = config.toolFilesystemPolicy;
1133
1094
  configuredAgents = config.agent;
1134
1095
  configuredPermission = config.permission;
1135
1096
  agentSources = new Map(config.agentSources);
@@ -1194,7 +1155,7 @@ function loadAgentCatalog(cwd, piAgentDir = getAgentDir2(), includeProject = tru
1194
1155
  } catch (error) {
1195
1156
  diagnostics.push(formatError(error));
1196
1157
  }
1197
- return { agents: normalized, permissions, diagnostics, maxSubagents, toolFilesystemPolicy };
1158
+ return { agents: normalized, permissions, diagnostics, maxSubagents };
1198
1159
  }
1199
1160
  function permissionMatches(pattern, permission) {
1200
1161
  return pattern === "*" || minimatch(permission, pattern, { dot: true });
@@ -1558,13 +1519,12 @@ var sandboxSubcommand = {
1558
1519
  };
1559
1520
  var settingsSubcommand = {
1560
1521
  name: "settings",
1561
- summary: "Manage concurrency and filesystem tool policies",
1522
+ summary: "Manage subagent concurrency settings",
1562
1523
  pane: (argv) => argv.rest ? undefined : "settings",
1563
1524
  run: (_argv, { ctx }) => {
1564
1525
  const projectTrusted = isProjectTrusted(ctx);
1565
1526
  const maxSubagents = loadMaxSubagentsSettings(ctx.cwd, projectTrusted);
1566
- const policy = loadToolFilesystemPolicySettings(ctx.cwd, projectTrusted);
1567
- ctx.ui.notify(`Settings: maxSubagents=${maxSubagents.project ?? maxSubagents.global} · toolFilesystemPolicy=${policy.project ?? policy.global}`, "info");
1527
+ ctx.ui.notify(`Settings: maxSubagents=${maxSubagents.project ?? maxSubagents.global}`, "info");
1568
1528
  }
1569
1529
  };
1570
1530
  var agentsSubcommand = {
@@ -3628,7 +3588,6 @@ class SubagentRuntime {
3628
3588
  primaryRules;
3629
3589
  primaryConfigurationError = false;
3630
3590
  maxSubagents = 0;
3631
- toolFilesystemPolicy = "host";
3632
3591
  primaryAgentSwitching = false;
3633
3592
  shuttingDown = false;
3634
3593
  activeSessionId;
@@ -3736,7 +3695,7 @@ ${this.primaryAgent.prompt}` };
3736
3695
  }
3737
3696
  }
3738
3697
  const filesystem = filesystemToolAccesses(event.toolName, input, ctx.cwd);
3739
- if (this.toolFilesystemPolicy === "sandbox" && filesystem) {
3698
+ if (filesystem && typeof this.integration.authorizeFilesystemToolAccess === "function") {
3740
3699
  if (!filesystem.valid) {
3741
3700
  return { block: true, reason: `Filesystem path unavailable for ${event.toolName}` };
3742
3701
  }
@@ -3822,7 +3781,6 @@ ${this.primaryAgent.prompt}` };
3822
3781
  refreshDeletableAgents();
3823
3782
  let overview = this.integration.sandboxCallbacks?.overview?.(ctx.cwd, projectTrusted);
3824
3783
  let maxSubagentsSettings = loadMaxSubagentsSettings(ctx.cwd, projectTrusted);
3825
- let policySettings = loadToolFilesystemPolicySettings(ctx.cwd, projectTrusted);
3826
3784
  let settingsList;
3827
3785
  let saving = false;
3828
3786
  let confirmingSandboxDisable = false;
@@ -3935,9 +3893,8 @@ ${this.primaryAgent.prompt}` };
3935
3893
  const settingValue = (project, global) => project === undefined ? `${global} (global)` : String(project);
3936
3894
  const syncSettingsList = () => {
3937
3895
  settingsList?.updateValue("maxSubagents", settingValue(maxSubagentsSettings.project, maxSubagentsSettings.global));
3938
- settingsList?.updateValue("toolFilesystemPolicy", settingValue(policySettings.project, policySettings.global));
3939
3896
  };
3940
- const saveSetting = (id, value) => {
3897
+ const saveSetting = (_id, value) => {
3941
3898
  if (!projectTrusted) {
3942
3899
  ctx.ui.notify("Project settings require a trusted project", "warning");
3943
3900
  syncSettingsList();
@@ -3945,35 +3902,24 @@ ${this.primaryAgent.prompt}` };
3945
3902
  }
3946
3903
  let save;
3947
3904
  let notifyMessage;
3948
- if (id === "maxSubagents") {
3949
- if (value === INHERIT || value === "") {
3950
- save = clearMaxSubagentsConfigForScope(ctx.cwd, "project");
3951
- notifyMessage = "Project maxSubagents inherits Global";
3952
- } else {
3953
- const parsed = Number(value);
3954
- if (!/^\d+$/.test(value) || !Number.isSafeInteger(parsed) || parsed < 0 || parsed > MAX_SUBAGENTS) {
3955
- ctx.ui.notify(`Enter a whole number between 0 and ${MAX_SUBAGENTS}`, "error");
3956
- syncSettingsList();
3957
- return;
3958
- }
3959
- save = setMaxSubagentsConfigForScope(ctx.cwd, parsed, "project");
3960
- notifyMessage = `Project maxSubagents = ${parsed}`;
3961
- }
3962
- } else if (value === INHERIT) {
3963
- save = clearToolFilesystemPolicyConfigForScope(ctx.cwd, "project");
3964
- notifyMessage = "Project toolFilesystemPolicy inherits Global";
3905
+ if (value === INHERIT || value === "") {
3906
+ save = clearMaxSubagentsConfigForScope(ctx.cwd, "project");
3907
+ notifyMessage = "Project maxSubagents inherits global setting";
3965
3908
  } else {
3966
- const policy = value === "host" ? "host" : "sandbox";
3967
- save = setToolFilesystemPolicyConfigForScope(ctx.cwd, policy, "project");
3968
- notifyMessage = `Project toolFilesystemPolicy = ${policy}`;
3909
+ const parsed = Number(value);
3910
+ if (!/^\d+$/.test(value) || !Number.isSafeInteger(parsed) || parsed < 0 || parsed > MAX_SUBAGENTS) {
3911
+ ctx.ui.notify(`Enter a whole number between 0 and ${MAX_SUBAGENTS}`, "error");
3912
+ syncSettingsList();
3913
+ return;
3914
+ }
3915
+ save = setMaxSubagentsConfigForScope(ctx.cwd, parsed, "project");
3916
+ notifyMessage = `Project maxSubagents = ${parsed}`;
3969
3917
  }
3970
3918
  saving = true;
3971
3919
  tui.requestRender();
3972
3920
  save.then(() => {
3973
3921
  maxSubagentsSettings = loadMaxSubagentsSettings(ctx.cwd, projectTrusted);
3974
- policySettings = loadToolFilesystemPolicySettings(ctx.cwd, projectTrusted);
3975
3922
  this.setMaxSubagents(maxSubagentsSettings.project ?? maxSubagentsSettings.global);
3976
- this.toolFilesystemPolicy = policySettings.project ?? policySettings.global;
3977
3923
  ctx.ui.notify(notifyMessage, "info");
3978
3924
  }).catch((error) => ctx.ui.notify(`Could not save setting: ${formatError(error)}`, "error")).finally(() => {
3979
3925
  saving = false;
@@ -3986,7 +3932,7 @@ ${this.primaryAgent.prompt}` };
3986
3932
  id: "maxSubagents",
3987
3933
  label: "Maximum Subagents",
3988
3934
  currentValue: settingValue(maxSubagentsSettings.project, maxSubagentsSettings.global),
3989
- description: "Concurrent subagent tasks. Enter to edit, empty to inherit Global.",
3935
+ description: "Concurrent subagent tasks. Enter to edit, empty to inherit global setting.",
3990
3936
  submenu: (_currentValue, doneEditing) => {
3991
3937
  const input = new Input;
3992
3938
  input.focused = true;
@@ -3998,38 +3944,6 @@ ${this.primaryAgent.prompt}` };
3998
3944
  input.onEscape = () => doneEditing(undefined);
3999
3945
  return input;
4000
3946
  }
4001
- },
4002
- {
4003
- id: "toolFilesystemPolicy",
4004
- label: "Filesystem Tool Policy",
4005
- currentValue: settingValue(policySettings.project, policySettings.global),
4006
- description: "How file tools resolve paths. Enter to choose.",
4007
- submenu: (_currentValue, doneEditing) => {
4008
- const options = [
4009
- {
4010
- value: INHERIT,
4011
- label: "inherit",
4012
- description: `Use Global (${policySettings.global})`
4013
- },
4014
- { value: "host", label: "host", description: "Resolve paths on the host" },
4015
- {
4016
- value: "sandbox",
4017
- label: "sandbox",
4018
- description: "Resolve paths inside the sandbox"
4019
- }
4020
- ];
4021
- const list = new SelectList(options, options.length, {
4022
- selectedPrefix: (text) => theme.fg("accent", text),
4023
- selectedText: (text) => theme.fg("accent", text),
4024
- description: (text) => theme.fg("dim", text),
4025
- scrollInfo: (text) => theme.fg("dim", text),
4026
- noMatch: (text) => theme.fg("dim", text)
4027
- });
4028
- list.setSelectedIndex(options.findIndex((option) => option.value === (policySettings.project ?? INHERIT)));
4029
- list.onSelect = (item) => doneEditing(item.value);
4030
- list.onCancel = () => doneEditing(undefined);
4031
- return list;
4032
- }
4033
3947
  }
4034
3948
  ];
4035
3949
  settingsList = new SettingsList(items, INSPECTOR_BODY_LINES, {
@@ -6296,7 +6210,6 @@ ${renderTaskResult(task.id, failed ? "error" : "completed", value)}`;
6296
6210
  this.primaryConfigurationError = false;
6297
6211
  const catalog = this.loadCatalog(ctx.cwd, getAgentDir4(), isProjectTrusted(ctx));
6298
6212
  this.maxSubagents = catalog.maxSubagents;
6299
- this.toolFilesystemPolicy = catalog.toolFilesystemPolicy;
6300
6213
  this.semaphore = new Semaphore(this.maxSubagents);
6301
6214
  if (catalog.diagnostics.length > 0) {
6302
6215
  this.invalidatePrimaryAgent(catalog, ctx);
@@ -6365,7 +6278,6 @@ ${renderTaskResult(task.id, failed ? "error" : "completed", value)}`;
6365
6278
  return false;
6366
6279
  this.primaryAgent = agent;
6367
6280
  this.primaryRules = mergePermissionRules(catalog.permissions, agent.permissions);
6368
- this.toolFilesystemPolicy = catalog.toolFilesystemPolicy;
6369
6281
  this.primaryConfigurationError = false;
6370
6282
  this.broker.reset();
6371
6283
  this.pi.registerTool(this.createTaskTool(undefined, this.primaryRules, ctx));
@@ -7087,8 +6999,16 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
7087
6999
  return [`${access.operation}\x00${path}`, { ...access, path }];
7088
7000
  })).values()
7089
7001
  ];
7002
+ if (getNoSandboxFlag())
7003
+ return { allowed: true, prompted: false };
7004
+ const blockedAccesses = (config) => {
7005
+ const enforceReads = enforcesShellReadPolicy(config.shell.readAccess);
7006
+ return normalized.filter((access) => access.operation === "read" ? enforceReads && !readAllowed(access.path, getEffectiveAllowRead(config, ctx.cwd), getEffectiveDenyRead(config, ctx.cwd), ctx.cwd) : shouldPromptForWrite(access.path, getEffectiveAllowWrite(config), ctx.cwd));
7007
+ };
7090
7008
  const current = () => {
7091
7009
  const config = loadConfig(ctx.cwd);
7010
+ if (!config.enabled)
7011
+ return { allowed: true, prompted: false };
7092
7012
  for (const access of normalized) {
7093
7013
  if (matchesPattern(access.path, access.operation === "read" ? config.filesystem.denyReadAlways : mergeArray(config.filesystem.denyWriteAlways, config.filesystem.denyWrite), ctx.cwd)) {
7094
7014
  return {
@@ -7098,8 +7018,7 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
7098
7018
  };
7099
7019
  }
7100
7020
  }
7101
- 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));
7102
- return blocked.length === 0 ? { allowed: true, prompted: false } : undefined;
7021
+ return blockedAccesses(config).length === 0 ? { allowed: true, prompted: false } : undefined;
7103
7022
  };
7104
7023
  const existing = current();
7105
7024
  if (existing)
@@ -7108,8 +7027,7 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
7108
7027
  return { allowed: false, prompted: false, reason: "Filesystem access requires approval" };
7109
7028
  }
7110
7029
  return permissionPrompts.resolve(current, async (promptSignal) => {
7111
- const config = loadConfig(ctx.cwd);
7112
- 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));
7030
+ const blocked = blockedAccesses(loadConfig(ctx.cwd));
7113
7031
  const paths = blocked.map(({ operation, path }) => `${operation}: ${path}`).join(`
7114
7032
  `);
7115
7033
  const note = options.authorizationNote ? `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-landstrip",
3
- "version": "0.20.1",
3
+ "version": "0.20.2",
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.20.1",
42
+ "@landstrip/landstrip-api": "^0.20.2",
43
43
  "jsonc-parser": "^3.3.1",
44
44
  "minimatch": "^10.2.5",
45
45
  "undici": "^8.9.0",