pi-landstrip 0.18.6 → 0.18.8
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.
- package/dist/index.ts +262 -165
- package/package.json +2 -2
package/dist/index.ts
CHANGED
|
@@ -88,7 +88,13 @@ import {
|
|
|
88
88
|
keyHint,
|
|
89
89
|
SessionManager
|
|
90
90
|
} from "@earendil-works/pi-coding-agent";
|
|
91
|
-
import {
|
|
91
|
+
import {
|
|
92
|
+
matchesKey,
|
|
93
|
+
Text,
|
|
94
|
+
truncateToWidth as truncateToWidth2,
|
|
95
|
+
visibleWidth as visibleWidth2,
|
|
96
|
+
wrapTextWithAnsi
|
|
97
|
+
} from "@earendil-works/pi-tui";
|
|
92
98
|
import { Type } from "typebox";
|
|
93
99
|
|
|
94
100
|
// agents.ts
|
|
@@ -1273,6 +1279,13 @@ function prepareProjectAgentEditor(cwd, agent, agentDir = getAgentDir3()) {
|
|
|
1273
1279
|
|
|
1274
1280
|
// box.ts
|
|
1275
1281
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
1282
|
+
function dialogTabs(theme, tabs, active) {
|
|
1283
|
+
return tabs.map((tab) => tab === active ? theme.fg("accent", theme.bold(`[${tab}]`)) : theme.fg("muted", tab)).join(" ");
|
|
1284
|
+
}
|
|
1285
|
+
function dialogKeys(theme, hints) {
|
|
1286
|
+
const separator = theme.fg("dim", " · ");
|
|
1287
|
+
return hints.map(([key, description]) => `${theme.fg("accent", key)} ${theme.fg("muted", description)}`).join(separator);
|
|
1288
|
+
}
|
|
1276
1289
|
function boxTop(theme, width, title) {
|
|
1277
1290
|
if (width < 5)
|
|
1278
1291
|
return truncateToWidth(theme.fg("accent", title), Math.max(1, width));
|
|
@@ -1720,22 +1733,27 @@ var MAX_DEPTH = 3;
|
|
|
1720
1733
|
var packageDir = dirname4(fileURLToPath(import.meta.url));
|
|
1721
1734
|
var MAX_TASK_OUTPUT_BYTES = 64 * 1024;
|
|
1722
1735
|
var INSPECTOR_BODY_LINES = 16;
|
|
1736
|
+
var TASK_PREVIEW_MIN_WIDTH = 92;
|
|
1737
|
+
var TASK_PREVIEW_LINES = 7;
|
|
1723
1738
|
var PI_THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
1724
1739
|
var AGENTS_HELP_ROWS = [
|
|
1725
1740
|
["Tab", "Next tab"],
|
|
1726
|
-
["Shift+Tab", "
|
|
1727
|
-
["↑ / ↓", "Select or scroll"],
|
|
1728
|
-
["
|
|
1729
|
-
["
|
|
1730
|
-
["
|
|
1741
|
+
["Shift+Tab / S", "Switch scope in Agents or Settings"],
|
|
1742
|
+
["↑ / ↓", "Select item or scroll when follow is off"],
|
|
1743
|
+
["F", "Toggle task log follow"],
|
|
1744
|
+
["Page Up / Down", "Scroll task output by page"],
|
|
1745
|
+
["Home / End", "Jump to task output boundary"],
|
|
1746
|
+
["Enter", "Inspect task, set primary, or save"],
|
|
1747
|
+
["Esc", "Back or close"],
|
|
1731
1748
|
["Ctrl+C", "Close"],
|
|
1732
|
-
["
|
|
1733
|
-
["
|
|
1749
|
+
["Space", "Enable agent or toggle sandbox"],
|
|
1750
|
+
["E", "Edit project agent"],
|
|
1751
|
+
["X", "Delete project agent"],
|
|
1752
|
+
["Y / N", "Confirm or cancel deletion"],
|
|
1734
1753
|
["I", "Use global value"],
|
|
1754
|
+
["R", "Discard setting changes"],
|
|
1735
1755
|
["0-9 / + / -", "Set subagent limit"],
|
|
1736
|
-
["
|
|
1737
|
-
["Ctrl+G", "Edit agent"],
|
|
1738
|
-
["Ctrl+D", "Delete agent"]
|
|
1756
|
+
["Backspace", "Open parent task"]
|
|
1739
1757
|
];
|
|
1740
1758
|
var MIN_SUPPORTED_PI_VERSION = [0, 82, 0];
|
|
1741
1759
|
var cachedPiPackage;
|
|
@@ -1848,6 +1866,40 @@ function taskDuration(details) {
|
|
|
1848
1866
|
const seconds = Math.max(0, end - details.startedAt) / 1000;
|
|
1849
1867
|
return seconds < 10 ? `${seconds.toFixed(1)}s` : `${Math.round(seconds)}s`;
|
|
1850
1868
|
}
|
|
1869
|
+
function activeTaskRecords(tasks) {
|
|
1870
|
+
const byId = new Map(tasks.map((task) => [task.id, task]));
|
|
1871
|
+
const visibleIds = new Set;
|
|
1872
|
+
for (const task of tasks) {
|
|
1873
|
+
if (task.state !== "queued" && task.state !== "running")
|
|
1874
|
+
continue;
|
|
1875
|
+
const seen = new Set;
|
|
1876
|
+
let current = task;
|
|
1877
|
+
while (current && !seen.has(current.id)) {
|
|
1878
|
+
seen.add(current.id);
|
|
1879
|
+
visibleIds.add(current.id);
|
|
1880
|
+
current = current.parentTaskId ? byId.get(current.parentTaskId) : undefined;
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
return tasks.filter((task) => visibleIds.has(task.id));
|
|
1884
|
+
}
|
|
1885
|
+
function taskProgress(task) {
|
|
1886
|
+
if (task.state === "queued")
|
|
1887
|
+
return ["waiting for slot"];
|
|
1888
|
+
if (task.state !== "running")
|
|
1889
|
+
return [];
|
|
1890
|
+
const progress = [];
|
|
1891
|
+
if (task.currentTool)
|
|
1892
|
+
progress.push(`→ ${task.currentTool}`);
|
|
1893
|
+
if (task.toolCalls !== undefined) {
|
|
1894
|
+
progress.push(`${task.toolCalls} call${task.toolCalls === 1 ? "" : "s"}`);
|
|
1895
|
+
}
|
|
1896
|
+
const duration = taskDuration(taskDetails(task));
|
|
1897
|
+
if (duration)
|
|
1898
|
+
progress.push(duration);
|
|
1899
|
+
if (task.retryAttempt)
|
|
1900
|
+
progress.push(`retry ${task.retryAttempt}`);
|
|
1901
|
+
return progress;
|
|
1902
|
+
}
|
|
1851
1903
|
function taskOutput(details, fallback) {
|
|
1852
1904
|
return details.error ?? details.output ?? fallback;
|
|
1853
1905
|
}
|
|
@@ -1993,7 +2045,7 @@ class PermissionBroker {
|
|
|
1993
2045
|
if (signal?.aborted)
|
|
1994
2046
|
throw new Error("Permission request cancelled");
|
|
1995
2047
|
const choice = await ctx.ui.select(`${task}: permission required
|
|
1996
|
-
${permission}: ${resource}`, ["Allow once", "Allow for this session", "
|
|
2048
|
+
${permission}: ${resource}`, ["Allow once", "Allow for this session", "Keep blocked"], { signal });
|
|
1997
2049
|
if (choice === "Allow for this session")
|
|
1998
2050
|
this.grants.add(key);
|
|
1999
2051
|
if (choice !== "Allow once" && choice !== "Allow for this session") {
|
|
@@ -2370,7 +2422,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
2370
2422
|
return;
|
|
2371
2423
|
}
|
|
2372
2424
|
const projectTrusted = isProjectTrusted(ctx);
|
|
2373
|
-
|
|
2425
|
+
let tasks = [...this.tasks.values()];
|
|
2374
2426
|
const requested = args.trim();
|
|
2375
2427
|
let scope = projectTrusted ? "project" : "global";
|
|
2376
2428
|
let catalog = this.getAgentCatalog(ctx);
|
|
@@ -2395,6 +2447,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
2395
2447
|
let editing = false;
|
|
2396
2448
|
let confirmingDeleteAgent;
|
|
2397
2449
|
let detail = requested.length > 0;
|
|
2450
|
+
let follow = detail;
|
|
2398
2451
|
let scroll = 0;
|
|
2399
2452
|
const reloadAgents = () => {
|
|
2400
2453
|
const selectedName = agents[selectedAgent]?.name;
|
|
@@ -2409,6 +2462,12 @@ ${this.primaryAgent.prompt}` };
|
|
|
2409
2462
|
maxSubagentsSettings = loadMaxSubagentsSettings(ctx.cwd, projectTrusted);
|
|
2410
2463
|
sandboxSettings = sandboxCallbacks?.load(ctx.cwd, projectTrusted) ?? { global: true };
|
|
2411
2464
|
};
|
|
2465
|
+
const refreshTasks = () => {
|
|
2466
|
+
const selectedId = tasks[selectedTask]?.id;
|
|
2467
|
+
tasks = [...this.tasks.values()];
|
|
2468
|
+
const nextSelected = selectedId ? tasks.findIndex((task) => task.id === selectedId) : selectedTask;
|
|
2469
|
+
selectedTask = Math.max(0, Math.min(nextSelected < 0 ? 0 : nextSelected, tasks.length - 1));
|
|
2470
|
+
};
|
|
2412
2471
|
const refreshAgentRuntime = async (name) => {
|
|
2413
2472
|
reloadAgents();
|
|
2414
2473
|
if (this.primaryAgent?.name === name) {
|
|
@@ -2440,38 +2499,39 @@ ${this.primaryAgent.prompt}` };
|
|
|
2440
2499
|
const clipped = truncateToWidth2(value, cellWidth);
|
|
2441
2500
|
return `${clipped}${" ".repeat(Math.max(0, cellWidth - visibleWidth2(clipped)))}`;
|
|
2442
2501
|
};
|
|
2443
|
-
|
|
2444
|
-
const tabs = `${
|
|
2502
|
+
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"}`)}`;
|
|
2445
2504
|
if (tab === "agents") {
|
|
2446
2505
|
const start = Math.max(0, Math.min(selectedAgent - 3, agents.length - 7));
|
|
2447
2506
|
const shown2 = agents.slice(start, start + 7);
|
|
2448
2507
|
const nameWidth = Math.min(20, Math.max(5, ...agents.map((agent2) => visibleWidth2(`@${agent2.name}`))));
|
|
2508
|
+
const primaryWidth = 7;
|
|
2449
2509
|
const modeWidth = 8;
|
|
2450
2510
|
const sourceWidth = 8;
|
|
2451
|
-
const
|
|
2452
|
-
const fixedWidth =
|
|
2511
|
+
const stateWidth = 13;
|
|
2512
|
+
const fixedWidth = 2 + primaryWidth + 1 + nameWidth + 1 + modeWidth + 1 + sourceWidth + 1 + stateWidth;
|
|
2453
2513
|
const modelWidth = Math.max(10, Math.min(30, contentWidth - fixedWidth - 1));
|
|
2454
2514
|
const lines2 = [
|
|
2455
2515
|
tabs,
|
|
2456
2516
|
"",
|
|
2457
|
-
theme.fg("dim", `
|
|
2517
|
+
theme.fg("dim", ` ${pad("Primary", primaryWidth)} ${pad("Agent", nameWidth)} ${pad("Mode", modeWidth)} ${pad("Source", sourceWidth)} ${pad("Model", modelWidth)} ${pad("State", stateWidth)}`)
|
|
2458
2518
|
];
|
|
2459
2519
|
for (const [offset, agent2] of shown2.entries()) {
|
|
2460
2520
|
const index = start + offset;
|
|
2461
2521
|
const selected = index === selectedAgent;
|
|
2462
2522
|
const cursor = selected ? theme.fg("accent", "›") : " ";
|
|
2463
|
-
const
|
|
2523
|
+
const primary = agent2.name === this.primaryAgent?.name ? "yes" : "";
|
|
2464
2524
|
const current = scopedAgent(agent2);
|
|
2465
2525
|
const unavailable = current === undefined;
|
|
2466
2526
|
const scopedDisabled = current?.disabled ?? false;
|
|
2467
2527
|
const disabled = agent2.disabled;
|
|
2468
2528
|
const deleting = confirmingDeleteAgent === agent2.name;
|
|
2469
2529
|
const inherited = scope === "project" && !disabledOverrides.project.has(agent2.name);
|
|
2470
|
-
const
|
|
2530
|
+
const state = unavailable ? "unavailable" : inherited ? `inherited ${scopedDisabled ? "off" : "on"}` : scopedDisabled ? "disabled" : "enabled";
|
|
2471
2531
|
const color = deleting ? "error" : disabled ? "muted" : "text";
|
|
2472
2532
|
const plainName = pad(`@${agent2.name}`, nameWidth);
|
|
2473
2533
|
const name = deleting ? theme.fg("error", plainName) : disabled ? theme.fg("muted", plainName) : colorizeAgentText(agent2.color, plainName, (agentColor, text) => theme.fg(agentColor, text));
|
|
2474
|
-
lines2.push(`${cursor} ${
|
|
2534
|
+
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))}`);
|
|
2475
2535
|
}
|
|
2476
2536
|
if (agents.length === 0)
|
|
2477
2537
|
lines2.push(theme.fg("muted", " No agents configured."));
|
|
@@ -2499,7 +2559,9 @@ ${this.primaryAgent.prompt}` };
|
|
|
2499
2559
|
if (agent.variant && !PI_THINKING_LEVELS.has(agent.variant)) {
|
|
2500
2560
|
unsupported.push(`variant=${agent.variant}`);
|
|
2501
2561
|
}
|
|
2502
|
-
|
|
2562
|
+
if (unsupported.length > 0) {
|
|
2563
|
+
lines2.push(`${theme.fg("dim", "Unsupported RPC options:")} ${theme.fg("text", unsupported.join(", "))}`);
|
|
2564
|
+
}
|
|
2503
2565
|
}
|
|
2504
2566
|
if (catalog.diagnostics.length > 0) {
|
|
2505
2567
|
lines2.push(theme.fg("error", "Catalog diagnostics"));
|
|
@@ -2508,7 +2570,10 @@ ${this.primaryAgent.prompt}` };
|
|
|
2508
2570
|
}
|
|
2509
2571
|
}
|
|
2510
2572
|
if (confirmingDeleteAgent) {
|
|
2511
|
-
lines2.push("", theme.fg("error", `Delete @${confirmingDeleteAgent}?
|
|
2573
|
+
lines2.push("", theme.fg("error", `Delete @${confirmingDeleteAgent}? This removes its project file.`), dialogKeys(theme, [
|
|
2574
|
+
["Y", "delete"],
|
|
2575
|
+
["N / Esc", "cancel"]
|
|
2576
|
+
]));
|
|
2512
2577
|
} else if (saving) {
|
|
2513
2578
|
lines2.push("", theme.fg("dim", "Saving…"));
|
|
2514
2579
|
}
|
|
@@ -2532,11 +2597,10 @@ ${this.primaryAgent.prompt}` };
|
|
|
2532
2597
|
const text = `[ ${row.value} ]`;
|
|
2533
2598
|
const value = selected ? theme.fg("accent", text) : theme.fg(row.unavailable ? "muted" : "text", text);
|
|
2534
2599
|
const label = theme.fg(row.unavailable ? "muted" : "text", row.label);
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
if (selectedMaxSubagents() === 0) {
|
|
2538
|
-
lines2.push(` ${theme.fg("dim", "Subagents disabled")}`);
|
|
2600
|
+
const dirty = dirtySettings.has(index) ? theme.fg("warning", " *") : "";
|
|
2601
|
+
lines2.push(`${cursor} ${value} ${label}${dirty}`);
|
|
2539
2602
|
}
|
|
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."));
|
|
2540
2604
|
if (saving)
|
|
2541
2605
|
lines2.push("", theme.fg("dim", "Saving…"));
|
|
2542
2606
|
return box(lines2);
|
|
@@ -2553,35 +2617,61 @@ ${this.primaryAgent.prompt}` };
|
|
|
2553
2617
|
return box(lines2);
|
|
2554
2618
|
}
|
|
2555
2619
|
if (!detail) {
|
|
2556
|
-
const
|
|
2620
|
+
const listLines = [];
|
|
2557
2621
|
if (tasks.length === 0) {
|
|
2558
|
-
|
|
2622
|
+
listLines.push("No task sessions in this session.");
|
|
2559
2623
|
} else {
|
|
2560
2624
|
const start = Math.max(0, Math.min(selectedTask - 5, tasks.length - 11));
|
|
2561
2625
|
const shown2 = tasks.slice(start, start + 11);
|
|
2562
|
-
for (const [offset,
|
|
2626
|
+
for (const [offset, task3] of shown2.entries()) {
|
|
2563
2627
|
const index = start + offset;
|
|
2564
2628
|
const cursor = index === selectedTask ? theme.fg("accent", "›") : " ";
|
|
2565
|
-
const indent = " ".repeat(Math.max(0,
|
|
2566
|
-
|
|
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))}`);
|
|
2567
2631
|
}
|
|
2568
2632
|
}
|
|
2569
|
-
|
|
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]);
|
|
2570
2664
|
}
|
|
2571
2665
|
const task = tasks[selectedTask];
|
|
2572
2666
|
if (!task)
|
|
2573
2667
|
return box([tabs, "", "No task sessions in this session."]);
|
|
2574
|
-
const
|
|
2575
|
-
const siblingIndex = siblings.findIndex((candidate) => candidate.id === task.id);
|
|
2576
|
-
const transcript = taskTranscript(task).flatMap((line) => line.split(`
|
|
2577
|
-
`));
|
|
2668
|
+
const transcript = taskTranscript(task).flatMap((line) => wrapTextWithAnsi(line, contentWidth));
|
|
2578
2669
|
const maxScroll = Math.max(0, transcript.length - INSPECTOR_BODY_LINES);
|
|
2579
|
-
scroll = Math.min(scroll, maxScroll);
|
|
2670
|
+
scroll = follow ? maxScroll : Math.min(scroll, maxScroll);
|
|
2580
2671
|
const shown = transcript.slice(scroll, scroll + INSPECTOR_BODY_LINES);
|
|
2581
2672
|
const duration = taskDuration(taskDetails(task));
|
|
2582
2673
|
const metrics = [
|
|
2583
2674
|
task.id,
|
|
2584
|
-
`${siblingIndex + 1} of ${siblings.length}`,
|
|
2585
2675
|
task.toolCalls === undefined ? undefined : `${task.toolCalls} tool call${task.toolCalls === 1 ? "" : "s"}`,
|
|
2586
2676
|
duration
|
|
2587
2677
|
].filter(Boolean);
|
|
@@ -2589,25 +2679,25 @@ ${this.primaryAgent.prompt}` };
|
|
|
2589
2679
|
tabs,
|
|
2590
2680
|
"",
|
|
2591
2681
|
`${theme.fg("accent", theme.bold(`@${task.agent}`))} ${theme.fg("text", task.description)}`,
|
|
2592
|
-
`${taskState(theme, task)} ${theme.fg("dim", metrics.join(" · "))}`,
|
|
2682
|
+
`${taskState(theme, task)} ${theme.fg("dim", metrics.join(" · "))} ${theme.fg("dim", "·")} ${theme.fg(follow ? "accent" : "muted", `Follow: ${follow ? "on" : "off"}`)}`,
|
|
2593
2683
|
"",
|
|
2594
2684
|
...shown.map((line) => theme.fg("toolOutput", line))
|
|
2595
2685
|
];
|
|
2596
2686
|
while (lines.length < INSPECTOR_BODY_LINES + 5)
|
|
2597
2687
|
lines.push("");
|
|
2598
2688
|
if (transcript.length > INSPECTOR_BODY_LINES) {
|
|
2599
|
-
lines.push(theme.fg("dim", `${scroll + 1}
|
|
2689
|
+
lines.push(theme.fg("dim", `${scroll + 1}–${scroll + shown.length} of ${transcript.length}`));
|
|
2600
2690
|
}
|
|
2601
2691
|
return box(lines.map((line) => truncateToWidth2(line, contentWidth)));
|
|
2602
2692
|
},
|
|
2603
2693
|
handleInput: (data) => {
|
|
2604
2694
|
if (confirmingDeleteAgent !== undefined) {
|
|
2605
|
-
if (matchesKey(data, "escape")) {
|
|
2695
|
+
if (matchesKey(data, "escape") || data.toLowerCase() === "n") {
|
|
2606
2696
|
confirmingDeleteAgent = undefined;
|
|
2607
2697
|
tui.requestRender();
|
|
2608
2698
|
return;
|
|
2609
2699
|
}
|
|
2610
|
-
if (
|
|
2700
|
+
if (data.toLowerCase() === "y") {
|
|
2611
2701
|
const name = confirmingDeleteAgent;
|
|
2612
2702
|
const agent = agents.find((candidate) => candidate.name === name);
|
|
2613
2703
|
confirmingDeleteAgent = undefined;
|
|
@@ -2629,15 +2719,18 @@ ${this.primaryAgent.prompt}` };
|
|
|
2629
2719
|
}
|
|
2630
2720
|
return;
|
|
2631
2721
|
}
|
|
2632
|
-
if (matchesKey(data, "shift+tab")) {
|
|
2722
|
+
if ((matchesKey(data, "shift+tab") || data.toLowerCase() === "s") && (tab === "agents" || tab === "settings")) {
|
|
2633
2723
|
if (saving)
|
|
2634
2724
|
return;
|
|
2725
|
+
if (dirtySettings.size > 0) {
|
|
2726
|
+
ctx.ui.notify("Save or discard settings changes before changing scope", "warning");
|
|
2727
|
+
return;
|
|
2728
|
+
}
|
|
2635
2729
|
if (!projectTrusted && scope === "global") {
|
|
2636
2730
|
ctx.ui.notify("Project settings require a trusted project", "warning");
|
|
2637
2731
|
return;
|
|
2638
2732
|
}
|
|
2639
2733
|
scope = scope === "project" ? "global" : "project";
|
|
2640
|
-
dirtySettings.clear();
|
|
2641
2734
|
editing = false;
|
|
2642
2735
|
reloadAgents();
|
|
2643
2736
|
reloadSettings();
|
|
@@ -2645,9 +2738,14 @@ ${this.primaryAgent.prompt}` };
|
|
|
2645
2738
|
return;
|
|
2646
2739
|
}
|
|
2647
2740
|
if (matchesKey(data, "tab")) {
|
|
2741
|
+
if (tab === "settings" && dirtySettings.size > 0) {
|
|
2742
|
+
ctx.ui.notify("Save changes with Enter or discard them with R before leaving", "warning");
|
|
2743
|
+
return;
|
|
2744
|
+
}
|
|
2648
2745
|
const tabs = ["agents", "tasks", "settings", "help"];
|
|
2649
2746
|
tab = tabs[(tabs.indexOf(tab) + 1) % tabs.length] ?? "agents";
|
|
2650
2747
|
detail = false;
|
|
2748
|
+
follow = false;
|
|
2651
2749
|
editing = false;
|
|
2652
2750
|
scroll = 0;
|
|
2653
2751
|
tui.requestRender();
|
|
@@ -2663,15 +2761,19 @@ ${this.primaryAgent.prompt}` };
|
|
|
2663
2761
|
selectedAgent = Math.max(0, selectedAgent - 1);
|
|
2664
2762
|
else if (matchesKey(data, "down") && agents.length > 0) {
|
|
2665
2763
|
selectedAgent = Math.min(agents.length - 1, selectedAgent + 1);
|
|
2666
|
-
} else if (
|
|
2667
|
-
if (!projectTrusted || !canDeleteProjectAgent(ctx.cwd, agent))
|
|
2764
|
+
} else if (data.toLowerCase() === "x" && agent && !saving) {
|
|
2765
|
+
if (!projectTrusted || !canDeleteProjectAgent(ctx.cwd, agent)) {
|
|
2766
|
+
ctx.ui.notify("Only project agent files can be deleted here", "warning");
|
|
2668
2767
|
return;
|
|
2768
|
+
}
|
|
2669
2769
|
confirmingDeleteAgent = agent.name;
|
|
2670
2770
|
tui.requestRender();
|
|
2671
2771
|
return;
|
|
2672
|
-
} else if (
|
|
2673
|
-
if (!projectTrusted)
|
|
2772
|
+
} else if (data.toLowerCase() === "e" && agent && !saving) {
|
|
2773
|
+
if (!projectTrusted) {
|
|
2774
|
+
ctx.ui.notify("Agent editing requires a trusted project", "warning");
|
|
2674
2775
|
return;
|
|
2776
|
+
}
|
|
2675
2777
|
saving = true;
|
|
2676
2778
|
(async () => {
|
|
2677
2779
|
const document = prepareProjectAgentEditor(ctx.cwd, agent);
|
|
@@ -2686,7 +2788,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
2686
2788
|
tui.requestRender();
|
|
2687
2789
|
});
|
|
2688
2790
|
return;
|
|
2689
|
-
} else if (
|
|
2791
|
+
} else if (data === " " && agent && !saving) {
|
|
2690
2792
|
const current = scopedAgent(agent);
|
|
2691
2793
|
if (!current) {
|
|
2692
2794
|
ctx.ui.notify("Local-only agents are unavailable in Global scope", "warning");
|
|
@@ -2702,7 +2804,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
2702
2804
|
tui.requestRender();
|
|
2703
2805
|
});
|
|
2704
2806
|
return;
|
|
2705
|
-
} else if (data === "i" && scope === "project" && agent && !saving) {
|
|
2807
|
+
} else if (data.toLowerCase() === "i" && scope === "project" && agent && !saving) {
|
|
2706
2808
|
if (!disabledOverrides.project.has(agent.name))
|
|
2707
2809
|
return;
|
|
2708
2810
|
saving = true;
|
|
@@ -2717,6 +2819,9 @@ ${this.primaryAgent.prompt}` };
|
|
|
2717
2819
|
} else if (matchesKey(data, "return") && agent && !agent.disabled && agentSupportsMode(agent, "primary")) {
|
|
2718
2820
|
this.selectPrimaryAgent(agent.name, ctx).then(() => tui.requestRender()).catch((error) => ctx.ui.notify(`Could not select primary agent: ${formatError(error)}`, "error"));
|
|
2719
2821
|
return;
|
|
2822
|
+
} else if (matchesKey(data, "return") && agent) {
|
|
2823
|
+
ctx.ui.notify(agent.disabled ? "Enable this agent before setting it as primary" : "This agent cannot be used as the primary agent", "warning");
|
|
2824
|
+
return;
|
|
2720
2825
|
} else
|
|
2721
2826
|
return;
|
|
2722
2827
|
tui.requestRender();
|
|
@@ -2726,9 +2831,20 @@ ${this.primaryAgent.prompt}` };
|
|
|
2726
2831
|
if (saving)
|
|
2727
2832
|
return;
|
|
2728
2833
|
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
|
|
2834
|
+
if (dirtySettings.size > 0) {
|
|
2835
|
+
ctx.ui.notify("Save changes with Enter or discard them with R before closing", "warning");
|
|
2836
|
+
return;
|
|
2837
|
+
}
|
|
2729
2838
|
done();
|
|
2730
2839
|
return;
|
|
2731
2840
|
}
|
|
2841
|
+
if (data.toLowerCase() === "r") {
|
|
2842
|
+
reloadSettings();
|
|
2843
|
+
dirtySettings.clear();
|
|
2844
|
+
editing = false;
|
|
2845
|
+
tui.requestRender();
|
|
2846
|
+
return;
|
|
2847
|
+
}
|
|
2732
2848
|
if (matchesKey(data, "up")) {
|
|
2733
2849
|
selectedSetting = Math.max(0, selectedSetting - 1);
|
|
2734
2850
|
editing = false;
|
|
@@ -2803,16 +2919,13 @@ ${this.primaryAgent.prompt}` };
|
|
|
2803
2919
|
if (!detail) {
|
|
2804
2920
|
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c"))
|
|
2805
2921
|
done();
|
|
2806
|
-
else if (matchesKey(data, "left"))
|
|
2807
|
-
tab = "agents";
|
|
2808
|
-
else if (matchesKey(data, "right"))
|
|
2809
|
-
tab = "settings";
|
|
2810
2922
|
else if (matchesKey(data, "up"))
|
|
2811
2923
|
selectedTask = Math.max(0, selectedTask - 1);
|
|
2812
2924
|
else if (matchesKey(data, "down") && tasks.length > 0) {
|
|
2813
2925
|
selectedTask = Math.min(tasks.length - 1, selectedTask + 1);
|
|
2814
2926
|
} else if (matchesKey(data, "return") && tasks.length > 0) {
|
|
2815
2927
|
detail = true;
|
|
2928
|
+
follow = true;
|
|
2816
2929
|
scroll = 0;
|
|
2817
2930
|
} else
|
|
2818
2931
|
return;
|
|
@@ -2824,27 +2937,33 @@ ${this.primaryAgent.prompt}` };
|
|
|
2824
2937
|
return;
|
|
2825
2938
|
if (matchesKey(data, "escape")) {
|
|
2826
2939
|
detail = false;
|
|
2940
|
+
follow = false;
|
|
2827
2941
|
scroll = 0;
|
|
2828
2942
|
} else if (matchesKey(data, "ctrl+c")) {
|
|
2829
2943
|
done();
|
|
2830
|
-
} else if (
|
|
2944
|
+
} else if (data.toLowerCase() === "f") {
|
|
2945
|
+
follow = !follow;
|
|
2946
|
+
} else if (!follow && matchesKey(data, "up")) {
|
|
2831
2947
|
scroll = Math.max(0, scroll - 1);
|
|
2832
|
-
} else if (matchesKey(data, "down")) {
|
|
2948
|
+
} else if (!follow && matchesKey(data, "down")) {
|
|
2833
2949
|
scroll += 1;
|
|
2834
|
-
} else if (
|
|
2950
|
+
} else if (!follow && matchesKey(data, "pageUp")) {
|
|
2951
|
+
scroll = Math.max(0, scroll - INSPECTOR_BODY_LINES);
|
|
2952
|
+
} else if (!follow && matchesKey(data, "pageDown")) {
|
|
2953
|
+
scroll += INSPECTOR_BODY_LINES;
|
|
2954
|
+
} else if (!follow && matchesKey(data, "home")) {
|
|
2955
|
+
scroll = 0;
|
|
2956
|
+
} else if (!follow && matchesKey(data, "end")) {
|
|
2957
|
+
scroll = Number.MAX_SAFE_INTEGER;
|
|
2958
|
+
} else if (matchesKey(data, "backspace")) {
|
|
2835
2959
|
const parentIndex = task.parentTaskId ? tasks.findIndex((candidate) => candidate.id === task.parentTaskId) : -1;
|
|
2836
|
-
if (parentIndex >= 0)
|
|
2960
|
+
if (parentIndex >= 0) {
|
|
2837
2961
|
selectedTask = parentIndex;
|
|
2838
|
-
|
|
2962
|
+
follow = true;
|
|
2963
|
+
} else {
|
|
2839
2964
|
detail = false;
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
const siblings = tasks.filter((candidate) => candidate.parentTaskId === task.parentTaskId);
|
|
2843
|
-
const siblingIndex = siblings.findIndex((candidate) => candidate.id === task.id);
|
|
2844
|
-
const offset = matchesKey(data, "left") ? -1 : 1;
|
|
2845
|
-
const sibling = siblings[siblingIndex + offset];
|
|
2846
|
-
if (sibling)
|
|
2847
|
-
selectedTask = tasks.indexOf(sibling);
|
|
2965
|
+
follow = false;
|
|
2966
|
+
}
|
|
2848
2967
|
scroll = 0;
|
|
2849
2968
|
} else
|
|
2850
2969
|
return;
|
|
@@ -2917,7 +3036,7 @@ ${descriptions}` : ""),
|
|
|
2917
3036
|
const background = input.background ? theme.fg("muted", " (background)") : "";
|
|
2918
3037
|
const description = input.description?.trim() || "...";
|
|
2919
3038
|
const agent = input.subagent_type?.trim() || "...";
|
|
2920
|
-
const title = theme.fg("toolTitle", theme.bold("Agent
|
|
3039
|
+
const title = theme.fg("toolTitle", theme.bold("Agent"));
|
|
2921
3040
|
return new Text(`${title}${background}${theme.fg("muted", " — ")}${theme.fg("text", description)}
|
|
2922
3041
|
` + theme.fg("dim", ` @${agent}`), 0, 0);
|
|
2923
3042
|
},
|
|
@@ -3608,33 +3727,26 @@ ${renderTaskResult(task.id, "error", message)}`);
|
|
|
3608
3727
|
if (!ctx.hasUI || ctx.mode !== undefined && ctx.mode !== "tui")
|
|
3609
3728
|
return;
|
|
3610
3729
|
const tasks = [...this.tasks.values()];
|
|
3611
|
-
const byId = new Map(tasks.map((task) => [task.id, task]));
|
|
3612
|
-
const rootId = (task) => {
|
|
3613
|
-
const seen = new Set;
|
|
3614
|
-
let current = task;
|
|
3615
|
-
while (current.parentTaskId && !seen.has(current.id)) {
|
|
3616
|
-
seen.add(current.id);
|
|
3617
|
-
const parent = byId.get(current.parentTaskId);
|
|
3618
|
-
if (!parent)
|
|
3619
|
-
break;
|
|
3620
|
-
current = parent;
|
|
3621
|
-
}
|
|
3622
|
-
return current.id;
|
|
3623
|
-
};
|
|
3624
3730
|
const active = tasks.filter((task) => task.state === "queued" || task.state === "running");
|
|
3625
|
-
|
|
3626
|
-
if (activeRoots.size === 0) {
|
|
3731
|
+
if (active.length === 0) {
|
|
3627
3732
|
ctx.ui.setWidget(TASK_WIDGET, undefined);
|
|
3628
3733
|
return;
|
|
3629
3734
|
}
|
|
3630
|
-
const visible = tasks
|
|
3735
|
+
const visible = activeTaskRecords(tasks);
|
|
3736
|
+
const byId = new Map(tasks.map((task) => [task.id, task]));
|
|
3737
|
+
const running = active.filter((task) => task.state === "running").length;
|
|
3738
|
+
const queued = active.length - running;
|
|
3739
|
+
const runningSummary = this.maxSubagents > 0 ? `${running}/${this.maxSubagents} running` : `${running} running`;
|
|
3740
|
+
const summary = queued > 0 ? `${runningSummary} · ${queued} queued` : runningSummary;
|
|
3631
3741
|
ctx.ui.setWidget(TASK_WIDGET, (_tui, theme) => ({
|
|
3632
3742
|
render: (width) => {
|
|
3633
|
-
const header = theme.fg("accent", theme.bold("Subagents")) + theme.fg("dim", ` ${
|
|
3743
|
+
const header = theme.fg("accent", theme.bold("Subagents")) + theme.fg("dim", ` ${summary}`);
|
|
3634
3744
|
const tree = taskTreeLines(visible, (task) => {
|
|
3635
3745
|
const agent = theme.fg("accent", `@${task.agent}`);
|
|
3636
3746
|
const description = theme.fg("text", task.description);
|
|
3637
|
-
|
|
3747
|
+
const progress = taskProgress(byId.get(task.id));
|
|
3748
|
+
const suffix = progress.length > 0 ? theme.fg("dim", ` · ${progress.join(" · ")}`) : "";
|
|
3749
|
+
return `${taskState(theme, task)} ${agent} ${description}${suffix}`;
|
|
3638
3750
|
});
|
|
3639
3751
|
const shown = tree.slice(0, 8);
|
|
3640
3752
|
if (tree.length > shown.length) {
|
|
@@ -3855,28 +3967,26 @@ var PROXY_ENVIRONMENT_VARIABLES = [
|
|
|
3855
3967
|
];
|
|
3856
3968
|
var packageDir2 = dirname5(fileURLToPath2(import.meta.url));
|
|
3857
3969
|
var PERMISSION_OPTIONS = [
|
|
3858
|
-
{ label: "Allow once",
|
|
3859
|
-
{ label: "Allow for this session
|
|
3860
|
-
{ label: "Abort (keep blocked)", key: "esc", action: "abort" },
|
|
3970
|
+
{ label: "Allow once", action: "once" },
|
|
3971
|
+
{ label: "Allow for this session", action: "session" },
|
|
3861
3972
|
{
|
|
3862
3973
|
label: "Allow for this project",
|
|
3863
|
-
key: "P",
|
|
3864
3974
|
action: "project",
|
|
3865
3975
|
confirm: true,
|
|
3866
|
-
hint: "
|
|
3976
|
+
hint: "save to .pi/sandbox.json"
|
|
3867
3977
|
},
|
|
3868
3978
|
{
|
|
3869
3979
|
label: "Allow for all projects",
|
|
3870
|
-
key: "A",
|
|
3871
3980
|
action: "global",
|
|
3872
3981
|
confirm: true,
|
|
3873
|
-
hint: "
|
|
3874
|
-
}
|
|
3982
|
+
hint: "save to ~/.pi/agent/sandbox.json"
|
|
3983
|
+
},
|
|
3984
|
+
{ label: "Keep blocked", action: "abort" }
|
|
3875
3985
|
];
|
|
3876
3986
|
var NETWORK_PERMISSION_OPTIONS = [
|
|
3877
|
-
{ label: "Allow once",
|
|
3878
|
-
{ label: "Allow for this session
|
|
3879
|
-
{ label: "
|
|
3987
|
+
{ label: "Allow once", action: "once" },
|
|
3988
|
+
{ label: "Allow for this session", action: "session" },
|
|
3989
|
+
{ label: "Keep blocked", action: "abort" }
|
|
3880
3990
|
];
|
|
3881
3991
|
function loadSandboxConfig(cwd, includeProject) {
|
|
3882
3992
|
const projectConfigPath = join7(cwd, ".pi", "sandbox.json");
|
|
@@ -3990,10 +4100,7 @@ function writeConfigFile(configPath, config) {
|
|
|
3990
4100
|
}
|
|
3991
4101
|
function getSandboxConfigWriteTarget(cwd, includeProject = true) {
|
|
3992
4102
|
const { globalPath, projectPath } = getConfigPaths(cwd);
|
|
3993
|
-
|
|
3994
|
-
return { scope: "project", path: projectPath };
|
|
3995
|
-
}
|
|
3996
|
-
return { scope: "global", path: globalPath };
|
|
4103
|
+
return includeProject ? { scope: "project", path: projectPath } : { scope: "global", path: globalPath };
|
|
3997
4104
|
}
|
|
3998
4105
|
async function writeSandboxConfigEnabled(cwd, enabled, scope) {
|
|
3999
4106
|
const paths = getConfigPaths(cwd);
|
|
@@ -4350,21 +4457,23 @@ async function stopPreparedChild(child) {
|
|
|
4350
4457
|
async function showPermissionPrompt(ctx, title, options, signal) {
|
|
4351
4458
|
if (!ctx.hasUI)
|
|
4352
4459
|
return "abort";
|
|
4353
|
-
const labels = options.map((option2) => option2.hint ? `${option2.label} ${option2.hint}` : option2.label);
|
|
4460
|
+
const labels = options.map((option2) => option2.hint ? `${option2.label} — ${option2.hint}` : option2.label);
|
|
4354
4461
|
const selected = await ctx.ui.select(title, labels, { signal });
|
|
4355
4462
|
const index = selected === undefined ? -1 : labels.indexOf(selected);
|
|
4356
4463
|
const option = options[index];
|
|
4357
4464
|
if (!option)
|
|
4358
4465
|
return "abort";
|
|
4359
4466
|
if (option.confirm) {
|
|
4360
|
-
const confirmed = await ctx.ui.confirm(
|
|
4467
|
+
const confirmed = await ctx.ui.confirm("Save this permission?", `${title}
|
|
4468
|
+
|
|
4469
|
+
${option.hint ?? "This changes persisted sandbox policy."}`, { signal });
|
|
4361
4470
|
if (!confirmed)
|
|
4362
4471
|
return "abort";
|
|
4363
4472
|
}
|
|
4364
4473
|
return option.action;
|
|
4365
4474
|
}
|
|
4366
4475
|
function promptDomainBlock(ctx, domain, signal) {
|
|
4367
|
-
return showPermissionPrompt(ctx, `Network blocked: "${domain}" is not in
|
|
4476
|
+
return showPermissionPrompt(ctx, `Network blocked: "${domain}" is not in the allowed domains list`, PERMISSION_OPTIONS, signal);
|
|
4368
4477
|
}
|
|
4369
4478
|
function promptReadBlock(ctx, filePath, reason, signal) {
|
|
4370
4479
|
const title = reason ? `Read blocked: "${filePath}" is in denyRead (${reason})` : `Read blocked: "${filePath}" is not in allowRead`;
|
|
@@ -4374,7 +4483,7 @@ function promptWriteBlock(ctx, filePath, signal) {
|
|
|
4374
4483
|
return showPermissionPrompt(ctx, `Write blocked: "${filePath}" is not in allowWrite`, PERMISSION_OPTIONS, signal);
|
|
4375
4484
|
}
|
|
4376
4485
|
function promptNetworkBlock(ctx, operation, target, signal) {
|
|
4377
|
-
return showPermissionPrompt(ctx, `Network blocked: ${operation}
|
|
4486
|
+
return showPermissionPrompt(ctx, `Network blocked: ${operation} "${target}"`, NETWORK_PERMISSION_OPTIONS, signal);
|
|
4378
4487
|
}
|
|
4379
4488
|
function landstripAvailable() {
|
|
4380
4489
|
try {
|
|
@@ -5656,82 +5765,62 @@ ${message}
|
|
|
5656
5765
|
handler: async (_args, ctx) => {
|
|
5657
5766
|
if (!ctx.hasUI)
|
|
5658
5767
|
return;
|
|
5659
|
-
const
|
|
5768
|
+
const trustContext = ctx;
|
|
5769
|
+
const projectTrusted = trustContext.isProjectTrusted?.() ?? projectConfigTrusted;
|
|
5770
|
+
const config = loadSandboxConfig(ctx.cwd, projectTrusted);
|
|
5660
5771
|
const { globalPath, projectPath } = getConfigPaths(ctx.cwd);
|
|
5661
5772
|
const windowsImplementation = process.platform === "win32" ? activeWindowsImplementation() : undefined;
|
|
5662
|
-
const
|
|
5663
|
-
|
|
5664
|
-
|
|
5773
|
+
const target = getSandboxConfigWriteTarget(ctx.cwd, projectTrusted);
|
|
5774
|
+
let tab = "Overview";
|
|
5775
|
+
const shouldToggle = await ctx.ui.custom((tui, theme, _kb, done) => {
|
|
5776
|
+
const { muted, accent, text } = themeColors(theme);
|
|
5777
|
+
const sandboxStatus = () => {
|
|
5665
5778
|
if (noSandboxFlag)
|
|
5666
|
-
return { color: "warning", label: "Disabled
|
|
5779
|
+
return { color: "warning", label: "Disabled by --no-sandbox" };
|
|
5667
5780
|
if (!config.enabled)
|
|
5668
|
-
return { color: "warning", label: "Disabled" };
|
|
5781
|
+
return { color: "warning", label: "Disabled by configuration" };
|
|
5669
5782
|
if (!sandboxEnabled || !sandboxReady)
|
|
5670
5783
|
return { color: "warning", label: "Inactive" };
|
|
5671
5784
|
return { color: "success", label: "Active" };
|
|
5672
|
-
}
|
|
5673
|
-
|
|
5674
|
-
return value ? theme.fg("warning", "yes") : theme.fg("success", "no");
|
|
5675
|
-
}
|
|
5785
|
+
};
|
|
5786
|
+
const networkMode = config.network.allowNetwork ? "Unrestricted" : windowsImplementation === "appContainer" && !config.windows.allowLoopback ? "Blocked" : "Proxied";
|
|
5676
5787
|
return {
|
|
5677
5788
|
render(width) {
|
|
5678
5789
|
const innerWidth = Math.max(1, width - 4);
|
|
5679
|
-
const row = (content = "") => boxRow(theme, width, content);
|
|
5680
|
-
const lines = [boxTop(theme, width, "Sandbox")];
|
|
5681
5790
|
const status = sandboxStatus();
|
|
5682
|
-
const
|
|
5683
|
-
const pathSnippet = text(truncateToWidth3(landstripDisplayPath(), Math.max(20, innerWidth - 28)));
|
|
5684
|
-
const section = (title, detail) => {
|
|
5685
|
-
lines.push(row(""));
|
|
5686
|
-
lines.push(row(`${accent(title)}${detail ? dim(` · ${detail}`) : ""}`));
|
|
5687
|
-
};
|
|
5688
|
-
const item = (label, value) => {
|
|
5689
|
-
lines.push(row(` ${dim("•")} ${muted(label.padEnd(13))} ${value}`));
|
|
5690
|
-
};
|
|
5791
|
+
const tabs = dialogTabs(theme, ["Overview", "Policy"], tab);
|
|
5691
5792
|
const listValue = (values) => {
|
|
5692
5793
|
const value = values.join(", ") || "none";
|
|
5693
|
-
return text(truncateToWidth3(value, Math.max(10, innerWidth -
|
|
5794
|
+
return text(truncateToWidth3(value, Math.max(10, innerWidth - 20)));
|
|
5694
5795
|
};
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
item("session", theme.fg("accent", sessionAllowedDomains.join(", ")));
|
|
5707
|
-
}
|
|
5708
|
-
section("Filesystem");
|
|
5709
|
-
item("deny read", listValue(config.filesystem.denyRead));
|
|
5710
|
-
item("allow read", listValue(config.filesystem.allowRead));
|
|
5711
|
-
item("allow write", listValue(config.filesystem.allowWrite));
|
|
5712
|
-
item("deny write", listValue(config.filesystem.denyWrite));
|
|
5713
|
-
if (windowsImplementation !== undefined) {
|
|
5714
|
-
section("Windows");
|
|
5715
|
-
item("implementation", text(windowsImplementation === "restrictedUser" ? "restricted user" : "AppContainer"));
|
|
5716
|
-
if (windowsImplementation === "appContainer") {
|
|
5717
|
-
const mode = config.windows.appContainerMode;
|
|
5718
|
-
item("container", mode === "standard" ? theme.fg("warning", `${mode} (weaker)`) : text(mode));
|
|
5719
|
-
item("loopback", boolValue(config.windows.allowLoopback));
|
|
5720
|
-
}
|
|
5721
|
-
}
|
|
5722
|
-
if (sessionAllowedReadPaths.length > 0 || sessionAllowedWritePaths.length > 0) {
|
|
5723
|
-
section("Session grants");
|
|
5724
|
-
if (sessionAllowedReadPaths.length > 0) {
|
|
5725
|
-
item("read", theme.fg("accent", sessionAllowedReadPaths.join(", ")));
|
|
5726
|
-
}
|
|
5727
|
-
if (sessionAllowedWritePaths.length > 0) {
|
|
5728
|
-
item("write", theme.fg("accent", sessionAllowedWritePaths.join(", ")));
|
|
5796
|
+
const item = (label, value) => ` ${muted(label.padEnd(16))} ${value}`;
|
|
5797
|
+
const footer = dialogKeys(theme, [
|
|
5798
|
+
["Tab", "next tab"],
|
|
5799
|
+
["Enter", `${config.enabled ? "disable" : "enable"} in ${target.scope}`],
|
|
5800
|
+
["Esc", "close"]
|
|
5801
|
+
]);
|
|
5802
|
+
const content = [tabs, ""];
|
|
5803
|
+
if (tab === "Overview") {
|
|
5804
|
+
content.push(`${theme.fg(status.color, "●")} ${text(status.label)}`, item("Configured", config.enabled ? text("Enabled") : theme.fg("warning", "Disabled")), item("Runtime", sandboxEnabled && sandboxReady ? theme.fg("success", "Running") : muted("Not running")), "", accent("Protection"), item("Network", text(networkMode)), item("Filesystem", text(`${config.filesystem.denyRead.length + config.filesystem.allowRead.length} read rules, ${config.filesystem.allowWrite.length + config.filesystem.denyWrite.length} write rules`)), item("Session grants", text(String(sessionAllowedDomains.length + sessionAllowedReadPaths.length + sessionAllowedWritePaths.length))), "", accent("Configuration"), item("Change scope", text(target.scope === "project" ? "Project" : "Global")), item("Change file", text(truncateToWidth3(target.path, Math.max(10, innerWidth - 20)))), item("Project file", text(truncateToWidth3(projectPath, Math.max(10, innerWidth - 20)))), item("Global file", text(truncateToWidth3(globalPath, Math.max(10, innerWidth - 20)))), item("Landstrip binary", text(truncateToWidth3(landstripDisplayPath(), Math.max(10, innerWidth - 20)))));
|
|
5805
|
+
if (windowsImplementation !== undefined) {
|
|
5806
|
+
content.push(item("Windows mode", text(windowsImplementation === "restrictedUser" ? "Restricted user" : `AppContainer (${config.windows.appContainerMode})`)));
|
|
5729
5807
|
}
|
|
5808
|
+
} else {
|
|
5809
|
+
content.push(accent(`Network · ${networkMode}`), item("Allowed domains", listValue(config.network.allowedDomains)), item("Denied domains", listValue(config.network.deniedDomains)), item("Unix sockets", config.network.allowAllUnixSockets ? text("all") : listValue(config.network.allowUnixSockets)), "", accent("Filesystem"), item("Denied reads", listValue(config.filesystem.denyRead)), item("Allowed reads", listValue(config.filesystem.allowRead)), item("Allowed writes", listValue(config.filesystem.allowWrite)), item("Denied writes", listValue(config.filesystem.denyWrite)), "", accent("Session grants"), item("Domains", listValue(sessionAllowedDomains)), item("Read paths", listValue(sessionAllowedReadPaths)), item("Write paths", listValue(sessionAllowedWritePaths)));
|
|
5730
5810
|
}
|
|
5731
|
-
|
|
5732
|
-
return
|
|
5811
|
+
content.push("", footer);
|
|
5812
|
+
return [
|
|
5813
|
+
boxTop(theme, width, "Sandbox"),
|
|
5814
|
+
...content.map((line) => boxRow(theme, width, line)),
|
|
5815
|
+
boxBottom(theme, width)
|
|
5816
|
+
];
|
|
5733
5817
|
},
|
|
5734
5818
|
handleInput(data) {
|
|
5819
|
+
if (matchesKey2(data, "tab")) {
|
|
5820
|
+
tab = tab === "Overview" ? "Policy" : "Overview";
|
|
5821
|
+
tui.requestRender();
|
|
5822
|
+
return;
|
|
5823
|
+
}
|
|
5735
5824
|
if (matchesKey2(data, "return")) {
|
|
5736
5825
|
done(true);
|
|
5737
5826
|
return;
|
|
@@ -5748,8 +5837,16 @@ ${message}
|
|
|
5748
5837
|
if (!shouldToggle)
|
|
5749
5838
|
return;
|
|
5750
5839
|
const enabled = !config.enabled;
|
|
5840
|
+
if (!enabled) {
|
|
5841
|
+
const confirmed = await ctx.ui.confirm("Disable sandbox?", `Commands will run without OS isolation.
|
|
5842
|
+
|
|
5843
|
+
Change: ${target.scope} configuration
|
|
5844
|
+
${target.path}`);
|
|
5845
|
+
if (!confirmed)
|
|
5846
|
+
return;
|
|
5847
|
+
}
|
|
5751
5848
|
try {
|
|
5752
|
-
const scope = await setSandboxConfigEnabled(ctx.cwd, enabled,
|
|
5849
|
+
const scope = await setSandboxConfigEnabled(ctx.cwd, enabled, projectTrusted);
|
|
5753
5850
|
if (!enabled) {
|
|
5754
5851
|
disableSandbox(ctx);
|
|
5755
5852
|
} else if (!noSandboxFlag) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-landstrip",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.8",
|
|
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.
|
|
42
|
+
"@landstrip/landstrip": "^0.18.8",
|
|
43
43
|
"jsonc-parser": "^3.3.1",
|
|
44
44
|
"minimatch": "^10.2.5",
|
|
45
45
|
"yaml": "^2.9.0"
|