pi-landstrip 0.18.23 → 0.18.25

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 +168 -90
  2. package/package.json +2 -2
package/dist/index.ts CHANGED
@@ -80,6 +80,7 @@ import {
80
80
  mkdirSync as mkdirSync3,
81
81
  mkdtempSync,
82
82
  readFileSync as readFileSync5,
83
+ readdirSync as readdirSync2,
83
84
  readlinkSync,
84
85
  realpathSync,
85
86
  rmSync,
@@ -1605,7 +1606,6 @@ var INSPECTOR_BODY_LINES = 16;
1605
1606
  var PI_THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
1606
1607
  var AGENTS_HELP_ROWS = [
1607
1608
  ["Tab", "Next tab"],
1608
- ["Shift+Tab / S", "Switch scope in Primary, Subagent, or Settings"],
1609
1609
  ["↑ / ↓", "Select item or scroll when follow is off"],
1610
1610
  ["F", "Toggle task log follow"],
1611
1611
  ["Page Up / Down", "Scroll task output by page"],
@@ -1618,7 +1618,7 @@ var AGENTS_HELP_ROWS = [
1618
1618
  ["E", "Edit project agent"],
1619
1619
  ["X", "Delete project agent"],
1620
1620
  ["Y / N", "Confirm or cancel deletion"],
1621
- ["Empty + Enter", "Use global setting in Project scope"],
1621
+ ["Empty + Enter", "Inherit global setting"],
1622
1622
  ["Backspace", "Open parent task or edit setting"]
1623
1623
  ];
1624
1624
  var MIN_SUPPORTED_PI_VERSION = [0, 82, 0];
@@ -1809,6 +1809,96 @@ function formatTaskUsageSummary(usage) {
1809
1809
  parts.push(`$${usage.cost.toFixed(4)}`);
1810
1810
  return parts.length > 0 ? parts.join(" ") : undefined;
1811
1811
  }
1812
+ function findFilesRecursively(dir) {
1813
+ const results = [];
1814
+ try {
1815
+ const entries = readdirSync2(dir, { withFileTypes: true });
1816
+ for (const entry of entries) {
1817
+ const fullPath = join6(dir, entry.name);
1818
+ if (entry.isDirectory()) {
1819
+ results.push(...findFilesRecursively(fullPath));
1820
+ } else if (entry.isFile()) {
1821
+ results.push(fullPath);
1822
+ }
1823
+ }
1824
+ } catch {}
1825
+ return results;
1826
+ }
1827
+ function extractCwdFromSessionFile(filePath) {
1828
+ try {
1829
+ const content = readFileSync5(filePath, "utf8");
1830
+ const firstNewline = content.indexOf(`
1831
+ `);
1832
+ const firstLine = firstNewline >= 0 ? content.slice(0, firstNewline) : content;
1833
+ if (!firstLine.trim())
1834
+ return;
1835
+ const header = JSON.parse(firstLine);
1836
+ if (typeof header.cwd === "string") {
1837
+ return header.cwd;
1838
+ }
1839
+ } catch {}
1840
+ return;
1841
+ }
1842
+ function extractCwdFromTaskJsonFile(filePath) {
1843
+ try {
1844
+ const content = readFileSync5(filePath, "utf8");
1845
+ const data = JSON.parse(content);
1846
+ return {
1847
+ cwd: typeof data.cwd === "string" ? data.cwd : undefined,
1848
+ parentSessionFile: typeof data.parentSessionFile === "string" ? data.parentSessionFile : undefined
1849
+ };
1850
+ } catch {
1851
+ return {};
1852
+ }
1853
+ }
1854
+ function defaultSessionDirForCwd(cwd, agentDir = getAgentDir4()) {
1855
+ const resolvedCwd = resolve2(cwd);
1856
+ const safePath = `--${resolvedCwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
1857
+ return join6(agentDir, "sessions", safePath);
1858
+ }
1859
+ function isSubagentSessionDirForCwd(parentSessionDir, targetCwd) {
1860
+ const files = findFilesRecursively(parentSessionDir);
1861
+ if (files.length === 0) {
1862
+ return true;
1863
+ }
1864
+ const resolvedTarget = resolve2(targetCwd);
1865
+ const targetSessionDir = resolve2(defaultSessionDirForCwd(targetCwd));
1866
+ let hasTargetCwd = false;
1867
+ let hasOtherCwd = false;
1868
+ for (const file of files) {
1869
+ if (file.endsWith(".jsonl")) {
1870
+ const cwd = extractCwdFromSessionFile(file);
1871
+ if (cwd) {
1872
+ if (resolve2(cwd) === resolvedTarget) {
1873
+ hasTargetCwd = true;
1874
+ } else {
1875
+ hasOtherCwd = true;
1876
+ }
1877
+ }
1878
+ } else if (file.endsWith(".json")) {
1879
+ const { cwd, parentSessionFile } = extractCwdFromTaskJsonFile(file);
1880
+ if (cwd) {
1881
+ if (resolve2(cwd) === resolvedTarget) {
1882
+ hasTargetCwd = true;
1883
+ } else {
1884
+ hasOtherCwd = true;
1885
+ }
1886
+ } else if (parentSessionFile) {
1887
+ const resolvedParent = resolve2(parentSessionFile);
1888
+ if (resolvedParent.startsWith(targetSessionDir)) {
1889
+ hasTargetCwd = true;
1890
+ } else {
1891
+ hasOtherCwd = true;
1892
+ }
1893
+ }
1894
+ }
1895
+ }
1896
+ if (hasOtherCwd)
1897
+ return false;
1898
+ if (hasTargetCwd)
1899
+ return true;
1900
+ return true;
1901
+ }
1812
1902
  function activeTaskRecords(tasks) {
1813
1903
  const byId = new Map(tasks.map((task) => [task.id, task]));
1814
1904
  const visibleIds = new Set;
@@ -2312,6 +2402,7 @@ class SubagentRuntime {
2312
2402
  this.broker.reset();
2313
2403
  this.restore(ctx);
2314
2404
  await this.restorePrimaryAgent(ctx);
2405
+ await this.cleanupOrphanSubagentSessions(ctx);
2315
2406
  const activeTools = this.pi.getActiveTools();
2316
2407
  const withoutTask = activeTools.filter((tool) => tool !== "task");
2317
2408
  const nextTools = this.maxSubagents > 0 ? [...withoutTask, "task"] : withoutTask;
@@ -2391,16 +2482,12 @@ ${this.primaryAgent.prompt}` };
2391
2482
  const projectTrusted = isProjectTrusted(ctx);
2392
2483
  let tasks = [...this.tasks.values()];
2393
2484
  const requested = args.trim();
2394
- let scope = projectTrusted ? "project" : "global";
2395
2485
  let catalog = this.getAgentCatalog(ctx);
2396
2486
  for (const diagnostic of catalog.diagnostics)
2397
2487
  ctx.ui.notify(diagnostic, "warning");
2398
- let globalCatalog = this.loadCatalog(ctx.cwd, getAgentDir4(), false);
2399
2488
  let agents = [...catalog.agents.values()].filter((agent) => !agent.hidden).sort((left, right) => left.name.localeCompare(right.name));
2400
2489
  let disabledOverrides = loadAgentDisabledOverrides(ctx.cwd, projectTrusted);
2401
2490
  let maxSubagentsSettings = loadMaxSubagentsSettings(ctx.cwd, projectTrusted);
2402
- const sandboxCallbacks = this.integration.sandboxCallbacks;
2403
- let sandboxSettings = sandboxCallbacks?.load(ctx.cwd, projectTrusted) ?? { global: true };
2404
2491
  let tab = requested ? "log" : "primary";
2405
2492
  const agentsForTab = () => agents.filter((agent) => agentSupportsMode(agent, tab === "subagent" ? "subagent" : "primary"));
2406
2493
  let selectedAgent = Math.max(0, agentsForTab().findIndex((agent) => agent.name === this.primaryAgent?.name));
@@ -2420,7 +2507,6 @@ ${this.primaryAgent.prompt}` };
2420
2507
  const reloadAgents = () => {
2421
2508
  const selectedName = agentsForTab()[selectedAgent]?.name;
2422
2509
  catalog = this.getAgentCatalog(ctx);
2423
- globalCatalog = this.loadCatalog(ctx.cwd, getAgentDir4(), false);
2424
2510
  agents = [...catalog.agents.values()].filter((agent) => !agent.hidden).sort((left, right) => left.name.localeCompare(right.name));
2425
2511
  disabledOverrides = loadAgentDisabledOverrides(ctx.cwd, projectTrusted);
2426
2512
  const listedAgents = agentsForTab();
@@ -2429,7 +2515,6 @@ ${this.primaryAgent.prompt}` };
2429
2515
  };
