pi-landstrip 0.18.8 → 0.18.10

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 +169 -64
  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"],
@@ -1853,6 +1865,7 @@ function taskDetails(task, state = task.state) {
1853
1865
  currentTool: task.currentTool,
1854
1866
  toolCalls: task.toolCalls,
1855
1867
  retryAttempt: task.retryAttempt,
1868
+ usage: task.usage ? { ...task.usage } : undefined,
1856
1869
  startedAt: task.startedAt,
1857
1870
  finishedAt: task.finishedAt,
1858
1871
  output: task.output,
@@ -1866,6 +1879,78 @@ function taskDuration(details) {
1866
1879
  const seconds = Math.max(0, end - details.startedAt) / 1000;
1867
1880
  return seconds < 10 ? `${seconds.toFixed(1)}s` : `${Math.round(seconds)}s`;
1868
1881
  }
1882
+ function usageNumber(value) {
1883
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
1884
+ }
1885
+ function isTaskUsage(value) {
1886
+ return isRecord2(value) && usageNumber(value.input) && usageNumber(value.output) && usageNumber(value.cacheRead) && usageNumber(value.cacheWrite) && usageNumber(value.cost) && usageNumber(value.turns) && Number.isInteger(value.turns);
1887
+ }
1888
+ function taskUsageFromMessage(message) {
1889
+ if (!isRecord2(message) || message.role !== "assistant" || !isRecord2(message.usage))
1890
+ return;
1891
+ const usage = message.usage;
1892
+ if (!usageNumber(usage.input) || !usageNumber(usage.output) || !usageNumber(usage.cacheRead) || !usageNumber(usage.cacheWrite) || !isRecord2(usage.cost) || !usageNumber(usage.cost.total)) {
1893
+ return;
1894
+ }
1895
+ return {
1896
+ input: usage.input,
1897
+ output: usage.output,
1898
+ cacheRead: usage.cacheRead,
1899
+ cacheWrite: usage.cacheWrite,
1900
+ cost: usage.cost.total,
1901
+ turns: 1
1902
+ };
1903
+ }
1904
+ function addTaskUsage(current, added) {
1905
+ return {
1906
+ input: (current?.input ?? 0) + added.input,
1907
+ output: (current?.output ?? 0) + added.output,
1908
+ cacheRead: (current?.cacheRead ?? 0) + added.cacheRead,
1909
+ cacheWrite: (current?.cacheWrite ?? 0) + added.cacheWrite,
1910
+ cost: (current?.cost ?? 0) + added.cost,
1911
+ turns: (current?.turns ?? 0) + added.turns
1912
+ };
1913
+ }
1914
+ function formatTokens(count) {
1915
+ if (count < 1000)
1916
+ return String(count);
1917
+ if (count < 1e4)
1918
+ return `${(count / 1000).toFixed(1)}k`;
1919
+ if (count < 1e6)
1920
+ return `${Math.round(count / 1000)}k`;
1921
+ if (count < 1e7)
1922
+ return `${(count / 1e6).toFixed(1)}M`;
1923
+ return `${Math.round(count / 1e6)}M`;
1924
+ }
1925
+ function formatTaskUsage(usage) {
1926
+ if (!usage)
1927
+ return;
1928
+ const parts = [];
1929
+ if (usage.turns)
1930
+ parts.push(`${usage.turns} turn${usage.turns === 1 ? "" : "s"}`);
1931
+ if (usage.input)
1932
+ parts.push(`in:${formatTokens(usage.input)}`);
1933
+ if (usage.output)
1934
+ parts.push(`out:${formatTokens(usage.output)}`);
1935
+ if (usage.cacheRead)
1936
+ parts.push(`R${formatTokens(usage.cacheRead)}`);
1937
+ if (usage.cacheWrite)
1938
+ parts.push(`W${formatTokens(usage.cacheWrite)}`);
1939
+ if (usage.cost)
1940
+ parts.push(`$${usage.cost.toFixed(4)}`);
1941
+ return parts.length > 0 ? parts.join(" ") : undefined;
1942
+ }
1943
+ function formatTaskUsageSummary(usage) {
1944
+ if (!usage)
1945
+ return;
1946
+ const parts = [];
1947
+ const tokens = usage.input + usage.output;
1948
+ if (tokens)
1949
+ parts.push(`${formatTokens(tokens)} tok`);
1950
+ if (usage.cost)
1951
+ parts.push(`$${usage.cost.toFixed(4)}`);
1952
+ return parts.length > 0 ? parts.join(" ") : undefined;
1953
+ }
1869
1954
  function activeTaskRecords(tasks) {
1870
1955
  const byId = new Map(tasks.map((task) => [task.id, task]));
1871
1956
  const visibleIds = new Set;
@@ -1893,6 +1978,9 @@ function taskProgress(task) {
1893
1978
  if (task.toolCalls !== undefined) {
1894
1979
  progress.push(`${task.toolCalls} call${task.toolCalls === 1 ? "" : "s"}`);
1895
1980
  }
1981
+ const usage = formatTaskUsageSummary(task.usage);
1982
+ if (usage)
1983
+ progress.push(usage);
1896
1984
  const duration = taskDuration(taskDetails(task));
1897
1985
  if (duration)
1898
1986
  progress.push(duration);
@@ -2154,6 +2242,15 @@ function permissionName(tool) {
2154
2242
  return "list";
2155
2243
  return tool;
2156
2244
  }
2245
+ function permittedToolNames(tools, rules) {
2246
+ return tools.filter((tool) => !permissionAlwaysDenied(rules, permissionName(tool)));
2247
+ }
2248
+ function enforcePermittedTools(pi, rules) {
2249
+ const active = pi.getActiveTools();
2250
+ const permitted = permittedToolNames(active, rules);
2251
+ if (permitted.join("\x00") !== active.join("\x00"))
2252
+ pi.setActiveTools(permitted);
2253
+ }
2157
2254
  function canonicalPermissionPath(path, seen = new Set) {
2158
2255
  const missing = [];
2159
2256
  let existing = path;
@@ -2235,14 +2332,20 @@ function parseControlResponse(value) {
2235
2332
  if (response.value !== undefined && typeof response.value !== "string") {
2236
2333
  throw new Error("Invalid supervisor response value");
2237
2334
  }
2238
- if (response.task !== undefined && (!isRecord2(response.task) || typeof response.task.taskId !== "string" || typeof response.task.state !== "string" || !TASK_STATES.has(response.task.state) || typeof response.task.agent !== "string")) {
2239
- throw new Error("Invalid supervisor task response");
2335
+ if (response.task !== undefined) {
2336
+ if (!isRecord2(response.task) || typeof response.task.taskId !== "string" || typeof response.task.state !== "string" || !TASK_STATES.has(response.task.state) || typeof response.task.agent !== "string" || response.task.usage !== undefined && !isTaskUsage(response.task.usage)) {
2337
+ throw new Error("Invalid supervisor task response");
2338
+ }
2240
2339
  }
2241
2340
  return response;
2242
2341
  }
2243
2342
  function registerSubagentWorker(pi, config) {
2244
2343
  pi.on("session_start", () => {
2245
2344
  delete process.env[WORKER_ENV];
2345
+ enforcePermittedTools(pi, config.rules);
2346
+ });
2347
+ pi.on("before_agent_start", () => {
2348
+ enforcePermittedTools(pi, config.rules);
2246
2349
  });
2247
2350
  pi.on("tool_call", async (event, ctx) => {
2248
2351
  if (event.toolName === "task")
@@ -2434,7 +2537,7 @@ ${this.primaryAgent.prompt}` };
2434
2537
  let maxSubagentsSettings = loadMaxSubagentsSettings(ctx.cwd, projectTrusted);
2435
2538
  const sandboxCallbacks = this.integration.sandboxCallbacks;
2436
2539
  let sandboxSettings = sandboxCallbacks?.load(ctx.cwd, projectTrusted) ?? { global: true };
2437
- let tab = requested ? "tasks" : "agents";
2540
+ let tab = requested ? "logs" : "agents";
2438
2541
  let selectedAgent = Math.max(0, agents.findIndex((agent) => agent.name === this.primaryAgent?.name));
2439
2542
  let selectedTask = requested ? tasks.findIndex((task) => task.id === requested || task.id.startsWith(requested)) : 0;
2440
2543
  if (selectedTask < 0) {
@@ -2446,8 +2549,7 @@ ${this.primaryAgent.prompt}` };
2446
2549
  let saving = false;
2447
2550
  let editing = false;
2448
2551
  let confirmingDeleteAgent;
2449
- let detail = requested.length > 0;
2450
- let follow = detail;
2552
+ let follow = requested.length > 0;
2451
2553
  let scroll = 0;
2452
2554
  const reloadAgents = () => {
2453
2555
  const selectedName = agents[selectedAgent]?.name;
@@ -2500,7 +2602,7 @@ ${this.primaryAgent.prompt}` };
2500
2602
  return `${clipped}${" ".repeat(Math.max(0, cellWidth - visibleWidth2(clipped)))}`;
2501
2603
  };
2502
2604
  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"}`)}`;
2605
+ 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
2606
  if (tab === "agents") {
2505
2607
  const start = Math.max(0, Math.min(selectedAgent - 3, agents.length - 7));
2506
2608
  const shown2 = agents.slice(start, start + 7);
@@ -2580,13 +2682,19 @@ ${this.primaryAgent.prompt}` };
2580
2682
  return box(lines2);
2581
2683
  }
2582
2684
  if (tab === "settings") {
2583
- const maxSubagents = scope === "project" && maxSubagentsSettings.project === undefined ? "-" : String(selectedMaxSubagents());
2584
- const sandbox = scope === "project" && sandboxSettings.project === undefined ? "-" : selectedSandboxEnabled() ? "on" : "off";
2685
+ const maxSubagentsInherited = scope === "project" && maxSubagentsSettings.project === undefined;
2686
+ const sandboxInherited = scope === "project" && sandboxSettings.project === undefined;
2585
2687
  const rows = [
2586
- { label: "Maximum subagents", value: maxSubagents, unavailable: false },
2688
+ {
2689
+ label: "Maximum subagents",
2690
+ value: String(selectedMaxSubagents()),
2691
+ inherited: maxSubagentsInherited,
2692
+ unavailable: false
2693
+ },
2587
2694
  {
2588
2695
  label: "Sandbox enabled",
2589
- value: sandbox,
2696
+ value: selectedSandboxEnabled() ? "on" : "off",
2697
+ inherited: sandboxInherited,
2590
2698
  unavailable: sandboxCallbacks === undefined
2591
2699
  }
2592
2700
  ];
@@ -2595,12 +2703,12 @@ ${this.primaryAgent.prompt}` };
2595
2703
  const selected = index === selectedSetting;
2596
2704
  const cursor = selected ? theme.fg("accent", "›") : " ";
2597
2705
  const text = `[ ${row.value} ]`;
2598
- const value = selected ? theme.fg("accent", text) : theme.fg(row.unavailable ? "muted" : "text", text);
2706
+ const value = row.inherited ? theme.fg("dim", text) : selected ? theme.fg("accent", text) : theme.fg(row.unavailable ? "muted" : "text", text);
2599
2707
  const label = theme.fg(row.unavailable ? "muted" : "text", row.label);
2600
2708
  const dirty = dirtySettings.has(index) ? theme.fg("warning", " *") : "";
2601
2709
  lines2.push(`${cursor} ${value} ${label}${dirty}`);
2602
2710
  }
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."));
2711
+ 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
2712
  if (saving)
2605
2713
  lines2.push("", theme.fg("dim", "Saving…"));
2606
2714
  return box(lines2);
@@ -2616,51 +2724,21 @@ ${this.primaryAgent.prompt}` };
2616
2724
  ];
2617
2725
  return box(lines2);
2618
2726
  }
2619
- if (!detail) {
2727
+ if (tab === "tasks") {
2620
2728
  const listLines = [];
2621
2729
  if (tasks.length === 0) {
2622
2730
  listLines.push("No task sessions in this session.");
2623
2731
  } else {
2624
2732
  const start = Math.max(0, Math.min(selectedTask - 5, tasks.length - 11));
2625
2733
  const shown2 = tasks.slice(start, start + 11);
2626
- for (const [offset, task3] of shown2.entries()) {
2734
+ for (const [offset, task2] of shown2.entries()) {
2627
2735
  const index = start + offset;
2628
2736
  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))}`);
2737
+ const indent = " ".repeat(Math.max(0, task2.depth - 1));
2738
+ 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
2739
  }
2632
2740
  }
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]);
2741
+ return box([tabs, "", ...listLines]);
2664
2742
  }
2665
2743
  const task = tasks[selectedTask];
2666
2744
  if (!task)
@@ -2675,11 +2753,13 @@ ${this.primaryAgent.prompt}` };
2675
2753
  task.toolCalls === undefined ? undefined : `${task.toolCalls} tool call${task.toolCalls === 1 ? "" : "s"}`,
2676
2754
  duration
2677
2755
  ].filter(Boolean);
2756
+ const usage = formatTaskUsage(task.usage);
2678
2757
  const lines = [
2679
2758
  tabs,
2680
2759
  "",
2681
2760
  `${theme.fg("accent", theme.bold(`@${task.agent}`))} ${theme.fg("text", task.description)}`,
2682
2761
  `${taskState(theme, task)} ${theme.fg("dim", metrics.join(" · "))} ${theme.fg("dim", "·")} ${theme.fg(follow ? "accent" : "muted", `Follow: ${follow ? "on" : "off"}`)}`,
2762
+ ...usage ? [theme.fg("dim", usage)] : [],
2683
2763
  "",
2684
2764
  ...shown.map((line) => theme.fg("toolOutput", line))
2685
2765
  ];
@@ -2742,10 +2822,12 @@ ${this.primaryAgent.prompt}` };
2742
2822
  ctx.ui.notify("Save changes with Enter or discard them with R before leaving", "warning");
2743
2823
  return;
2744
2824
  }
2745
- const tabs = ["agents", "tasks", "settings", "help"];
2825
+ const tabs = ["agents", "tasks", "logs", "settings", "help"];
2746
2826
  tab = tabs[(tabs.indexOf(tab) + 1) % tabs.length] ?? "agents";
2747
- detail = false;
2748
- follow = false;
2827
+ if (tab === "logs" && tasks.length > 0)
2828
+ follow = true;
2829
+ else
2830
+ follow = false;
2749
2831
  editing = false;
2750
2832
  scroll = 0;
2751
2833
  tui.requestRender();
@@ -2916,7 +2998,7 @@ ${this.primaryAgent.prompt}` };
2916
2998
  done();
2917
2999
  return;
2918
3000
  }
