opencode-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 (3) hide show
  1. package/index.ts +21 -49
  2. package/package.json +2 -2
  3. package/tui.ts +10 -24
package/index.ts CHANGED
@@ -74,11 +74,6 @@ const REQUIRED_LANDSTRIP_VERSION = LANDSTRIP_VERSION.join('.');
74
74
  const SUPPORTED_PLATFORMS = new Set<NodeJS.Platform>(['linux', 'darwin', 'win32']);
75
75
  const DISCOVERY_CONNECT_TIMEOUT_MS = 250;
76
76
 
77
- function configuredShellPath(config: unknown): string | undefined {
78
- if (!isRecord(config)) return undefined;
79
- return typeof config.shell === 'string' ? config.shell : undefined;
80
- }
81
-
82
77
  function normalizePathForMatch(filePath: string): string {
83
78
  return process.platform === 'win32' ? normalizePathSeparators(filePath).toLowerCase() : filePath;
84
79
  }
@@ -310,12 +305,6 @@ function evaluateCommandDomains(
310
305
  );
311
306
  }
312
307
 
313
- function landstripVersion(): string | null {
314
- const result = spawnSync(landstripBinaryPath(), ['--version'], { encoding: 'utf-8' });
315
- if (result.status !== 0) return null;
316
- return result.stdout.trim();
317
- }
318
-
319
308
  function parseVersion(version: string): [number, number, number] | null {
320
309
  const match = version.match(/\b(\d+)\.(\d+)\.(\d+)\b/);
321
310
  if (!match) return null;
@@ -492,12 +481,6 @@ function trapPortAcceptsConnections(port: number): Promise<boolean> {
492
481
  });
493
482
  }
494
483
 
495
- async function readLiveDiscoveryPort(baseDirectory: string): Promise<number | null> {
496
- const port = readDiscoveryPort(baseDirectory);
497
- if (port === null) return null;
498
- return (await trapPortAcceptsConnections(port)) ? port : null;
499
- }
500
-
501
484
  function buildWrappedCommand(
502
485
  policyPath: string,
503
486
  shell: string,
@@ -542,10 +525,6 @@ function isGeneratedWrappedCommand(command: string): boolean {
542
525
  );
543
526
  }
544
527
 