2430
2516
  const reloadSettings = () => {
2431
2517
  maxSubagentsSettings = loadMaxSubagentsSettings(ctx.cwd, projectTrusted);
2432
- sandboxSettings = sandboxCallbacks?.load(ctx.cwd, projectTrusted) ?? { global: true };
2433
2518
  };
2434
2519
  const refreshTasks = () => {
2435
2520
  const selectedId = tasks[selectedTask]?.id;
@@ -2457,11 +2542,7 @@ ${this.primaryAgent.prompt}` };
2457
2542
  this.pi.registerTool(this.createTaskTool(undefined, this.primaryRules, ctx));
2458
2543
  }
2459
2544
  };
2460
- const scopedAgent = (agent) => scope === "global" ? globalCatalog.agents.get(agent.name) : agent;
2461
- const selectedMaxSubagents = () => scope === "global" ? maxSubagentsSettings.global : maxSubagentsSettings.project ?? maxSubagentsSettings.global;
2462
- const settingField = (setting) => setting === 0 ? "maxSubagents" : "sandboxEnabled";
2463
- const settingLabel = (settingScope, field) => `${settingScope === "global" ? "Global" : "Project"} ${field}`;
2464
- const selectedSandboxEnabled = () => scope === "global" ? sandboxSettings.global : sandboxSettings.project ?? sandboxSettings.global;
2545
+ const selectedMaxSubagents = () => maxSubagentsSettings.project ?? maxSubagentsSettings.global;
2465
2546
  await ctx.ui.custom((tui, theme, _keybindings, done) => ({
2466
2547
  render: (width) => {
2467
2548
  const contentWidth = Math.max(1, width - 4);
@@ -2475,7 +2556,7 @@ ${this.primaryAgent.prompt}` };
2475
2556
  return `${clipped}${" ".repeat(Math.max(0, cellWidth - visibleWidth2(clipped)))}`;
