pi-landstrip 0.18.16 → 0.18.18

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/README.md +31 -0
  2. package/dist/index.ts +43 -33
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -143,6 +143,37 @@ Pi Markdown agents are loaded from `~/.pi/agent/agents/` and `.pi/agents/`.
143
143
  `landstrip.agent` settings override Markdown agents with the same name.
144
144
  Project definitions override global definitions.
145
145
 
146
+ For example, save this advisor subagent as `~/.pi/agent/agents/advisor.md`:
147
+
148
+ ```markdown
149
+ ---
150
+ description: Escalate to a stronger reviewer for a plan, correction, or stop signal
151
+ mode: subagent
152
+ hidden: false
153
+ variant: high
154
+ permission:
155
+ '*': deny
156
+ read: allow
157
+ grep: allow
158
+ glob: allow
159
+ list: allow
160
+ ---
161
+
162
+ You are an advisor model in an advisor-strategy pattern. An executor model is running a task end-to-end — calling tools, reading results, iterating toward a solution. When the executor hits a decision it cannot reasonably solve alone, it consults you for guidance via the `task` tool with `subagent_type: "advisor"`.
163
+
164
+ The executor should put the full situation in the task prompt: the goal, what it has tried, tool results that matter, current hypothesis, and the decision it needs. You may use read-only tools to verify claims against the repository when file paths are given; do not modify anything.
165
+
166
+ Return ONE of:
167
+
168
+ - a **plan** (concrete next steps the executor should take),
169
+ - a **correction** (the executor is going down a wrong path — redirect it),
170
+ - a **stop signal** (the executor should halt and escalate to the user).
171
+
172
+ Be concise, directive, and grounded in the shared context. Name files, functions, and line numbers where possible. No preamble, no apologies, no meta-commentary about being an advisor — just the guidance the executor needs.
173
+
174
+ Give advice serious weight for the executor: prefer primary-source evidence and root causes over temporary fixes. If requirements are ambiguous or contradictory, stop and say what the user must decide.
175
+ ```
176
+
146
177
  See the main [Landstrip documentation](https://github.com/landstrip/landstrip#readme)
147
178
  for `sandbox.json` fields and platform policy semantics.
148
179
 
package/dist/index.ts CHANGED
@@ -1604,12 +1604,12 @@ var INSPECTOR_BODY_LINES = 16;
1604
1604
  var PI_THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
1605
1605
  var AGENTS_HELP_ROWS = [
1606
1606
  ["Tab", "Next tab"],
1607
- ["Shift+Tab / S", "Switch scope in Agents or Settings"],
1607
+ ["Shift+Tab / S", "Switch scope in Primary, Subagent, or Settings"],
1608
1608
  ["↑ / ↓", "Select item or scroll when follow is off"],
1609
1609
  ["F", "Toggle task log follow"],
1610
1610
  ["Page Up / Down", "Scroll task output by page"],
1611
1611
  ["Home / End", "Jump to task output boundary"],
1612
- ["Enter", "Open logs, set primary, or save"],
1612
+ ["Enter", "Open log, set primary, or save"],
1613
1613
  ["Esc", "Back or close"],
1614
1614
  ["Ctrl+C", "Close"],
1615
1615
  ["Space", "Enable agent or toggle sandbox"],
@@ -2397,8 +2397,9 @@ ${this.primaryAgent.prompt}` };
2397
2397
  let maxSubagentsSettings = loadMaxSubagentsSettings(ctx.cwd, projectTrusted);
2398
2398
  const sandboxCallbacks = this.integration.sandboxCallbacks;
2399
2399
  let sandboxSettings = sandboxCallbacks?.load(ctx.cwd, projectTrusted) ?? { global: true };
2400
- let tab = requested ? "logs" : "agents";
2401
- let selectedAgent = Math.max(0, agents.findIndex((agent) => agent.name === this.primaryAgent?.name));
2400
+ let tab = requested ? "log" : "primary";
2401
+ const agentsForTab = () => agents.filter((agent) => agentSupportsMode(agent, tab === "subagent" ? "subagent" : "primary"));
2402
+ let selectedAgent = Math.max(0, agentsForTab().findIndex((agent) => agent.name === this.primaryAgent?.name));
2402
2403
  let selectedTask = requested ? tasks.findIndex((task) => task.id === requested || task.id.startsWith(requested)) : 0;
2403
2404
  if (selectedTask < 0) {
2404
2405
  ctx.ui.notify(`Unknown task session: ${requested}`, "error");
@@ -2412,13 +2413,14 @@ ${this.primaryAgent.prompt}` };
2412
2413
  let follow = requested.length > 0;
2413
2414
  let scroll = 0;
2414
2415
  const reloadAgents = () => {
2415
- const selectedName = agents[selectedAgent]?.name;
2416
+ const selectedName = agentsForTab()[selectedAgent]?.name;
2416
2417
  catalog = this.getAgentCatalog(ctx);
2417
2418
  globalCatalog = this.loadCatalog(ctx.cwd, getAgentDir4(), false);
2418
2419
  agents = [...catalog.agents.values()].filter((agent) => !agent.hidden).sort((left, right) => left.name.localeCompare(right.name));
2419
2420
  disabledOverrides = loadAgentDisabledOverrides(ctx.cwd, projectTrusted);
2420
- const nextSelected = selectedName ? agents.findIndex((agent) => agent.name === selectedName) : selectedAgent;
2421
- selectedAgent = Math.max(0, Math.min(nextSelected < 0 ? 0 : nextSelected, agents.length - 1));
2421
+ const listedAgents = agentsForTab();
2422
+ const nextSelected = selectedName ? listedAgents.findIndex((agent) => agent.name === selectedName) : selectedAgent;
2423
+ selectedAgent = Math.max(0, Math.min(nextSelected < 0 ? 0 : nextSelected, listedAgents.length - 1));
2422
2424
  };
2423
2425
  const reloadSettings = () => {
2424
2426
  maxSubagentsSettings = loadMaxSubagentsSettings(ctx.cwd, projectTrusted);
@@ -2462,27 +2464,25 @@ ${this.primaryAgent.prompt}` };
2462
2464
  return `${clipped}${" ".repeat(Math.max(0, cellWidth - visibleWidth2(clipped)))}`;
2463
2465
  };
2464
2466
  refreshTasks();
2465
- 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"}`)}`;
2466
- if (tab === "agents") {
2467
- const start = Math.max(0, Math.min(selectedAgent - 3, agents.length - 7));
2468
- const shown2 = agents.slice(start, start + 7);
2469
- const nameWidth = Math.min(20, Math.max(5, ...agents.map((agent2) => visibleWidth2(`@${agent2.name}`))));
2470
- const primaryWidth = 7;
2471
- const modeWidth = 8;
2467
+ const tabs = `${dialogTabs(theme, ["Primary", "Subagent", "Tasks", "Log", "Settings", "Help"], `${tab[0]?.toUpperCase()}${tab.slice(1)}`)}${tab === "tasks" || tab === "log" ? "" : theme.fg("dim", ` · Scope: ${scope === "project" ? "Project" : "Global"}`)}`;
2468
+ if (tab === "primary" || tab === "subagent") {
2469
+ const listedAgents = agentsForTab();
2470
+ const start = Math.max(0, Math.min(selectedAgent - 3, listedAgents.length - 7));
2471
+ const shown2 = listedAgents.slice(start, start + 7);
2472
+ const nameWidth = Math.min(20, Math.max(5, ...listedAgents.map((agent2) => visibleWidth2(`@${agent2.name}`))));
2472
2473
  const sourceWidth = 8;
2473
2474
  const stateWidth = 13;
2474
- const fixedWidth = 2 + primaryWidth + 1 + nameWidth + 1 + modeWidth + 1 + sourceWidth + 1 + stateWidth;
2475
+ const fixedWidth = 2 + nameWidth + 1 + sourceWidth + 1 + stateWidth;
2475
2476
  const modelWidth = Math.max(10, Math.min(30, contentWidth - fixedWidth - 1));
2476
2477
  const lines2 = [
2477
2478
  tabs,
2478
2479
  "",
2479
- theme.fg("dim", ` ${pad("Primary", primaryWidth)} ${pad("Agent", nameWidth)} ${pad("Mode", modeWidth)} ${pad("Source", sourceWidth)} ${pad("Model", modelWidth)} ${pad("State", stateWidth)}`)
2480
+ theme.fg("dim", ` ${pad("Agent", nameWidth)} ${pad("Source", sourceWidth)} ${pad("Model", modelWidth)} ${pad("State", stateWidth)}`)
2480
2481
  ];
2481
2482
  for (const [offset, agent2] of shown2.entries()) {
2482
2483
  const index = start + offset;
2483
2484
  const selected = index === selectedAgent;
2484
2485
  const cursor = selected ? theme.fg("accent", "›") : " ";
2485
- const primary = agent2.name === this.primaryAgent?.name ? "yes" : "";
2486
2486
  const current = scopedAgent(agent2);
2487
2487
  const unavailable = current === undefined;
2488
2488
  const scopedDisabled = current?.disabled ?? false;
@@ -2493,11 +2493,13 @@ ${this.primaryAgent.prompt}` };
2493
2493
  const color = deleting ? "error" : disabled ? "muted" : "text";