2919
- if (!detail) {
3001
+ if (tab === "tasks") {
2920
3002
  if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c"))
2921
3003
  done();
2922
3004
  else if (matchesKey(data, "up"))
@@ -2924,7 +3006,7 @@ ${this.primaryAgent.prompt}` };
2924
3006
  else if (matchesKey(data, "down") && tasks.length > 0) {
2925
3007
  selectedTask = Math.min(tasks.length - 1, selectedTask + 1);
2926
3008
  } else if (matchesKey(data, "return") && tasks.length > 0) {
2927
- detail = true;
3009
+ tab = "logs";
2928
3010
  follow = true;
2929
3011
  scroll = 0;
2930
3012
  } else
@@ -2932,11 +3014,16 @@ ${this.primaryAgent.prompt}` };
2932
3014
  tui.requestRender();
2933
3015
  return;
2934
3016
  }
3017
+ if (tab !== "logs")
3018
+ return;
2935
3019
  const task = tasks[selectedTask];
2936
- if (!task)
3020
+ if (!task) {
3021
+ if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c"))
3022
+ done();
2937
3023
  return;
3024
+ }
2938
3025
  if (matchesKey(data, "escape")) {
2939
- detail = false;
3026
+ tab = "tasks";
2940
3027
  follow = false;
2941
3028
  scroll = 0;
2942
3029
  } else if (matchesKey(data, "ctrl+c")) {
@@ -2961,7 +3048,7 @@ ${this.primaryAgent.prompt}` };
2961
3048
  selectedTask = parentIndex;
2962
3049
  follow = true;
2963
3050
  } else {
2964
- detail = false;
3051
+ tab = "tasks";
2965
3052
  follow = false;
2966
3053
  }
2967
3054
  scroll = 0;
@@ -3079,6 +3166,9 @@ ${descriptions}` : ""),
3079
3166
  const duration = taskDuration(details);