2476
2557
  };
2477
2558
  refreshTasks();
2478
- 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"}`)}`;
2559
+ const tabs = dialogTabs(theme, ["Primary", "Subagent", "Tasks", "Log", "Settings", "Help"], `${tab[0]?.toUpperCase()}${tab.slice(1)}`);
2479
2560
  if (tab === "primary" || tab === "subagent") {
2480
2561
  const listedAgents = agentsForTab();
2481
2562
  const start = Math.max(0, Math.min(selectedAgent - 3, listedAgents.length - 7));
@@ -2494,17 +2575,14 @@ ${this.primaryAgent.prompt}` };
2494
2575
  const index = start + offset;
2495
2576
  const selected = index === selectedAgent;
2496
2577
  const cursor = selected ? theme.fg("accent", "›") : " ";
2497
- const current = scopedAgent(agent2);
2498
- const unavailable = current === undefined;
2499
- const scopedDisabled = current?.disabled ?? false;
2500
2578
  const disabled = agent2.disabled;
2501
2579
  const deleting = confirmingDeleteAgent === agent2.name;
2502
- const inherited = scope === "project" && !disabledOverrides.project.has(agent2.name);
2503
- const state = unavailable ? "unavailable" : inherited ? `inherited ${scopedDisabled ? "off" : "on"}` : scopedDisabled ? "disabled" : "enabled";
2580
+ const inherited = !disabledOverrides.project.has(agent2.name);
2581
+ const state = inherited ? `inherited ${disabled ? "off" : "on"}` : disabled ? "disabled" : "enabled";
2504
2582
  const color = deleting ? "error" : disabled ? "muted" : "text";
2505
2583
  const plainName = pad(`@${agent2.name}`, nameWidth);
2506
2584
  const name = deleting ? theme.fg("error", plainName) : disabled ? theme.fg("muted", plainName) : colorizeAgentText(agent2.color, plainName, (agentColor, text) => theme.fg(agentColor, text));
2507
- 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))}`;
2585
+ const line = `${cursor} ${name} ${theme.fg(color, pad(agent2.source, sourceWidth))} ${theme.fg(color, pad(agent2.model ?? "current model", modelWidth))} ${theme.fg(deleting ? "error" : disabled ? "warning" : color, pad(state, stateWidth))}`;
2508
2586
  lines2.push(agent2.name === this.primaryAgent?.name ? theme.bold(line) : line);
2509
2587
  }
