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.
- package/api.ts +2 -4
- package/dist/index.ts +26 -148
- 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 {
|
|
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
|
-
|
|
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
|
-
|
|
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" && (
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
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 (!
|
|
6531
|
+
} else if (!getNoSandboxFlag()) {
|
|
6654
6532
|
enableSandbox(ctx);
|
|
6655
6533
|
}
|
|
6656
|
-
if (enabled &&
|
|
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 (!
|
|
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.
|
|
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.
|
|
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"
|