3080
3167
  if (duration)
3081
3168
  metrics.push(duration);
3169
+ const usage = formatTaskUsage(details.usage);
3170
+ if (usage)
3171
+ metrics.push(usage);
3082
3172
  if (metrics.length > 0)
3083
3173
  text += `
3084
3174
  ${theme.fg("dim", metrics.join(" · "))}`;
@@ -3327,6 +3417,15 @@ ${catalog.diagnostics.join(`
3327
3417
  update(streamedText);
3328
3418
  }
3329
3419
  }
3420
+ if (event.type === "message_end") {
3421
+ const usage = taskUsageFromMessage(event.message);
3422
+ if (usage) {
3423
+ task.usage = addTaskUsage(task.usage, usage);
3424
+ this.persist(task);
3425
+ this.updateTaskWidget(ctx);
3426
+ update(streamedText || "Subagent running");
3427
+ }
3428
+ }
3330
3429
  if (event.type === "tool_execution_start" && typeof event.toolName === "string") {
3331
3430
  task.currentTool = event.toolName;
3332
3431
  task.toolCalls = (task.toolCalls ?? 0) + 1;
@@ -3434,7 +3533,8 @@ ${catalog.diagnostics.join(`
3434
3533
  ctx.ui.notify(`Agent ${agent.name} options are not supported by Pi RPC mode: ${providerOptionNames.join(", ")}`, "warning");
3435
3534
  }
3436
3535
  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");
3536
+ const activeTools = taskEnabled ? [...new Set([...this.pi.getActiveTools(), "task"])] : this.pi.getActiveTools().filter((tool) => tool !== "task");
3537
+ const tools = permittedToolNames(activeTools, rules);
3438
3538
  const workerExtensions = this.integration.getWorkerExtensions?.() ?? [];
3439
3539
  const args = [
3440
3540
  ...invocation.args,
@@ -3742,11 +3842,15 @@ ${renderTaskResult(task.id, "error", message)}`);
3742
3842
  render: (width) => {
3743
3843
  const header = theme.fg("accent", theme.bold("Subagents")) + theme.fg("dim", ` ${summary}`);
3744
3844
  const tree = taskTreeLines(visible, (task) => {
3845
+ const state = taskState(theme, task);
3745
3846
  const agent = theme.fg("accent", `@${task.agent}`);
3746
- const description = theme.fg("text", task.description);
3747
- const progress = taskProgress(byId.get(task.id));
3847
+ const record = byId.get(task.id);
3848
+ const progress = taskProgress(record);
3748
3849
  const suffix = progress.length > 0 ? theme.fg("dim", ` · ${progress.join(" · ")}`) : "";
3749
- return `${taskState(theme, task)} ${agent} ${description}${suffix}`;
3850
+ const indentWidth = 3 + Math.max(0, record.depth) * 3;
3851
+ const descriptionWidth = Math.max(1, width - indentWidth - visibleWidth2(`${state} ${agent} ${suffix}`));
3852
+ const description = theme.fg("text", truncateToWidth2(task.description, descriptionWidth));
3853
+ return `${state} ${agent} ${description}${suffix}`;
3750
3854
  });
3751
3855
  const shown = tree.slice(0, 8);
3752
3856
  if (tree.length > shown.length) {
@@ -3767,6 +3871,7 @@ ${renderTaskResult(task.id, "error", message)}`);
3767
3871
  continue;
3768
3872
  this.tasks.set(task.id, {
3769
3873
  ...task,
3874
+ usage: isTaskUsage(task.usage) ? { ...task.usage } : undefined,
3770
3875
  state: task.state === "running" || task.state === "queued" ? "interrupted" : task.state
3771
3876
  });
3772
3877
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-landstrip",
3
- "version": "0.18.8",
3
+ "version": "0.18.10",
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.10",
43
43
  "jsonc-parser": "^3.3.1",
44
44
  "minimatch": "^10.2.5",
45
45
  "yaml": "^2.9.0"