pi-landstrip 0.18.8 → 0.18.9

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 +67 -59
  2. package/package.json +2 -2
package/dist/index.ts CHANGED
@@ -1006,19 +1006,33 @@ function loadAgentCatalog(cwd, piAgentDir = getAgentDir2(), includeProject = tru
1006
1006
  }
1007
1007
  return { agents: normalized, permissions, diagnostics, maxSubagents };
1008
1008
  }
1009
+ function permissionMatches(pattern, permission) {
1010
+ return pattern === "*" || minimatch(permission, pattern, { dot: true });
1011
+ }
1009
1012
  function permissionDecision(rules, permission, resource = "*") {
1010
1013
  let decision = "ask";
1011
1014
  for (const rule of rules) {
1012
- const matchesPermission = rule.permission === "*" || minimatch(permission, rule.permission, { dot: true });
1013
1015
  const matchesResource = rule.pattern === "*" || minimatch(normalizeAbsolutePath(resource), normalizeAbsolutePath(rule.pattern), {
1014
1016
  dot: true,
1015
1017
  matchBase: false
1016
1018
  });
1017
- if (matchesPermission && matchesResource)
1019
+ if (permissionMatches(rule.permission, permission) && matchesResource)
1018
1020
  decision = rule.action;
1019
1021
  }
1020
1022
  return decision;
1021
1023
  }
1024
+ function permissionAlwaysDenied(rules, permission) {
1025
+ let denied = false;
1026
+ for (const rule of rules) {
1027
+ if (!permissionMatches(rule.permission, permission))
1028
+ continue;
1029
+ if (rule.pattern === "*")
1030
+ denied = rule.action === "deny";
1031
+ else if (rule.action !== "deny")
1032
+ denied = false;
1033
+ }
1034
+ return denied;
1035
+ }
1022
1036
  function mergePermissionRules(...values) {
1023
1037
  return values.flatMap((value) => value);
1024
1038
  }
@@ -1733,8 +1747,6 @@ var MAX_DEPTH = 3;
1733
1747
  var packageDir = dirname4(fileURLToPath(import.meta.url));
1734
1748
  var MAX_TASK_OUTPUT_BYTES = 64 * 1024;
1735
1749
  var INSPECTOR_BODY_LINES = 16;
1736
- var TASK_PREVIEW_MIN_WIDTH = 92;
1737
- var TASK_PREVIEW_LINES = 7;
1738
1750
  var PI_THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