2494
2494
  const plainName = pad(`@${agent2.name}`, nameWidth);
2495
2495
  const name = deleting ? theme.fg("error", plainName) : disabled ? theme.fg("muted", plainName) : colorizeAgentText(agent2.color, plainName, (agentColor, text) => theme.fg(agentColor, text));
2496
- lines2.push(`${cursor} ${theme.fg(primary ? "success" : "dim", pad(primary, primaryWidth))} ${name} ${theme.fg(color, pad(agent2.mode, modeWidth))} ${theme.fg(color, pad(agent2.source, sourceWidth))} ${theme.fg(color, pad(agent2.model ?? "current model", modelWidth))} ${theme.fg(deleting ? "error" : unavailable ? "muted" : scopedDisabled ? "warning" : color, pad(state, stateWidth))}`);
2496
+ const line = `${cursor} ${name} ${theme.fg(color, pad(agent2.source, sourceWidth))} ${theme.fg(color, pad(agent2.model ?? "current model", modelWidth))} ${theme.fg(deleting ? "error" : unavailable ? "muted" : scopedDisabled ? "warning" : color, pad(state, stateWidth))}`;
2497
+ lines2.push(agent2.name === this.primaryAgent?.name ? theme.bold(line) : line);
2497
2498
  }
2498
- if (agents.length === 0)
2499
- lines2.push(theme.fg("muted", " No agents configured."));
2500
- const agent = agents[selectedAgent];
2499
+ if (listedAgents.length === 0) {
2500
+ lines2.push(theme.fg("muted", ` No ${tab} agents configured.`));
2501
+ }
2502
+ const agent = listedAgents[selectedAgent];
2501
2503
  if (agent) {
2502
2504
  lines2.push("", theme.fg(agent.disabled ? "muted" : "text", agent.description ?? "No description"));
2503
2505
  const details = [
@@ -2661,7 +2663,7 @@ ${this.primaryAgent.prompt}` };
2661
2663
  }
2662
2664
  return;
2663
2665
  }
2664
- if ((matchesKey(data, "shift+tab") || data.toLowerCase() === "s") && (tab === "agents" || tab === "settings")) {
2666
+ if ((matchesKey(data, "shift+tab") || data.toLowerCase() === "s") && (tab === "primary" || tab === "subagent" || tab === "settings")) {
2665
2667
  if (saving)
2666
2668
  return;
2667
2669
  if (dirtySettings.size > 0) {
@@ -2684,9 +2686,16 @@ ${this.primaryAgent.prompt}` };
2684
2686
  ctx.ui.notify("Save changes with Enter or discard them with R before leaving", "warning");
2685
2687
  return;
2686
2688
  }