2510
2588
  if (listedAgents.length === 0) {
@@ -2555,26 +2633,19 @@ ${this.primaryAgent.prompt}` };
2555
2633
  return box(lines2);
2556
2634
  }
2557
2635
  if (tab === "settings") {
2558
- const maxSubagentsInherited = scope === "project" && maxSubagentsSettings.project === undefined;
2559
- const sandboxInherited = scope === "project" && sandboxSettings.project === undefined;
2636
+ const maxSubagentsInherited = maxSubagentsSettings.project === undefined;
2560
2637
  const rows = [
2561
2638
  {
2562
2639
  label: "maxSubagents",
2563
2640
  value: String(selectedMaxSubagents()),
2564
2641
  inherited: maxSubagentsInherited,
2565
2642
  unavailable: false
2566
- },
2567
- {
2568
- label: "sandboxEnabled",
2569
- value: selectedSandboxEnabled() ? "on" : "off",
2570
- inherited: sandboxInherited,
2571
- unavailable: sandboxCallbacks === undefined
2572
2643
  }
2573
2644
  ];
2574
2645
  const lines2 = [tabs, ""];
2575
2646
  if (settingEditor) {
2576
- const current = settingEditor.setting === 0 ? String(selectedMaxSubagents()) : selectedSandboxEnabled() ? "on" : "off";
2577
- lines2.push(theme.fg("dim", ` ${settingLabel(settingEditor.scope, settingEditor.field)} · current ${current}`), "", ` ${theme.fg("accent", "›")} [ ${settingEditor.value}${theme.fg("accent", "█")} ]`, "", theme.fg("dim", settingEditor.scope === "project" ? " empty inherit · enter submit · esc cancel" : " enter submit · esc cancel"));
2647
+ const current = String(selectedMaxSubagents());
2648
+ lines2.push(theme.fg("dim", ` Project maxSubagents · current ${current}`), "", ` ${theme.fg("accent", "›")} [ ${settingEditor.value}${theme.fg("accent", "█")} ]`, "", theme.fg("dim", " empty inherit · enter submit · esc cancel"));
2578
2649
  if (saving)
2579
2650
  lines2.push("", theme.fg("dim", "Saving…"));
2580
2651
  return box(lines2);
@@ -2587,9 +2658,6 @@ ${this.primaryAgent.prompt}` };
2587
2658
  const label = theme.fg(row.unavailable ? "muted" : "text", row.label);
2588
2659
  lines2.push(`${cursor} ${value} ${label}`);
2589
2660
  }