1739
1751
  var AGENTS_HELP_ROWS = [
1740
1752
  ["Tab", "Next tab"],
@@ -1743,7 +1755,7 @@ var AGENTS_HELP_ROWS = [
1743
1755
  ["F", "Toggle task log follow"],
1744
1756
  ["Page Up / Down", "Scroll task output by page"],
1745
1757
  ["Home / End", "Jump to task output boundary"],
1746
- ["Enter", "Inspect task, set primary, or save"],
1758
+ ["Enter", "Open logs, set primary, or save"],
1747
1759
  ["Esc", "Back or close"],
1748
1760
  ["Ctrl+C", "Close"],
1749
1761
  ["Space", "Enable agent or toggle sandbox"],
@@ -2154,6 +2166,15 @@ function permissionName(tool) {
2154
2166
  return "list";
2155
2167
  return tool;
2156
2168
  }
2169
+ function permittedToolNames(tools, rules) {
2170
+ return tools.filter((tool) => !permissionAlwaysDenied(rules, permissionName(tool)));
2171
+ }
2172
+ function enforcePermittedTools(pi, rules) {
2173
+ const active = pi.getActiveTools();
2174
+ const permitted = permittedToolNames(active, rules);
2175
+ if (permitted.join("\x00") !== active.join("\x00"))
2176
+ pi.setActiveTools(permitted);
2177
+ }
2157
2178
  function canonicalPermissionPath(path, seen = new Set) {
2158
2179
  const missing = [];
2159
2180
  let existing = path;
@@ -2243,6 +2264,10 @@ function parseControlResponse(value) {
2243
2264
  function registerSubagentWorker(pi, config) {
2244
2265
  pi.on("session_start", () => {
2245
2266
  delete process.env[WORKER_ENV];
2267
+ enforcePermittedTools(pi, config.rules);
2268
+ });
2269
+ pi.on("before_agent_start", () => {
2270
+ enforcePermittedTools(pi, config.rules);
2246
2271
  });
2247
2272
  pi.on("tool_call", async (event, ctx) => {
2248
2273
  if (event.toolName === "task")
@@ -2434,7 +2459,7 @@ ${this.primaryAgent.prompt}` };
2434
2459
  let maxSubagentsSettings = loadMaxSubagentsSettings(ctx.cwd, projectTrusted);
2435
2460
  const sandboxCallbacks = this.integration.sandboxCallbacks;
2436
2461
  let sandboxSettings = sandboxCallbacks?.load(ctx.cwd, projectTrusted) ?? { global: true };
2437
- let tab = requested ? "tasks" : "agents";
2462
+ let tab = requested ? "logs" : "agents";
2438
2463
  let selectedAgent = Math.max(0, agents.findIndex((agent) => agent.name === this.primaryAgent?.name));
2439
2464
  let selectedTask = requested ? tasks.findIndex((task) => task.id === requested || task.id.startsWith(requested)) : 0;
2440
2465
  if (selectedTask < 0) {
@@ -2446,8 +2471,7 @@ ${this.primaryAgent.prompt}` };
2446
2471
  let saving = false;
2447
2472
  let editing = false;
2448
2473
  let confirmingDeleteAgent;
2449
- let detail = requested.length > 0;
2450
- let follow = detail;
2474
+ let follow = requested.length > 0;
2451
2475
  let scroll = 0;
2452
2476
  const reloadAgents = () => {
2453
2477
  const selectedName = agents[selectedAgent]?.name;
@@ -2500,7 +2524,7 @@ ${this.primaryAgent.prompt}` };
2500
2524
  return `${clipped}${" ".repeat(Math.max(0, cellWidth - visibleWidth2(clipped)))}`;
2501
2525
  };
2502
2526
  refreshTasks();
2503
- const tabs = `${dialogTabs(theme, ["Agents", "Tasks", "Settings", "Help"], `${tab[0]?.toUpperCase()}${tab.slice(1)}`)}${tab === "tasks" ? "" : theme.fg("dim", ` · Scope: ${scope === "project" ? "Project" : "Global"}`)}`;
2527
+ const tabs = `${dialogTabs(theme, ["Agents", "Tasks", "Logs", "Settings", "Help"], `${tab[0]?.toUpperCase()}${tab.slice(1)}`)}${tab === "tasks" || tab === "logs" ? "" : theme.fg("dim", ` · Scope: ${scope === "project" ? "Project" : "Global"}`)}`;
2504
2528
  if (tab === "agents") {
2505
2529
  const start = Math.max(0, Math.min(selectedAgent - 3, agents.length - 7));
2506
2530
  const shown2 = agents.slice(start, start + 7);
@@ -2580,13 +2604,19 @@ ${this.primaryAgent.prompt}` };
2580
2604
  return box(lines2);
2581
2605
  }
2582
2606
  if (tab === "settings") {
2583
- const maxSubagents = scope === "project" && maxSubagentsSettings.project === undefined ? "-" : String(selectedMaxSubagents());
2584
- const sandbox = scope === "project" && sandboxSettings.project === undefined ? "-" : selectedSandboxEnabled() ? "on" : "off";
2607
+ const maxSubagentsInherited = scope === "project" && maxSubagentsSettings.project === undefined;
2608
+ const sandboxInherited = scope === "project" && sandboxSettings.project === undefined;
2585
2609
  const rows = [
2586
- { label: "Maximum subagents", value: maxSubagents, unavailable: false },
2610
+ {
2611
+ label: "Maximum subagents",
2612
+ value: String(selectedMaxSubagents()),
2613
+ inherited: maxSubagentsInherited,
2614
+ unavailable: false
2615
+ },
2587
2616
  {
2588
2617
  label: "Sandbox enabled",
2589
- value: sandbox,
2618
+ value: selectedSandboxEnabled() ? "on" : "off",
2619
+ inherited: sandboxInherited,
2590
2620
  unavailable: sandboxCallbacks === undefined
2591
2621
  }
2592
2622
  ];
@@ -2595,12 +2625,12 @@ ${this.primaryAgent.prompt}` };
2595
2625
  const selected = index === selectedSetting;
2596
2626
  const cursor = selected ? theme.fg("accent", "›") : " ";
2597
2627
  const text = `[ ${row.value} ]`;
2598
- const value = selected ? theme.fg("accent", text) : theme.fg(row.unavailable ? "muted" : "text", text);
2628
+ const value = row.inherited ? theme.fg("dim", text) : selected ? theme.fg("accent", text) : theme.fg(row.unavailable ? "muted" : "text", text);
2599
2629
  const label = theme.fg(row.unavailable ? "muted" : "text", row.label);
2600
2630
  const dirty = dirtySettings.has(index) ? theme.fg("warning", " *") : "";
2601
2631
  lines2.push(`${cursor} ${value} ${label}${dirty}`);
2602
2632
  }
2603
- lines2.push("", theme.fg("dim", selectedSetting === 0 ? "Sets concurrent subagents. Zero disables the task tool." : "Controls OS sandboxing. Use /sandbox to inspect its policy."));
2633
+ lines2.push("", theme.fg("dim", selectedSetting === 0 ? rows[0]?.inherited ? "Sets concurrent subagents. Zero disables the task tool. Using the Global value." : "Sets concurrent subagents. Zero disables the task tool." : rows[1]?.inherited ? "Controls OS sandboxing. Use /sandbox to inspect its policy. Using the Global value." : "Controls OS sandboxing. Use /sandbox to inspect its policy."));
2604
2634
  if (saving)
2605
2635
  lines2.push("", theme.fg("dim", "Saving…"));
2606
2636
  return box(lines2);
@@ -2616,51 +2646,21 @@ ${this.primaryAgent.prompt}` };
2616
2646
  ];
2617
2647
  return box(lines2);
2618
2648
  }
2619
- if (!detail) {
2649
+ if (tab === "tasks") {
2620
2650
  const listLines = [];
2621
2651
  if (tasks.length === 0) {
2622
2652
  listLines.push("No task sessions in this session.");
2623
2653
  } else {
2624
2654
  const start = Math.max(0, Math.min(selectedTask - 5, tasks.length - 11));
2625
2655
  const shown2 = tasks.slice(start, start + 11);
2626
- for (const [offset, task3] of shown2.entries()) {
2656
+ for (const [offset, task2] of shown2.entries()) {
2627
2657
  const index = start + offset;
2628
2658
  const cursor = index === selectedTask ? theme.fg("accent", "›") : " ";
2629
- const indent = " ".repeat(Math.max(0, task3.depth - 1));
2630
- listLines.push(`${cursor} ${indent}${taskState(theme, task3)} ${theme.fg("accent", `@${task3.agent}`)} ${theme.fg("text", task3.description)} ${theme.fg("dim", task3.id.slice(0, 8))}`);
2659
+ const indent = " ".repeat(Math.max(0, task2.depth - 1));
2660
+ listLines.push(`${cursor} ${indent}${taskState(theme, task2)} ${theme.fg("accent", `@${task2.agent}`)} ${theme.fg("text", task2.description)} ${theme.fg("dim", task2.id.slice(0, 8))}`);
2631
2661
  }
2632
2662
  }
2633
- if (width < TASK_PREVIEW_MIN_WIDTH || tasks.length === 0) {
2634
- return box([tabs, "", ...listLines]);
2635
- }
2636
- const task2 = tasks[selectedTask];
2637
- if (!task2)
2638
- return box([tabs, "", ...listLines]);
2639
- const leftWidth = Math.max(44, Math.min(64, Math.floor(contentWidth * 0.52)));
2640
- const rightWidth = Math.max(1, contentWidth - leftWidth - 3);
2641
- const transcript2 = taskTranscript(task2).flatMap((line) => wrapTextWithAnsi(line, rightWidth));
2642
- const duration2 = taskDuration(taskDetails(task2));
2643
- const metrics2 = [
2644
- task2.currentTool ? `→ ${task2.currentTool}` : undefined,
2645
- task2.toolCalls === undefined ? undefined : `${task2.toolCalls} tool call${task2.toolCalls === 1 ? "" : "s"}`,
2646
- duration2,
2647
- task2.retryAttempt ? `retry ${task2.retryAttempt}` : undefined
2648
- ].filter(Boolean);
2649
- const previewLines = [
2650
- `${theme.fg("accent", theme.bold(`@${task2.agent}`))} ${theme.fg("text", task2.description)}`,
2651
- `${taskState(theme, task2)} ${theme.fg("dim", task2.id)}`,
2652
- metrics2.length > 0 ? theme.fg("dim", metrics2.join(" · ")) : "",
2653
- theme.fg("dim", "Latest activity"),
2654
- ...transcript2.slice(-TASK_PREVIEW_LINES).map((line) => theme.fg("toolOutput", line))
2655
- ];
2656
- const divider = theme.fg("border", "│");
2657
- const rowCount = Math.max(listLines.length, previewLines.length);
2658
- const lines2 = Array.from({ length: rowCount }, (_, index) => {
2659
- const left = pad(listLines[index] ?? "", leftWidth);
2660
- const right = truncateToWidth2(previewLines[index] ?? "", rightWidth);
2661
- return `${left} ${divider} ${right}`;
2662
- });
2663
- return box([tabs, "", ...lines2]);
2663
+ return box([tabs, "", ...listLines]);
2664
2664
  }
2665
2665
  const task = tasks[selectedTask];
2666
2666
  if (!task)
@@ -2742,10 +2742,12 @@ ${this.primaryAgent.prompt}` };
2742
2742
  ctx.ui.notify("Save changes with Enter or discard them with R before leaving", "warning");
2743
2743
  return;
2744
2744
  }
2745
- const tabs = ["agents", "tasks", "settings", "help"];
2745
+ const tabs = ["agents", "tasks", "logs", "settings", "help"];
2746
2746
  tab = tabs[(tabs.indexOf(tab) + 1) % tabs.length] ?? "agents";
2747
- detail = false;
2748
- follow = false;
2747
+ if (tab === "logs" && tasks.length > 0)
2748
+ follow = true;
2749
+ else
2750
+ follow = false;
2749
2751
  editing = false;
2750
2752
  scroll = 0;
2751
2753
  tui.requestRender();
@@ -2916,7 +2918,7 @@ ${this.primaryAgent.prompt}` };
2916
2918
  done();
2917
2919
  return;
2918
2920
  }
2919
- if (!detail) {
2921
+ if (tab === "tasks") {
2920
2922
  if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c"))
2921
2923
  done();
2922
2924
  else if (matchesKey(data, "up"))
@@ -2924,7 +2926,7 @@ ${this.primaryAgent.prompt}` };
2924
2926
  else if (matchesKey(data, "down") && tasks.length > 0) {
2925
2927
  selectedTask = Math.min(tasks.length - 1, selectedTask + 1);
2926
2928
  } else if (matchesKey(data, "return") && tasks.length > 0) {
2927
- detail = true;
2929
+ tab = "logs";
2928
2930
  follow = true;
2929
2931
  scroll = 0;
2930
2932
  } else
@@ -2932,11 +2934,16 @@ ${this.primaryAgent.prompt}` };
2932
2934
  tui.requestRender();
2933
2935
  return;
2934
2936
  }
2937
+ if (tab !== "logs")
2938
+ return;
2935
2939
  const task = tasks[selectedTask];
2936
- if (!task)
2940
+ if (!task) {
2941
+ if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c"))
2942
+ done();
2937
2943
  return;
2944
+ }
2938
2945
  if (matchesKey(data, "escape")) {
2939
- detail = false;
2946
+ tab = "tasks";
2940
2947
  follow = false;
2941
2948
  scroll = 0;
2942
2949
  } else if (matchesKey(data, "ctrl+c")) {
@@ -2961,7 +2968,7 @@ ${this.primaryAgent.prompt}` };
2961
2968
  selectedTask = parentIndex;
2962
2969
  follow = true;
2963
2970
  } else {
2964
- detail = false;
2971
+ tab = "tasks";
2965
2972
  follow = false;
2966
2973
  }
2967
2974
  scroll = 0;
@@ -3434,7 +3441,8 @@ ${catalog.diagnostics.join(`
3434
3441
  ctx.ui.notify(`Agent ${agent.name} options are not supported by Pi RPC mode: ${providerOptionNames.join(", ")}`, "warning");
3435
3442
  }
3436
3443
  const taskEnabled = agent.permissions.some((rule) => rule.permission === "task" && rule.action !== "deny");
3437
- const tools = taskEnabled ? [...new Set([...this.pi.getActiveTools(), "task"])] : this.pi.getActiveTools().filter((tool) => tool !== "task");
3444
+ const activeTools = taskEnabled ? [...new Set([...this.pi.getActiveTools(), "task"])] : this.pi.getActiveTools().filter((tool) => tool !== "task");
3445
+ const tools = permittedToolNames(activeTools, rules);
3438
3446
  const workerExtensions = this.integration.getWorkerExtensions?.() ?? [];
3439
3447
  const args = [
3440
3448
  ...invocation.args,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-landstrip",
3
- "version": "0.18.8",
3
+ "version": "0.18.9",
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.8",
42
+ "@landstrip/landstrip": "^0.18.9",
43
43
  "jsonc-parser": "^3.3.1",
44
44
  "minimatch": "^10.2.5",
45
45
  "yaml": "^2.9.0"