2687
- const tabs = ["agents", "tasks", "logs", "settings", "help"];
2688
- tab = tabs[(tabs.indexOf(tab) + 1) % tabs.length] ?? "agents";
2689
- if (tab === "logs" && tasks.length > 0)
2689
+ const selectedName = tab === "primary" || tab === "subagent" ? agentsForTab()[selectedAgent]?.name : undefined;
2690
+ const tabs = ["primary", "subagent", "tasks", "log", "settings", "help"];
2691
+ tab = tabs[(tabs.indexOf(tab) + 1) % tabs.length] ?? "primary";
2692
+ if (tab === "primary" || tab === "subagent") {
2693
+ const listedAgents = agentsForTab();
2694
+ const preferredName = selectedName ?? (tab === "primary" ? this.primaryAgent?.name : undefined);
2695
+ const nextSelected = preferredName ? listedAgents.findIndex((agent) => agent.name === preferredName) : 0;
2696
+ selectedAgent = Math.max(0, Math.min(nextSelected < 0 ? 0 : nextSelected, listedAgents.length - 1));
2697
+ }
2698
+ if (tab === "log" && tasks.length > 0)
2690
2699
  follow = true;
2691
2700
  else
2692
2701
  follow = false;
@@ -2695,16 +2704,17 @@ ${this.primaryAgent.prompt}` };
2695
2704
  tui.requestRender();
2696
2705
  return;
2697
2706
  }
2698
- if (tab === "agents") {
2699
- const agent = agents[selectedAgent];
2707
+ if (tab === "primary" || tab === "subagent") {
2708
+ const listedAgents = agentsForTab();
2709
+ const agent = listedAgents[selectedAgent];
2700
2710
  if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
2701
2711
  done();
2702
2712
  return;
2703
2713
  }
2704
2714
  if (matchesKey(data, "up"))
2705
2715
  selectedAgent = Math.max(0, selectedAgent - 1);
2706
- else if (matchesKey(data, "down") && agents.length > 0) {
2707
- selectedAgent = Math.min(agents.length - 1, selectedAgent + 1);
2716
+ else if (matchesKey(data, "down") && listedAgents.length > 0) {
2717
+ selectedAgent = Math.min(listedAgents.length - 1, selectedAgent + 1);
2708
2718
  } else if (data.toLowerCase() === "x" && agent && !saving) {
2709
2719
  if (!projectTrusted || !canDeleteProjectAgent(ctx.cwd, agent)) {
2710
2720
  ctx.ui.notify("Only project agent files can be deleted here", "warning");
@@ -2760,10 +2770,10 @@ ${this.primaryAgent.prompt}` };
2760
2770
  tui.requestRender();