2590
- if (selectedSetting === 1) {
2591
- lines2.push("", theme.fg("dim", 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."));
2592
- }
2593
2661
  if (saving)
2594
2662
  lines2.push("", theme.fg("dim", "Saving…"));
2595
2663
  return box(lines2);
@@ -2693,19 +2761,6 @@ ${this.primaryAgent.prompt}` };
2693
2761
  }
2694
2762
  return;
2695
2763
  }
2696
- if ((matchesKey(data, "shift+tab") || data.toLowerCase() === "s") && (tab === "primary" || tab === "subagent" || tab === "settings") && settingEditor === undefined) {
2697
- if (saving)
2698
- return;
2699
- if (!projectTrusted && scope === "global") {
2700
- ctx.ui.notify("Project settings require a trusted project", "warning");
2701
- return;
2702
- }
2703
- scope = scope === "project" ? "global" : "project";
2704
- reloadAgents();
2705
- reloadSettings();
2706
- tui.requestRender();
2707
- return;
2708
- }
2709
2764
  if (matchesKey(data, "tab")) {
2710
2765
  if (settingEditor !== undefined)
2711
2766
  return;
@@ -2765,22 +2820,25 @@ ${this.primaryAgent.prompt}` };
2765
2820
  });
2766
2821
  return;