545
- function landstripDescription(description: string): string {
546
- return description.endsWith(' (landstrip)') ? description : `${description} (landstrip)`;
547
- }
548
-
549
528
  function splitShellQuotedArgs(command: string): string[] {
550
529
  const args: string[] = [];
551
530
  let i = 0;
@@ -597,10 +576,6 @@ function getToolPath(args: Record<string, unknown>): string | undefined {
597
576
  return typeof filePath === 'string' ? filePath : undefined;
598
577
  }
599
578
 
600
- function getSearchPath(args: Record<string, unknown>): string {
601
- return typeof args.path === 'string' ? args.path : '.';
602
- }
603
-
604
579
  function extractPatchPaths(patchText: string): string[] {
605
580
  const paths: string[] = [];
606
581
 
@@ -636,7 +611,8 @@ function evaluateToolPermissions(
636
611
  }
637
612
 
638
613
  if (tool === 'glob' || tool === 'grep' || tool === 'list') {
639
- return [evaluateReadPermission(getSearchPath(args), config, baseDirectory, effectiveAllowRead)];
614
+ const searchPath = typeof args.path === 'string' ? args.path : '.';
615
+ return [evaluateReadPermission(searchPath, config, baseDirectory, effectiveAllowRead)];
640
616
  }
641
617
 
642
618
  if (tool === 'write' || tool === 'edit') {
@@ -679,16 +655,6 @@ const plugin: Plugin = async ({ client, directory }: PluginInput, options?: Plug
679
655
  return `${callID}:${kind}:${resource}`;
680
656
  }
681
657
 
682
- function rememberCallAllowances(
683
- callID: string | undefined,
684
- approvals: readonly SandboxPermissionDecision[],
685
- ): void {
686
- if (!callID) return;
687
- for (const approval of approvals) {
688
- callAllowances.add(allowanceKey(callID, approval.kind, approval.resource));
689
- }
690
- }
691
-
692
658
  function clearCallAllowances(callID: string): void {
693
659
  for (const key of callAllowances) {
694
660
  if (key.startsWith(`${callID}:`)) callAllowances.delete(key);
@@ -712,11 +678,6 @@ const plugin: Plugin = async ({ client, directory }: PluginInput, options?: Plug
712
678
  throw errorWithConfigPaths(directory, decision.message);
713
679
  }
714
680
 
715
- function enforcePermission(callID: string, decision: SandboxPermissionDecision): void {
716
- if (decision.status === 'allow' || hasCallAllowance(callID, decision)) return;
717
- reportBlocked(decision);
718
- }
719
-
720
681
  client.app
721
682
  ?.log?.({
722
683
  body: {
@@ -787,7 +748,8 @@ const plugin: Plugin = async ({ client, directory }: PluginInput, options?: Plug
787
748
 
788
749
  let version: string | null;
789
750
  try {
790
- version = landstripVersion();
751
+ const result = spawnSync(landstripBinaryPath(), ['--version'], { encoding: 'utf-8' });
752
+ version = result.status === 0 ? result.stdout.trim() : null;
791
753
  } catch (error) {
792
754
  landstripCheck = {
793
755
  ok: false,
@@ -889,8 +851,9 @@ const plugin: Plugin = async ({ client, directory }: PluginInput, options?: Plug
889
851
  const normalizedSessionID = sessionID?.trim() || undefined;
890
852
 
891
853
  const rewriteDescription = (): void => {
892
- if (typeof args.description === 'string')
893
- args.description = landstripDescription(args.description);
854
+ if (typeof args.description === 'string' && !args.description.endsWith(' (landstrip)')) {
855
+ args.description = `${args.description} (landstrip)`;
856
+ }
894
857
  };
895
858
 
896
859
  const existing = activeBash.get(callID);
@@ -965,9 +928,11 @@ const plugin: Plugin = async ({ client, directory }: PluginInput, options?: Plug
965
928
  // The TUI owns interactive query handling. Fall back to an in-process
966
929
  // broker when no TUI endpoint or session identity is available.
967
930
  const interactiveSessionID = normalizedSessionID ?? '';
931
+ const discoveredPort =
932
+ process.platform === 'linux' && interactiveSessionID ? readDiscoveryPort(directory) : null;
968
933
  const tuiTrapPort =
969
- process.platform === 'linux' && interactiveSessionID
970
- ? await readLiveDiscoveryPort(directory)
934
+ discoveredPort !== null && (await trapPortAcceptsConnections(discoveredPort))
935
+ ? discoveredPort
971
936
  : null;
972
937
  const trapServer =
973
938
  tuiTrapPort === null
@@ -1008,7 +973,9 @@ const plugin: Plugin = async ({ client, directory }: PluginInput, options?: Plug
1008
973
 
1009
974
  const hooks: Hooks = {
1010
975
  config: async (config) => {
1011
- configuredShell = configuredShellPath(config);
976
+ const value: unknown = config;
977
+ configuredShell =
978
+ isRecord(value) && typeof value.shell === 'string' ? value.shell : undefined;
1012
979
  },
1013
980
 
1014
981
  'permission.ask': async (input, output) => {
@@ -1057,7 +1024,11 @@ const plugin: Plugin = async ({ client, directory }: PluginInput, options?: Plug
1057
1024
  if (approvals.length === 0) return;
1058
1025
 
1059
1026
  output.status = 'ask';
1060
- rememberCallAllowances(callID, approvals);
1027
+ if (callID) {
1028
+ for (const approval of approvals) {
1029
+ callAllowances.add(allowanceKey(callID, approval.kind, approval.resource));
1030
+ }
1031
+ }
1061
1032
  },
1062
1033
 
1063
1034
  'tool.execute.before': async (input, output) => {
@@ -1080,7 +1051,8 @@ const plugin: Plugin = async ({ client, directory }: PluginInput, options?: Plug
1080
1051
  config.filesystem.allowWrite,
1081
1052
  );
1082
1053
  for (const decision of decisions) {
1083
- enforcePermission(input.callID, decision);
1054
+ if (decision.status === 'allow' || hasCallAllowance(input.callID, decision)) continue;
1055
+ reportBlocked(decision);
1084
1056
  }
1085
1057
  },
1086
1058
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-landstrip",
3
- "version": "0.18.41",
3
+ "version": "0.18.42",
4
4
  "description": "OS-level AI command sandboxing for OpenCode with Landstrip",
5
5
  "keywords": [
6
6
  "landlock",
@@ -48,7 +48,7 @@
48
48
  "ci:test": "npm test"
49
49
  },
50
50
  "dependencies": {
51
- "@landstrip/landstrip-api": "^0.18.41",
51
+ "@landstrip/landstrip-api": "^0.18.42",
52
52
  "@opentui/core": ">=0.3.4",
53
53
  "@opentui/solid": ">=0.3.4",
54
54
  "solid-js": "1.9.12"
package/tui.ts CHANGED
@@ -118,11 +118,6 @@ function formatPath(input: string, base: string): string {
118
118
  return absolute;
119
119
  }
120
120
 
121
- function selectedForeground(background: RGBA): RGBA {
122
- const luminance = 0.2126 * background.r + 0.7152 * background.g + 0.0722 * background.b;
123
- return luminance > 0.5 ? RGBA.fromInts(0, 0, 0) : RGBA.fromInts(255, 255, 255);
124
- }
125
-
126
121
  const tui: TuiPlugin = async (api, options, meta) => {
127
122
  const optionOverrides = normalizeOptions(options);
128
123
  const enabledManagedByOptions = optionOverrides.enabled !== undefined;
@@ -176,7 +171,10 @@ const tui: TuiPlugin = async (api, options, meta) => {
176
171
  },
177
172
  children: jsx('text', {
178
173
  get fg() {
179
- return selected() === index ? selectedForeground(theme.warning) : theme.textMuted;
174
+ if (selected() !== index) return theme.textMuted;
175
+ const background = theme.warning;
176
+ const luminance = 0.2126 * background.r + 0.7152 * background.g + 0.0722 * background.b;
177
+ return luminance > 0.5 ? RGBA.fromInts(0, 0, 0) : RGBA.fromInts(255, 255, 255);
180
178
  },
181
179
  children: option.label,
182
180
  }),
@@ -333,14 +331,11 @@ const tui: TuiPlugin = async (api, options, meta) => {
333
331
  );
334
332
  }
335
333
 
336
- function currentRouteSessionID(): string | undefined {
337
- const route = api.route.current;
338
- if (route.name !== 'session' || !route.params) return undefined;
339
- return typeof route.params.sessionID === 'string' ? route.params.sessionID : undefined;
340
- }
341
-
342
334
  function pump(): void {
343
- const routeSessionID = currentRouteSessionID();
335
+ const route = api.route.current;
336
+ if (route.name !== 'session' || !route.params) return;
337
+ const routeSessionID =
338
+ typeof route.params.sessionID === 'string' ? route.params.sessionID : undefined;
344
339
  if (!routeSessionID) return;
345
340
 
346
341
  const active = activeEntry();
@@ -360,8 +355,8 @@ const tui: TuiPlugin = async (api, options, meta) => {
360
355
  if (index === -1) return;
361
356
  const [next] = queue.splice(index, 1);
362
357
  if (!next) return;
363
- if (next.kind === 'fs-query') showFsQuery(next);
364
- else showNetworkQuery(next);
358
+ activeId = next.id;
359
+ setActiveEntry(next);
365
360
  }
366
361
 
367
362
  function enqueueEntry(entry: QueueEntry): void {
@@ -505,10 +500,6 @@ const tui: TuiPlugin = async (api, options, meta) => {
505
500
  }
506
501
  }
507
502
 
508
- function showFsQuery(entry: FsQueryEntry): void {
509
- activeId = entry.id;
510
- setActiveEntry(entry);
511
- }
512
503
  function resolveNetworkQuery(entry: NetworkQueryEntry, choice: NetworkQueryChoice): void {
513
504
  if (resolved.has(entry.id)) return;
514
505
  const action = choice === 'deny' ? 'deny' : 'allow';
@@ -533,11 +524,6 @@ const tui: TuiPlugin = async (api, options, meta) => {
533
524
  }
534
525
  }
535
526
 
536
- function showNetworkQuery(entry: NetworkQueryEntry): void {
537
- activeId = entry.id;
538
- setActiveEntry(entry);
539
- }
540
-
541
527
  // Query-response socket server (Linux-only — landstrip's socket protocol lives
542
528
  // in the seccomp broker). The server plugin connects each sandboxed run's
543
529
  // fd 3 here via a /dev/tcp redirect and we answer held writes interactively.