2761
2771
  });
2762
2772
  return;
2763
- } else if (matchesKey(data, "return") && agent && !agent.disabled && agentSupportsMode(agent, "primary")) {
2773
+ } else if (tab === "primary" && matchesKey(data, "return") && agent && !agent.disabled && agentSupportsMode(agent, "primary")) {
2764
2774
  this.selectPrimaryAgent(agent.name, ctx).then(() => tui.requestRender()).catch((error) => ctx.ui.notify(`Could not select primary agent: ${formatError(error)}`, "error"));
2765
2775
  return;
2766
- } else if (matchesKey(data, "return") && agent) {
2776
+ } else if (tab === "primary" && matchesKey(data, "return") && agent) {
2767
2777
  ctx.ui.notify(agent.disabled ? "Enable this agent before setting it as primary" : "This agent cannot be used as the primary agent", "warning");
2768
2778
  return;
2769
2779
  } else
@@ -2868,7 +2878,7 @@ ${this.primaryAgent.prompt}` };
2868
2878
  else if (matchesKey(data, "down") && tasks.length > 0) {
2869
2879
  selectedTask = Math.min(tasks.length - 1, selectedTask + 1);
2870
2880
  } else if (matchesKey(data, "return") && tasks.length > 0) {
2871
- tab = "logs";
2881
+ tab = "log";
2872
2882
  follow = true;
2873
2883
  scroll = 0;
2874
2884
  } else
@@ -2876,7 +2886,7 @@ ${this.primaryAgent.prompt}` };
2876
2886
  tui.requestRender();
2877
2887
  return;
2878
2888
  }
2879
- if (tab !== "logs")
2889
+ if (tab !== "log")
2880
2890
  return;
2881
2891
  const task = tasks[selectedTask];
2882
2892
  if (!task) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-landstrip",
3
- "version": "0.18.16",
3
+ "version": "0.18.18",
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.16",
42
+ "@landstrip/landstrip": "^0.18.18",
43
43
  "jsonc-parser": "^3.3.1",
44
44
  "minimatch": "^10.2.5",
45
45
  "yaml": "^2.9.0"