2767
2822
  } else if (data === " " && agent && !saving) {
2768
- const current = scopedAgent(agent);
2769
- if (!current) {
2770
- ctx.ui.notify("Local-only agents are unavailable in Global scope", "warning");
2823
+ if (!projectTrusted) {
2824
+ ctx.ui.notify("Project settings require a trusted project", "warning");
2771
2825
  return;
2772
2826
  }
2773
- const disabled = !current.disabled;
2827
+ const disabled = !agent.disabled;
2774
2828
  saving = true;
2775
- setAgentDisabledForScope(ctx.cwd, agent.name, disabled, scope).then(async () => {
2829
+ setAgentDisabledForScope(ctx.cwd, agent.name, disabled, "project").then(async () => {
2776
2830
  await refreshAgentRuntime(agent.name);
2777
- ctx.ui.notify(`${agent.name} ${disabled ? "disabled" : "enabled"} in ${scope} settings`, "info");
2831
+ ctx.ui.notify(`${agent.name} ${disabled ? "disabled" : "enabled"} in project settings`, "info");
2778
2832
  }).catch((error) => ctx.ui.notify(`Could not update agent: ${formatError(error)}`, "error")).finally(() => {
2779
2833
  saving = false;
2780
2834
  tui.requestRender();
2781
2835
  });
2782
2836
  return;
2783
- } else if (data.toLowerCase() === "i" && scope === "project" && agent && !saving) {
2837
+ } else if (data.toLowerCase() === "i" && agent && !saving) {
2838
+ if (!projectTrusted) {
2839
+ ctx.ui.notify("Project settings require a trusted project", "warning");
2840
+ return;
2841
+ }
2784
2842
  if (!disabledOverrides.project.has(agent.name))
2785
2843
  return;
2786
2844
  saving = true;
@@ -2822,19 +2880,21 @@ ${this.primaryAgent.prompt}` };
2822
2880
  return;
2823
2881
  }
2824
2882
  if (matchesKey(data, "return")) {
2825
- const editor = settingEditor;
2826
- const input = editor.value.trim();
2883
+ const input = settingEditor.value.trim();
2827
2884
  let save;
2828
2885
  let notifyMessage;
2829
2886
  if (input === "") {
2830
- if (editor.scope !== "project") {
2831
- settingEditor = undefined;
2832
- tui.requestRender();
2887
+ if (!projectTrusted) {
2888
+ ctx.ui.notify("Project settings require a trusted project", "warning");
2889
+ return;
2890
+ }
2891
+ save = clearMaxSubagentsConfigForScope(ctx.cwd, "project");
2892
+ notifyMessage = "Project maxSubagents inherits Global";
2893
+ } else {
2894
+ if (!projectTrusted) {
2895
+ ctx.ui.notify("Project settings require a trusted project", "warning");
2833
2896
  return;
2834
2897
  }
2835
- save = editor.setting === 0 ? clearMaxSubagentsConfigForScope(ctx.cwd, "project") : sandboxCallbacks?.clearProject(ctx);
2836
- notifyMessage = `${settingLabel(editor.scope, editor.field)} inherits Global`;
2837
- } else if (editor.setting === 0) {
2838
2898
  if (!/^\d+$/.test(input)) {
2839
2899
  ctx.ui.notify(`Enter a whole number between 0 and ${MAX_SUBAGENTS}`, "error");
2840
2900
  return;
@@ -2844,21 +2904,8 @@ ${this.primaryAgent.prompt}` };
2844
2904
  ctx.ui.notify(`Enter a whole number between 0 and ${MAX_SUBAGENTS}`, "error");
2845
2905
  return;
2846
2906
  }
2847
- save = setMaxSubagentsConfigForScope(ctx.cwd, parsed, editor.scope);
2848
- notifyMessage = `${settingLabel(editor.scope, editor.field)} = ${parsed}`;
2849
- } else {
2850
- const normalized = input.toLowerCase();
2851
- const parsed = ["1", "on", "true", "yes"].includes(normalized) ? true : ["0", "off", "false", "no"].includes(normalized) ? false : undefined;
2852
- if (parsed === undefined) {
2853
- ctx.ui.notify("Enter on/off, true/false, or 1/0", "error");
2854
- return;
2855
- }
2856
- save = sandboxCallbacks?.setEnabled(ctx, parsed, editor.scope);
2857
- notifyMessage = `${settingLabel(editor.scope, editor.field)} = ${parsed ? "on" : "off"}`;
2858
- }
2859
- if (!save) {
2860
- ctx.ui.notify("Sandbox settings are unavailable", "warning");
2861
- return;
2907
+ save = setMaxSubagentsConfigForScope(ctx.cwd, parsed, "project");
2908
+ notifyMessage = `Project maxSubagents = ${parsed}`;
2862
2909
  }
2863
2910
  saving = true;
2864
2911
  settingEditor = undefined;
@@ -2868,9 +2915,6 @@ ${this.primaryAgent.prompt}` };
2868
2915
  const loadedMaxSubagents = loadMaxSubagentsSettings(ctx.cwd, projectTrusted);
2869
2916
  this.setMaxSubagents(loadedMaxSubagents.project ?? loadedMaxSubagents.global);
2870
2917
  maxSubagentsSettings = loadedMaxSubagents;
2871
- sandboxSettings = sandboxCallbacks?.load(ctx.cwd, projectTrusted) ?? {
2872
- global: true
2873
- };
2874
2918
  ctx.ui.notify(notifyMessage ?? "Setting updated", "info");
2875
2919
  }).catch((error) => ctx.ui.notify(`Could not save setting: ${formatError(error)}`, "error")).finally(() => {
2876
2920
  saving = false;
@@ -2882,9 +2926,7 @@ ${this.primaryAgent.prompt}` };
2882
2926
  const printable = kittyChar ?? (data.length === 1 && !isControlChar(data) ? data : undefined);
2883
2927
  if (printable === undefined)
2884
2928
  return;
2885
- if (settingEditor.setting === 0 && !/^\d$/.test(printable))
2886
- return;
2887
- if (settingEditor.setting === 1 && !/^[a-zA-Z0-9]$/.test(printable))
2929
+ if (!/^\d$/.test(printable))
2888
2930
  return;
2889
2931
  settingEditor.value += printable;
2890
2932
  tui.requestRender();
