pi-landstrip 0.18.33 → 0.18.34

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/api.ts +2 -4
  2. package/dist/index.ts +26 -148
  3. package/package.json +2 -2
package/api.ts CHANGED
@@ -6,6 +6,8 @@ import type { Readable, Writable } from 'node:stream';
6
6
 
7
7
  import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';
8
8
 
9
+ import { isRecord } from '@landstrip/landstrip/shared';
10
+
9
11
  export const LANDSTRIP_CONTEXT_ENV = 'LANDSTRIP_CONTEXT';
10
12
  export const LANDSTRIP_RUNTIME_VERSION = 2;
11
13
 
@@ -168,10 +170,6 @@ function eventBus(pi: ExtensionAPI): EventBusLike | undefined {
168
170
  return (pi as ExtensionAPI & { events?: EventBusLike }).events;
169
171
  }
170
172
 
171
- function isRecord(value: unknown): value is Record<string, unknown> {
172
- return typeof value === 'object' && value !== null && !Array.isArray(value);
173
- }
174
-
175
173
  function isRuntime(value: unknown): value is PiLandstripRuntimeV2 {
176
174
  return (
177
175
  isRecord(value) &&
package/dist/index.ts CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  isIP,
21
21
  Socket as NetSocket
22
22
  } from "node:net";
23
- import { homedir as homedir3, tmpdir as tmpdir2 } from "node:os";
23
+ import { tmpdir as tmpdir2 } from "node:os";
24
24
  import { basename as basename3, dirname as dirname4, isAbsolute as isAbsolute3, join as join7, parse, resolve as resolve3, sep as sep4 } from "node:path";
25
25
  import { StringDecoder as StringDecoder2 } from "node:string_decoder";
26
26
  import { fileURLToPath as fileURLToPath2, URL } from "node:url";
@@ -35,8 +35,22 @@ import { matchesKey as matchesKey2, truncateToWidth as truncateToWidth3 } from "
35
35
  import {
36
36
  binaryPath
37
37
  } from "@landstrip/landstrip";
38
+ import {
39
+ allowsAllDomains,
40
+ controlResponseLine,
41
+ domainMatchesAny,
42
+ formatLandstripTraps,
43
+ isDenialTrap,
44
+ isFilesystemTrap,
45
+ isQueryTrap,
46
+ parseLandstripTraps,
47
+ parseTrapLine,
48
+ pathUnderDirectory,
49
+ sessionScopeFor
50
+ } from "@landstrip/landstrip/shared";
38
51
 
39
52
  // api.ts
53
+ import { isRecord } from "@landstrip/landstrip/shared";
40
54
  var LANDSTRIP_CONTEXT_ENV = "LANDSTRIP_CONTEXT";
41
55
  var LANDSTRIP_RUNTIME_VERSION = 2;
42
56
  var RUNTIME_REGISTER_EVENT = "landstrip:runtime:register:v2";
@@ -44,9 +58,6 @@ var RUNTIME_DISCOVER_EVENT = "landstrip:runtime:discover:v2";
44
58
  function eventBus(pi) {
45
59
  return pi.events;
46
60
  }
47
- function isRecord(value) {
48
- return typeof value === "object" && value !== null && !Array.isArray(value);
49
- }
50
61
  function publishLandstripRuntime(pi, runtime) {
51
62
  const events = eventBus(pi);
52
63
  if (!events)
@@ -198,15 +209,7 @@ var BUILT_IN_LANDSTRIP_CONFIG = {
198
209
  import { readFileSync } from "node:fs";
199
210
  import { homedir } from "node:os";
200
211
  import { isAbsolute, join, resolve } from "node:path";
201
- function expandHomePath(path) {
202
- if (path === "~" || path === "$HOME")
203
- return homedir();
204
- if (path.startsWith("~/"))
205
- return join(homedir(), path.slice(2));
206
- if (path.startsWith("$HOME/"))
207
- return join(homedir(), path.slice(6));
208
- return path;
209
- }
212
+ import { expandHomePath, isRecord as isRecord2 } from "@landstrip/landstrip/shared";
210
213
  function expandFileReferences(text, baseDir) {
211
214
  if (!text.includes("{file:"))
212
215
  return text;
@@ -237,9 +240,6 @@ function expandFileReferences(text, baseDir) {
237
240
  function formatError(error) {
238
241
  return error instanceof Error ? error.message : String(error);
239
242
  }
240
- function isRecord2(value) {
241
- return typeof value === "object" && value !== null && !Array.isArray(value);
242
- }
243
243
  var OPENCODE_THEME_COLORS = new Set([
244
244
  "primary",
245
245
  "secondary",
@@ -4627,23 +4627,6 @@ function addWritePathToConfig(configPath, pathToAdd) {
4627
4627
  function mergeAllowances(base, session, execution) {
4628
4628
  return [...base, ...session, ...execution ?? []];
4629
4629
  }
4630
- function domainMatchesPattern(domain, pattern) {
4631
- const normalizedDomain = domain.toLowerCase().replace(/\.+$/, "");
4632
- const normalizedPattern = pattern.toLowerCase().replace(/\.+$/, "");
4633
- if (normalizedPattern === "*")
4634
- return true;
4635
- if (normalizedPattern.startsWith("*.")) {
4636
- const base = normalizedPattern.slice(2);
4637
- return normalizedDomain === base || normalizedDomain.endsWith(`.${base}`);
4638
- }
4639
- return normalizedDomain === normalizedPattern;
4640
- }
4641
- function domainMatchesAny(domain, patterns) {
4642
- return patterns.some((pattern) => domainMatchesPattern(domain, pattern));
4643
- }
4644
- function allowsAllDomains(allowedDomains) {
4645
- return allowedDomains.includes("*");
4646
- }
4647
4630
  function shouldPromptForWrite(path, allowWrite, cwd) {
4648
4631
  return allowWrite.length === 0 || !matchesPattern(path, allowWrite, cwd);
4649
4632
  }
@@ -4710,40 +4693,6 @@ function normalizeBlockedPath(path, cwd) {
4710
4693
  const nativePath = process.platform === "win32" && /^\/[a-zA-Z](?:\/|$)/.test(path) ? `${path[1]}:${path.slice(2)}` : path;
4711
4694
  return canonicalizePath(isAbsolute3(nativePath) ? nativePath : join7(cwd, nativePath), cwd);
4712
4695
  }
4713
- function pathUnderDirectory(filePath, dir) {
4714
- const normalizedFilePath = normalizePathSeparators(filePath);
4715
- const normalizedDir = normalizePathSeparators(dir);
4716
- if (normalizedFilePath === normalizedDir)
4717
- return true;
4718
- const sep5 = normalizedDir.endsWith("/") ? "" : "/";
4719
- return normalizedFilePath.startsWith(normalizedDir + sep5);
4720
- }
4721
- function sessionScopeFor(filePath, baseDirectory) {
4722
- const dir = dirname4(filePath);
4723
- const normalizedDir = normalizePathSeparators(dir);
4724
- const home = homedir3();
4725
- const boundaries = new Set;
4726
- if (home)
4727
- boundaries.add(home);
4728
- try {
4729
- const realHome = realpathSync2.native(home);
4730
- if (realHome)
4731
- boundaries.add(realHome);
4732
- } catch {}
4733
- for (const boundary of boundaries) {
4734
- const normalizedBoundary = normalizePathSeparators(boundary);
4735
- if (pathUnderDirectory(dir, boundary)) {
4736
- const rest = normalizedDir.slice(normalizedBoundary.length).replace(/^\/+/, "");
4737
- const first = rest.split("/")[0];
4738
- if (!first)
4739
- return filePath;
4740
- return join7(boundary, first);
4741
- }
4742
- }
4743
- if (pathUnderDirectory(dir, baseDirectory))
4744
- return baseDirectory;
4745
- return dir;
4746
- }
4747
4696
  function longestPrefixMatch(path, patterns, cwd) {
4748
4697
  let best = -1;
4749
4698
  for (const pattern of patterns) {
@@ -4768,15 +4717,6 @@ function isPathLike(value) {
4768
4717
  function normalizePathMatch(value, cwd) {
4769
4718
  return isPathLike(value) ? normalizeBlockedPath(value, cwd) : null;
4770
4719
  }
4771
- function isFilesystemTrap(trap) {
4772
- return trap.kind === "filesystem";
4773
- }
4774
- function isDenialTrap(trap) {
4775
- return trap.kind === "filesystem" || trap.kind === "network";
4776
- }
4777
- function isQueryTrap(trap) {
4778
- return isDenialTrap(trap) && trap.state === "query";
4779
- }
4780
4720
  function extractBlockedPath(trapOutput, cwd) {
4781
4721
  const landstripErrors = parseLandstripTraps(trapOutput).filter(isFilesystemTrap);
4782
4722
  if (landstripErrors.length > 0) {
@@ -4822,67 +4762,6 @@ function extractTrapBlockedPath(trapOutput, cwd, operation) {
4822
4762
  }
4823
4763
  return null;
4824
4764
  }
4825
- function isLandstripTrap(value) {
4826
- if (typeof value !== "object" || value === null)
4827
- return false;
4828
- const obj = value;
4829
- switch (obj.kind) {
4830
- case "filesystem":
4831
- return (obj.operation === "read" || obj.operation === "write") && typeof obj.path === "string" && typeof obj.query_id === "string";
4832
- case "network":
4833
- return typeof obj.operation === "string" && typeof obj.target === "string" && typeof obj.query_id === "string";
4834
- case "launch":
4835
- return typeof obj.program === "string" && typeof obj.message === "string";
4836
- case "usage":
4837
- return typeof obj.message === "string";
4838
- case "internal":
4839
- return typeof obj.code === "string" && typeof obj.message === "string";
4840
- default:
4841
- return false;
4842
- }
4843
- }
4844
- function parseTrapLine(line) {
4845
- try {
4846
- const parsed = JSON.parse(line);
4847
- return isLandstripTrap(parsed) ? parsed : null;
4848
- } catch {
4849
- return null;
4850
- }
4851
- }
4852
- function parseLandstripTraps(output) {
4853
- const traps = [];
4854
- for (const line of output.trim().split(`
4855
- `)) {
4856
- const trap = parseTrapLine(line);
4857
- if (trap)
4858
- traps.push(trap);
4859
- }
4860
- return traps;
4861
- }
4862
- function formatLandstripTraps(traps) {
4863
- return traps.map((trap) => {
4864
- switch (trap.kind) {
4865
- case "filesystem":
4866
- return `landstrip: filesystem ${trap.operation} denied: ${trap.path} (${trap.mechanism})`;
4867
- case "network":
4868
- return `landstrip: network ${trap.operation} denied: ${trap.target} (${trap.mechanism})`;
4869
- case "launch":
4870
- return `landstrip: launch failed: ${trap.program}: ${trap.message}`;
4871
- case "usage":
4872
- return `landstrip: usage error: ${trap.message}`;
4873
- case "internal": {
4874
- const mechanism = trap.mechanism ? ` (${trap.mechanism})` : "";
4875
- return `landstrip: ${trap.code}${mechanism}: ${trap.message}`;
4876
- }
4877
- }
4878
- }).join(`
4879
- `);
4880
- }
4881
- function controlResponseLine(queryId, action) {
4882
- const response = { query_id: queryId, action };
4883
- return JSON.stringify(response) + `
4884
- `;
4885
- }
4886
4765
  function notify(ctx, message, level) {
4887
4766
  if (!ctx.hasUI)
4888
4767
  return;
@@ -5770,8 +5649,7 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
5770
5649
  if (options2.signal?.aborted)
5771
5650
  throw new Error("Task cancelled");
5772
5651
  if (!ensureSandboxState(options2.ctx)) {
5773
- const explicitlyDisabled = noSandboxFlag || !loadConfig(options2.cwd).enabled;
5774
- if (!explicitlyDisabled) {
5652
+ if (sandboxState === "unavailable") {
5775
5653
  throw new Error("Sandbox is unavailable; refusing subagent process");
5776
5654
  }
5777
5655
  if (!unsandboxedWorkerWarningShown) {
@@ -5877,7 +5755,7 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
5877
5755
  };
5878
5756
  }
5879
5757
  async function prepareProcess(options2) {
5880
- if (process.platform === "win32" && (noSandboxFlag || !loadConfig(options2.cwd).enabled)) {
5758
+ if (process.platform === "win32" && (getNoSandboxFlag() || !loadConfig(options2.cwd).enabled)) {
5881
5759
  throw new Error("Generic process preparation requires sandboxing on Windows");
5882
5760
  }
5883
5761
  return prepareRpcWorker({
@@ -6457,7 +6335,7 @@ ${message}
6457
6335
  enableStatus(ctx, config);
6458
6336
  return true;
6459
6337
  }
6460
- let noSandboxFlag = false;
6338
+ let getNoSandboxFlag = () => false;
6461
6339
  function disableSandbox(ctx) {
6462
6340
  sandboxEnabled = false;
6463
6341
  sandboxReady = false;
@@ -6465,7 +6343,7 @@ ${message}
6465
6343
  setTuiStatus(ctx, "sandbox", undefined);
6466
6344
  }
6467
6345
  function ensureSandboxState(ctx) {
6468
- if (noSandboxFlag) {
6346
+ if (getNoSandboxFlag()) {
6469
6347
  disableSandbox(ctx);
6470
6348
  return false;
6471
6349
  }
@@ -6511,6 +6389,7 @@ ${message}
6511
6389
  type: "boolean",
6512
6390
  default: false
6513
6391
  });
6392
+ getNoSandboxFlag = () => Boolean(maybePi.getFlag?.("no-sandbox"));
6514
6393
  unpublishRuntime?.();
6515
6394
  unpublishRuntime = publishLandstripRuntime(pi, integration);
6516
6395
  if (shouldRegisterBashTool)
@@ -6533,8 +6412,7 @@ ${message}
6533
6412
  resetSessionAllowances();
6534
6413
  const trustContext = ctx;
6535
6414
  projectConfigTrusted = trustContext.isProjectTrusted?.() ?? false;
6536
- noSandboxFlag = Boolean(maybePi.getFlag?.("no-sandbox"));
6537
- if (noSandboxFlag) {
6415
+ if (getNoSandboxFlag()) {
6538
6416
  disableSandbox(ctx);
6539
6417
  notify(ctx, "Sandbox disabled via --no-sandbox", "warning");
6540
6418
  if (!unpublishRuntime)
@@ -6574,7 +6452,7 @@ ${message}
6574
6452
  const shouldToggle = await ctx.ui.custom((tui, theme, _kb, done) => {
6575
6453
  const { muted, accent, text } = themeColors(theme);
6576
6454
  const sandboxStatus = () => {
6577
- if (noSandboxFlag)
6455
+ if (getNoSandboxFlag())
6578
6456
  return { color: "warning", label: "Disabled by --no-sandbox" };
6579
6457
  if (!config.enabled)
6580
6458
  return { color: "warning", label: "Disabled by configuration" };
@@ -6650,10 +6528,10 @@ ${target.path}`);
6650
6528
  const scope = await setSandboxConfigEnabled(ctx.cwd, enabled, projectTrusted);
6651
6529
  if (!enabled) {
6652
6530
  disableSandbox(ctx);
6653
- } else if (!noSandboxFlag) {
6531
+ } else if (!getNoSandboxFlag()) {
6654
6532
  enableSandbox(ctx);
6655
6533
  }
6656
- if (enabled && noSandboxFlag) {
6534
+ if (enabled && getNoSandboxFlag()) {
6657
6535
  notify(ctx, "Sandbox remains disabled via --no-sandbox", "warning");
6658
6536
  } else {
6659
6537
  notify(ctx, `Sandbox ${enabled ? "enabled" : "disabled"} in ${scope} config`, "info");
@@ -6669,7 +6547,7 @@ ${target.path}`);
6669
6547
  const effectiveEnabled = loadConfig(ctx.cwd).enabled;
6670
6548
  if (!effectiveEnabled) {
6671
6549
  disableSandbox(ctx);
6672
- } else if (!noSandboxFlag) {
6550
+ } else if (!getNoSandboxFlag()) {
6673
6551
  enableSandbox(ctx);
6674
6552
  }
6675
6553
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-landstrip",
3
- "version": "0.18.33",
3
+ "version": "0.18.34",
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": "^0.18.33",
42
+ "@landstrip/landstrip": "^0.18.34",
43
43
  "jsonc-parser": "^3.3.1",
44
44
  "minimatch": "^10.2.5",
45
45
  "yaml": "^2.9.0"