@@ -2895,19 +2937,16 @@ ${this.primaryAgent.prompt}` };
2895
2937
  return;
2896
2938
  }
2897
2939
  if (matchesKey(data, "up"))
2898
- selectedSetting = Math.max(0, selectedSetting - 1);
2940
+ selectedSetting = 0;
2899
2941
  else if (matchesKey(data, "down"))
2900
- selectedSetting = Math.min(1, selectedSetting + 1);
2942
+ selectedSetting = 0;
2901
2943
  else if (matchesKey(data, "return")) {
2902
- if (selectedSetting === 1 && !sandboxCallbacks) {
2903
- ctx.ui.notify("Sandbox settings are unavailable", "warning");
2944
+ if (!projectTrusted) {
2945
+ ctx.ui.notify("Project settings require a trusted project", "warning");
2904
2946
  return;
2905
2947
  }
2906
2948
  settingEditor = {
2907
- setting: selectedSetting,
2908
- scope,
2909
- field: settingField(selectedSetting),
2910
- value: ""
2949
+ value: maxSubagentsSettings.project !== undefined ? String(maxSubagentsSettings.project) : ""
2911
2950
  };
2912
2951
  } else
2913
2952
  return;
@@ -3827,6 +3866,9 @@ ${renderTaskResult(task.id, "error", message)}`);
3827
3866
  task.deleted = true;
3828
3867
  task.delivered = true;
3829
3868
  this.controllers.get(taskId)?.abort();
3869
+ if (task.sessionDir && existsSync4(task.sessionDir)) {
3870
+ rmSync(task.sessionDir, { recursive: true, force: true });
3871
+ }
3830
3872
  this.tasks.delete(taskId);
3831
3873
  this.pendingPrompts.delete(taskId);
3832
3874
  this.foregroundClaims.delete(taskId);
@@ -3837,6 +3879,42 @@ ${renderTaskResult(task.id, "error", message)}`);
3837
3879
  this.updateTaskWidget(ctx);
3838
3880
  return deleted;
3839
3881
  }
3882
+ async cleanupOrphanSubagentSessions(ctx) {
3883
+ try {
3884
+ const subagentBaseDir = join6(getAgentDir4(), "sessions", "pi-landstrip");
3885
+ if (!existsSync4(subagentBaseDir))
3886
+ return;
3887
+ const sessionFile = ctx.sessionManager.getSessionFile();
3888
+ const sessionDir = sessionFile ? dirname3(sessionFile) : undefined;
3889
+ const cwdSessions = sessionDir ? await SessionManager.list(ctx.cwd, sessionDir).catch(() => []) : await SessionManager.list(ctx.cwd).catch(() => []);
3890
+ const allSessions = await SessionManager.listAll().catch(() => []);
3891
+ const existingParentSessionIds = new Set;
3892
+ for (const s of cwdSessions) {
3893
+ if (s.id)
3894
+ existingParentSessionIds.add(s.id);
3895
+ }
3896
+ for (const s of allSessions) {
3897
+ if (s.id)
3898
+ existingParentSessionIds.add(s.id);
3899
+ }
3900
+ const activeId = ctx.sessionManager.getSessionId();
3901
+ if (activeId)
3902
+ existingParentSessionIds.add(activeId);
3903
+ const entries = readdirSync2(subagentBaseDir, { withFileTypes: true });
3904
+ const targetCwd = ctx.cwd;
3905
+ for (const entry of entries) {
3906
+ if (!entry.isDirectory())
3907
+ continue;
3908
+ const parentSessionId = entry.name;
3909
+ if (existingParentSessionIds.has(parentSessionId))
3910
+ continue;
3911
+ const parentSessionDir = join6(subagentBaseDir, parentSessionId);
3912
+ if (isSubagentSessionDirForCwd(parentSessionDir, targetCwd)) {
3913
+ rmSync(parentSessionDir, { recursive: true, force: true });
3914
+ }
3915
+ }
3916
+ } catch {}
3917
+ }
3840
3918
  restore(ctx) {
3841
3919
  this.tasks.clear();
3842
3920
  for (const entry of ctx.sessionManager.getBranch()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-landstrip",
3
- "version": "0.18.23",
3
+ "version": "0.18.25",
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.23",
42
+ "@landstrip/landstrip": "^0.18.25",
43
43
  "jsonc-parser": "^3.3.1",
44
44
  "minimatch": "^10.2.5",
45
45
  "yaml": "^2.9.0"