pi-landstrip 0.19.18 → 0.19.20
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/README.md +5 -3
- package/dist/index.ts +574 -81
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -32,9 +32,11 @@ and platform limits, including Windows read grants and hard-denial restrictions.
|
|
|
32
32
|
|
|
33
33
|
Worker boundaries:
|
|
34
34
|
|
|
35
|
-
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
- Workers can read their startup files despite the bundled `denyRead` defaults.
|
|
36
|
+
If your own sandbox settings block a needed path with `denyRead`, Pi asks for
|
|
37
|
+
approval before starting the worker. Declining prevents startup. Existing
|
|
38
|
+
`allowRead` grants and session approvals are reused. `denyReadAlways` blocks
|
|
39
|
+
startup without asking.
|
|
38
40
|
- Hard read denials also apply to shell host reads. Private workers protect
|
|
39
41
|
parent auth-file/lock roots with `denyReadAlways` and `denyWrite`;
|
|
40
42
|
private worker authentication is unsupported on Windows.
|
package/dist/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { StringDecoder as StringDecoder3 } from "node:string_decoder";
|
|
|
17
17
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
18
18
|
import {
|
|
19
19
|
createBashToolDefinition,
|
|
20
|
+
createLocalBashOperations,
|
|
20
21
|
getAgentDir as getAgentDir5,
|
|
21
22
|
getShellConfig,
|
|
22
23
|
SettingsManager as SettingsManager2,
|
|
@@ -228,16 +229,19 @@ function serveWorkerAuth(pipe, resolve) {
|
|
|
228
229
|
// subagents.ts
|
|
229
230
|
import { randomUUID } from "node:crypto";
|
|
230
231
|
import {
|
|
232
|
+
closeSync,
|
|
231
233
|
existsSync as existsSync5,
|
|
232
234
|
lstatSync,
|
|
233
235
|
mkdirSync as mkdirSync3,
|
|
234
236
|
mkdtempSync,
|
|
237
|
+
openSync,
|
|
235
238
|
readFileSync as readFileSync6,
|
|
236
239
|
readdirSync as readdirSync2,
|
|
237
240
|
readlinkSync,
|
|
238
241
|
realpathSync as realpathSync2,
|
|
239
242
|
rmSync,
|
|
240
|
-
writeFileSync as writeFileSync3
|
|
243
|
+
writeFileSync as writeFileSync3,
|
|
244
|
+
writeSync
|
|
241
245
|
} from "node:fs";
|
|
242
246
|
import { homedir as homedir3, tmpdir } from "node:os";
|
|
243
247
|
import { basename as basename3, dirname as dirname4, isAbsolute as isAbsolute3, join as join7, relative as relative3, resolve as resolve3, sep as sep4 } from "node:path";
|
|
@@ -1457,12 +1461,15 @@ function parseArgv(args) {
|
|
|
1457
1461
|
};
|
|
1458
1462
|
}
|
|
1459
1463
|
function taskItems(tasks, prefix) {
|
|
1460
|
-
return tasks.map((task) =>
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1464
|
+
return tasks.map((task) => {
|
|
1465
|
+
const label = task.kind === "command" || task.agent === "bash" ? "$ bash" : `@${task.agent}`;
|
|
1466
|
+
return {
|
|
1467
|
+
value: `${prefix}${task.id}`,
|
|
1468
|
+
label: task.id.slice(0, 8),
|
|
1469
|
+
description: `${label} [${task.state}]`,
|
|
1470
|
+
search: task.id
|
|
1471
|
+
};
|
|
1472
|
+
});
|
|
1466
1473
|
}
|
|
1467
1474
|
function matchingTasksById(tasks, taskId) {
|
|
1468
1475
|
const exact = tasks.find((task) => task.id === taskId);
|
|
@@ -1580,22 +1587,46 @@ Scope: ${scope} configuration`);
|
|
|
1580
1587
|
{
|
|
1581
1588
|
name: "tasks",
|
|
1582
1589
|
summary: "Inspect and manage running task sessions",
|
|
1583
|
-
pane: (argv) =>
|
|
1590
|
+
pane: (argv) => {
|
|
1591
|
+
const first = argv.words[0]?.toLowerCase() ?? "";
|
|
1592
|
+
if (["cancel", "delete", "kill"].includes(first))
|
|
1593
|
+
return;
|
|
1594
|
+
if (first === "inspect" && argv.words[1])
|
|
1595
|
+
return argv.words[1];
|
|
1596
|
+
if (first && first !== "list")
|
|
1597
|
+
return first;
|
|
1598
|
+
return "tasks";
|
|
1599
|
+
},
|
|
1584
1600
|
complete: (argv, { runtime }) => {
|
|
1585
1601
|
const tasks = runtime?.getTasks() ?? [];
|
|
1586
|
-
|
|
1587
|
-
|
|
1602
|
+
const action = argv.words[0]?.toLowerCase();
|
|
1603
|
+
if (action === "cancel" || action === "kill")
|
|
1604
|
+
return taskItems(tasks, `tasks ${action} `);
|
|
1605
|
+
if (action === "delete")
|
|
1606
|
+
return taskItems(tasks, "tasks delete ");
|
|
1607
|
+
if (action === "inspect")
|
|
1608
|
+
return taskItems(tasks, "tasks inspect ");
|
|
1588
1609
|
return [
|
|
1589
|
-
{ value: "tasks
|
|
1590
|
-
{ value: "tasks
|
|
1610
|
+
{ value: "tasks cancel", label: "cancel", description: "Cancel an active task session" },
|
|
1611
|
+
{ value: "tasks delete", label: "delete", description: "Delete a task session by ID" },
|
|
1612
|
+
{ value: "tasks inspect", label: "inspect", description: "Inspect a task session in TUI" },
|
|
1591
1613
|
...taskItems(tasks, "tasks ")
|
|
1592
1614
|
];
|
|
1593
1615
|
},
|
|
1594
|
-
run: (argv, { ctx, runtime }) => {
|
|
1595
|
-
const [
|
|
1596
|
-
|
|
1616
|
+
run: async (argv, { ctx, runtime }) => {
|
|
1617
|
+
const [rawAction = "", rawTaskId = ""] = argv.words;
|
|
1618
|
+
let action = rawAction.toLowerCase();
|
|
1619
|
+
let taskId = rawTaskId;
|
|
1620
|
+
if (!["cancel", "delete", "kill", "inspect", "list"].includes(action) && action) {
|
|
1621
|
+
const matched = matchingTasksById(runtime.getTasks(), action);
|
|
1622
|
+
if (matched.length > 0) {
|
|
1623
|
+
action = "inspect";
|
|
1624
|
+
taskId = matched[0].id;
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
if (action === "cancel" || action === "kill") {
|
|
1597
1628
|
if (!taskId) {
|
|
1598
|
-
ctx.ui.notify("Task ID required to
|
|
1629
|
+
ctx.ui.notify("Task ID required to cancel a task", "error");
|
|
1599
1630
|
return;
|
|
1600
1631
|
}
|
|
1601
1632
|
const matched = matchingTasksById(runtime.getTasks(), taskId);
|
|
@@ -1607,17 +1638,36 @@ Scope: ${scope} configuration`);
|
|
|
1607
1638
|
ctx.ui.notify(`Task ID prefix ${taskId} matches ${matched.length} tasks`, "error");
|
|
1608
1639
|
return;
|
|
1609
1640
|
}
|
|
1610
|
-
const
|
|
1611
|
-
ctx.ui.notify(`
|
|
1641
|
+
const cancelled = await runtime.cancelTasks([matched[0].id], ctx);
|
|
1642
|
+
ctx.ui.notify(`Cancelled ${cancelled} task${cancelled === 1 ? "" : "s"}`, "info");
|
|
1612
1643
|
return;
|
|
1613
1644
|
}
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1645
|
+
if (action === "delete") {
|
|
1646
|
+
if (!taskId) {
|
|
1647
|
+
ctx.ui.notify("Task ID required to delete a task", "error");
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
const matched = matchingTasksById(runtime.getTasks(), taskId);
|
|
1651
|
+
if (matched.length === 0) {
|
|
1652
|
+
ctx.ui.notify(`No task found matching ${taskId}`, "error");
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1655
|
+
if (matched.length > 1) {
|
|
1656
|
+
ctx.ui.notify(`Task ID prefix ${taskId} matches ${matched.length} tasks`, "error");
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
const deleted = runtime.deleteTasks([matched[0].id], ctx);
|
|
1660
|
+
ctx.ui.notify(`Deleted ${deleted} task${deleted === 1 ? "" : "s"}`, "info");
|
|
1617
1661
|
return;
|
|
1618
1662
|
}
|
|
1619
|
-
|
|
1620
|
-
|
|
1663
|
+
if (action === "inspect" && taskId) {
|
|
1664
|
+
const matched = matchingTasksById(runtime.getTasks(), taskId);
|
|
1665
|
+
if (matched.length === 1) {
|
|
1666
|
+
await runtime.openLandstrip(matched[0].id, ctx);
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
await runtime.openLandstrip("tasks", ctx);
|
|
1621
1671
|
}
|
|
1622
1672
|
},
|
|
1623
1673
|
{
|
|
@@ -2485,6 +2535,7 @@ var AGENTS_HELP_ROWS = [
|
|
|
2485
2535
|
["Esc", "Back one level, or close from a top-level tab"],
|
|
2486
2536
|
["Ctrl+C", "Close"],
|
|
2487
2537
|
["Space", "Toggle agent enabled or select task"],
|
|
2538
|
+
["C", "Cancel running task or execution"],
|
|
2488
2539
|
["X", "Delete selected agent or task sessions"],
|
|
2489
2540
|
["E", "Edit project agent"],
|
|
2490
2541
|
["I", "Inherit global agent state"],
|
|
@@ -2596,6 +2647,14 @@ var taskParameters = Type.Object({
|
|
|
2596
2647
|
command: Type.Optional(Type.String({ description: "Originating command, if any" })),
|
|
2597
2648
|
background: Type.Optional(Type.Boolean({ description: "Return immediately and run in background" }))
|
|
2598
2649
|
});
|
|
2650
|
+
var TasksToolSchema = Type.Object({
|
|
2651
|
+
action: Type.Union([Type.Literal("list"), Type.Literal("inspect"), Type.Literal("cancel")], {
|
|
2652
|
+
description: 'Task management action: "list" to view task sessions, "inspect" for status/output, or "cancel" to stop an active task'
|
|
2653
|
+
}),
|
|
2654
|
+
task_id: Type.Optional(Type.String({
|
|
2655
|
+
description: 'Task ID or unique prefix (required for "inspect" and "cancel")'
|
|
2656
|
+
}))
|
|
2657
|
+
});
|
|
2599
2658
|
var TASK_STATES = new Set([
|
|
2600
2659
|
"queued",
|
|
2601
2660
|
"running",
|
|
@@ -2609,6 +2668,8 @@ function taskDetails(task, state = task.state) {
|
|
|
2609
2668
|
taskId: task.id,
|
|
2610
2669
|
state,
|
|
2611
2670
|
agent: task.agent,
|
|
2671
|
+
kind: task.kind ?? "subagent",
|
|
2672
|
+
command: task.command,
|
|
2612
2673
|
description: task.description,
|
|
2613
2674
|
background: task.background,
|
|
2614
2675
|
currentTool: task.currentTool,
|
|
@@ -2618,7 +2679,12 @@ function taskDetails(task, state = task.state) {
|
|
|
2618
2679
|
startedAt: task.startedAt,
|
|
2619
2680
|
finishedAt: task.finishedAt,
|
|
2620
2681
|
output: task.output,
|
|
2621
|
-
error: task.error
|
|
2682
|
+
error: task.error,
|
|
2683
|
+
outputFile: task.outputFile,
|
|
2684
|
+
errorFile: task.errorFile,
|
|
2685
|
+
exitCode: task.exitCode,
|
|
2686
|
+
parentTaskId: task.parentTaskId,
|
|
2687
|
+
depth: task.depth
|
|
2622
2688
|
};
|
|
2623
2689
|
}
|
|
2624
2690
|
function taskDuration(details) {
|
|
@@ -2808,6 +2874,12 @@ function taskProgress(task) {
|
|
|
2808
2874
|
if (task.state !== "running")
|
|
2809
2875
|
return [];
|
|
2810
2876
|
const progress = [];
|
|
2877
|
+
if (task.kind === "command") {
|
|
2878
|
+
const duration = taskDuration(taskDetails(task));
|
|
2879
|
+
if (duration)
|
|
2880
|
+
progress.push(duration);
|
|
2881
|
+
return progress;
|
|
2882
|
+
}
|
|
2811
2883
|
if (task.currentTool)
|
|
2812
2884
|
progress.push(`→ ${task.currentTool}`);
|
|
2813
2885
|
if (task.toolCalls !== undefined) {
|
|
@@ -2848,6 +2920,19 @@ function messageContentText(message) {
|
|
|
2848
2920
|
`);
|
|
2849
2921
|
}
|
|
2850
2922
|
function taskTranscript(task) {
|
|
2923
|
+
if (task.kind === "command") {
|
|
2924
|
+
if (task.outputFile) {
|
|
2925
|
+
try {
|
|
2926
|
+
const content = readFileSync6(task.outputFile, "utf8");
|
|
2927
|
+
const lines = content.split(`
|
|
2928
|
+
`);
|
|
2929
|
+
return lines.length > 0 && lines[0] !== "" ? lines : ["Command running... (no output yet)"];
|
|
2930
|
+
} catch {}
|
|
2931
|
+
}
|
|
2932
|
+
const fallback = task.error ?? task.output ?? "Command output is not available yet.";
|
|
2933
|
+
return fallback.split(`
|
|
2934
|
+
`);
|
|
2935
|
+
}
|
|
2851
2936
|
if (!task.sessionFile || !existsSync5(task.sessionFile)) {
|
|
2852
2937
|
const fallback = task.error ?? task.output ?? "Child session is not available yet.";
|
|
2853
2938
|
return fallback.split(`
|
|
@@ -3105,7 +3190,7 @@ ${requested.map(({ permission, resource }) => `${permission}: ${resource}`).join
|
|
|
3105
3190
|
}
|
|
3106
3191
|
}
|
|
3107
3192
|
function renderTaskResult(id, state, value) {
|
|
3108
|
-
const tag = state === "error" ? "task_error" : "task_result";
|
|
3193
|
+
const tag = state === "error" || state === "cancelled" || state === "interrupted" ? "task_error" : "task_result";
|
|
3109
3194
|
return `<task id="${id}" state="${state}">
|
|
3110
3195
|
<${tag}>
|
|
3111
3196
|
${value}
|
|
@@ -3401,6 +3486,8 @@ class SubagentRuntime {
|
|
|
3401
3486
|
primaryAgentSwitching = false;
|
|
3402
3487
|
shuttingDown = false;
|
|
3403
3488
|
activeSessionId;
|
|
3489
|
+
taskUpdateListeners = new Set;
|
|
3490
|
+
recentCompletions = new Map;
|
|
3404
3491
|
constructor(pi, integration, createWorker = (...args) => this.defaultWorker(...args), loadCatalog = loadAgentCatalog, permissionPrompts = new PermissionPromptCoordinator) {
|
|
3405
3492
|
this.pi = pi;
|
|
3406
3493
|
this.integration = integration;
|
|
@@ -3417,14 +3504,31 @@ class SubagentRuntime {
|
|
|
3417
3504
|
primaryAgentName() {
|
|
3418
3505
|
return this.primaryAgent?.name;
|
|
3419
3506
|
}
|
|
3507
|
+
onTaskUpdate(listener) {
|
|
3508
|
+
this.taskUpdateListeners.add(listener);
|
|
3509
|
+
return () => this.taskUpdateListeners.delete(listener);
|
|
3510
|
+
}
|
|
3511
|
+
notifyTaskUpdate() {
|
|
3512
|
+
for (const listener of this.taskUpdateListeners) {
|
|
3513
|
+
try {
|
|
3514
|
+
listener();
|
|
3515
|
+
} catch {}
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3420
3518
|
register() {
|
|
3421
3519
|
this.pi.registerTool(this.createTaskTool());
|
|
3520
|
+
this.pi.registerTool(this.createTasksTool());
|
|
3422
3521
|
registerLandstripCommands(this.pi, this, this.integration);
|
|
3423
3522
|
this.pi.registerShortcut("ctrl+shift+a", {
|
|
3424
3523
|
description: "Cycle to the next primary agent",
|
|
3425
3524
|
handler: async (ctx) => this.cyclePrimaryAgent(ctx)
|
|
3426
3525
|
});
|
|
3526
|
+
this.pi.registerShortcut("ctrl+shift+t", {
|
|
3527
|
+
description: "Open Landstrip task sessions dashboard",
|
|
3528
|
+
handler: async (ctx) => this.openLandstrip("tasks", ctx)
|
|
3529
|
+
});
|
|
3427
3530
|
this.pi.on("session_start", async (_event, ctx) => {
|
|
3531
|
+
this.recentCompletions.clear();
|
|
3428
3532
|
this.activeSessionId = undefined;
|
|
3429
3533
|
await this.dispose();
|
|
3430
3534
|
this.shuttingDown = false;
|
|
@@ -3439,6 +3543,10 @@ class SubagentRuntime {
|
|
|
3439
3543
|
if (nextTools.join("\x00") !== activeTools.join("\x00"))
|
|
3440
3544
|
this.pi.setActiveTools(nextTools);
|
|
3441
3545
|
});
|
|
3546
|
+
this.pi.on("turn_start", async (_event, ctx) => {
|
|
3547
|
+
this.recentCompletions.clear();
|
|
3548
|
+
this.updateTaskWidget(ctx);
|
|
3549
|
+
});
|
|
3442
3550
|
this.pi.on("session_before_switch", async (_event, ctx) => {
|
|
3443
3551
|
this.activeSessionId = undefined;
|
|
3444
3552
|
if (ctx.hasUI)
|
|
@@ -3784,6 +3892,14 @@ ${this.primaryAgent.prompt}` };
|
|
|
3784
3892
|
return settingsList;
|
|
3785
3893
|
};
|
|
3786
3894
|
return ctx.ui.custom((tui, theme, _keybindings, done) => {
|
|
3895
|
+
const unsubscribe = this.onTaskUpdate(() => {
|
|
3896
|
+
refreshTasks();
|
|
3897
|
+
tui.requestRender();
|
|
3898
|
+
});
|
|
3899
|
+
const finish = () => {
|
|
3900
|
+
unsubscribe();
|
|
3901
|
+
done(undefined);
|
|
3902
|
+
};
|
|
3787
3903
|
const renderAgentsTab = (frame) => {
|
|
3788
3904
|
const { tabs, pane, contentWidth, pad } = frame;
|
|
3789
3905
|
const listedAgents = agentsForTab();
|
|
@@ -4008,7 +4124,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
4008
4124
|
const shellReadMode = overview.windowsMode !== undefined ? "Policy (required)" : overview.shellReadMode === "host" ? "Host-aligned" : "Policy";
|
|
4009
4125
|
const changePath = overview.changeScope === "project" ? overview.paths.project : overview.paths.global;
|
|
4010
4126
|
const item = (label, value) => ` ${theme.fg("dim", label.padEnd(16))} ${value}`;
|
|
4011
|
-
lines.push(`${theme.fg(status.color, "●")} ${theme.fg("text", status.label)}`, item("Configured", overview.enabled ? theme.fg("text", "Enabled") : theme.fg("warning", "Disabled")), item("Runtime", overview.running ? theme.fg("success", "Running") : theme.fg("dim", "Not running")), "", theme.fg("accent", "Protection"), item("Network", networkMode), item("Shell reads", shellReadMode), item("Read rules", `${overview.config.filesystem.denyRead.length + overview.config.filesystem.allowRead.length} · ${overview.readRuleScope}`), item("Write rules", String(overview.config.filesystem.allowWrite.length + overview.config.filesystem.denyWrite.length)), item("Session grants", String(overview.sessionDomains.length + overview.sessionReadPaths.length + overview.sessionWritePaths.length)), "", theme.fg("accent", "Configuration"), item("Change scope", overview.changeScope === "project" ? "Project" : "Global"), item("Change file", truncateToWidth(changePath, Math.max(10, contentWidth - 20))), item("Project file", truncateToWidth(overview.paths.project, Math.max(10, contentWidth - 20))), item("Global file", truncateToWidth(overview.paths.global, Math.max(10, contentWidth - 20))), item("Landstrip binary", truncateToWidth(overview.paths.binary, Math.max(10, contentWidth - 20))));
|
|
4127
|
+
lines.push(`${theme.fg(status.color, "●")} ${theme.fg("text", status.label)}`, item("Configured", overview.enabled ? theme.fg("text", "Enabled") : theme.fg("warning", "Disabled")), item("Runtime", overview.running ? theme.fg("success", "Running") : theme.fg("dim", "Not running")), item("Tasks", `${tasks.filter((t) => t.state === "running").length} running · ${tasks.length} total`), "", theme.fg("accent", "Protection"), item("Network", networkMode), item("Shell reads", shellReadMode), item("Read rules", `${overview.config.filesystem.denyRead.length + overview.config.filesystem.allowRead.length} · ${overview.readRuleScope}`), item("Write rules", String(overview.config.filesystem.allowWrite.length + overview.config.filesystem.denyWrite.length)), item("Session grants", String(overview.sessionDomains.length + overview.sessionReadPaths.length + overview.sessionWritePaths.length)), "", theme.fg("accent", "Configuration"), item("Change scope", overview.changeScope === "project" ? "Project" : "Global"), item("Change file", truncateToWidth(changePath, Math.max(10, contentWidth - 20))), item("Project file", truncateToWidth(overview.paths.project, Math.max(10, contentWidth - 20))), item("Global file", truncateToWidth(overview.paths.global, Math.max(10, contentWidth - 20))), item("Landstrip binary", truncateToWidth(overview.paths.binary, Math.max(10, contentWidth - 20))));
|
|
4012
4128
|
if (overview.windowsMode !== undefined)
|
|
4013
4129
|
lines.push(item("Windows mode", overview.windowsMode));
|
|
4014
4130
|
lines.push("", theme.fg("accent", "Network"), item("Allowed domains", listValue(overview.config.network.allowedDomains)), item("Denied domains", listValue(overview.config.network.deniedDomains)), item("Unix sockets", overview.config.network.allowAllUnixSockets ? "all" : listValue(overview.config.network.allowUnixSockets)), "", theme.fg("accent", "Filesystem"), item("Shell reads", shellReadMode), item("Read rules for", overview.readRuleScope), item("Denied reads", listValue(overview.config.filesystem.denyRead)), item("Allowed reads", listValue(overview.config.filesystem.allowRead)), item("Allowed writes", listValue(overview.config.filesystem.allowWrite)), item("Denied writes", listValue(overview.config.filesystem.denyWrite)), "", theme.fg("accent", "Session grants"), item("Domains", listValue(overview.sessionDomains)), item("Read paths", listValue(overview.sessionReadPaths)), item("Write paths", listValue(overview.sessionWritePaths)));
|
|
@@ -4054,7 +4170,8 @@ ${this.primaryAgent.prompt}` };
|
|
|
4054
4170
|
const cursor = index === selectedTask ? theme.fg("accent", "›") : " ";
|
|
4055
4171
|
const selected = selectedTaskIds.has(task.id) ? theme.fg("accent", "✓") : " ";
|
|
4056
4172
|
const indent = " ".repeat(Math.max(0, task.depth - 1));
|
|
4057
|
-
|
|
4173
|
+
const tag = task.kind === "command" ? "$ bash" : `@${task.agent}`;
|
|
4174
|
+
listLines.push(`${cursor} ${selected} ${indent}${taskState(theme, task)} ${theme.fg("accent", tag)} ${theme.fg("text", task.description)} ${theme.fg("dim", task.id.slice(0, 8))}`);
|
|
4058
4175
|
}
|
|
4059
4176
|
if (status)
|
|
4060
4177
|
listLines.push(theme.fg("dim", ` ${status}`));
|
|
@@ -4066,7 +4183,8 @@ ${this.primaryAgent.prompt}` };
|
|
|
4066
4183
|
listLines.push("");
|
|
4067
4184
|
if (pendingTaskAction) {
|
|
4068
4185
|
const count = pendingTaskAction.taskIds.length;
|
|
4069
|
-
const
|
|
4186
|
+
const actionLabel = pendingTaskAction.type === "cancel" ? "Cancel" : "Delete";
|
|
4187
|
+
const label = count === 1 ? `${actionLabel} ${pendingTaskAction.taskIds[0]?.slice(0, 8) ?? "task"}?` : `${actionLabel} ${count} selected tasks?`;
|
|
4070
4188
|
listLines.push(`${theme.fg("warning", label)} ${dialogKeys(theme, [
|
|
4071
4189
|
["Enter", "confirm"],
|
|
4072
4190
|
["Esc", "cancel"]
|
|
@@ -4076,6 +4194,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
4076
4194
|
["↑ / ↓", "move"],
|
|
4077
4195
|
["Space", "select"],
|
|
4078
4196
|
["Enter", "inspect"],
|
|
4197
|
+
["C", "cancel"],
|
|
4079
4198
|
["X", "delete"],
|
|
4080
4199
|
["Esc", "close"]
|
|
4081
4200
|
]));
|
|
@@ -4098,10 +4217,11 @@ ${this.primaryAgent.prompt}` };
|
|
|
4098
4217
|
duration
|
|
4099
4218
|
].filter(Boolean);
|
|
4100
4219
|
const usage = formatTaskUsage(task.usage);
|
|
4220
|
+
const tag = task.kind === "command" ? "$ bash" : `@${task.agent}`;
|
|
4101
4221
|
const lines = [
|
|
4102
4222
|
tabs,
|
|
4103
4223
|
"",
|
|
4104
|
-
`${theme.fg("accent", theme.bold(
|
|
4224
|
+
`${theme.fg("accent", theme.bold(tag))} ${theme.fg("text", task.description)}`,
|
|
4105
4225
|
`${taskState(theme, task)} ${theme.fg("dim", metrics.join(" · "))} ${theme.fg("dim", "·")} ${theme.fg(follow ? "accent" : "muted", `Follow: ${follow ? "on" : "off"}`)}`,
|
|
4106
4226
|
...usage ? [theme.fg("dim", usage)] : [],
|
|
4107
4227
|
"",
|
|
@@ -4116,11 +4236,11 @@ ${this.primaryAgent.prompt}` };
|
|
|
4116
4236
|
const input = steering.render(Math.max(2, contentWidth - visibleWidth("Steer ")))[0] ?? "";
|
|
4117
4237
|
lines.push(`${theme.fg("accent", "Steer")} ${input.replace(/^>/, "›")}`);
|
|
4118
4238
|
} else if (task.state === "queued" || task.state === "running") {
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
["
|
|
4122
|
-
|
|
4123
|
-
|
|
4239
|
+
const keys = [];
|
|
4240
|
+
if (task.kind !== "command")
|
|
4241
|
+
keys.push(["Enter", "steer"]);
|
|
4242
|
+
keys.push(["C", "cancel"], ["F", "follow"], ["Esc", "tasks"]);
|
|
4243
|
+
lines.push(dialogKeys(theme, keys));
|
|
4124
4244
|
}
|
|
4125
4245
|
return pane(lines.map((line) => truncateToWidth(line, contentWidth)));
|
|
4126
4246
|
},
|
|
@@ -4149,7 +4269,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
4149
4269
|
return;
|
|
4150
4270
|
}
|
|
4151
4271
|
if (matchesKey(data, "escape")) {
|
|
4152
|
-
|
|
4272
|
+
finish();
|
|
4153
4273
|
return;
|
|
4154
4274
|
}
|
|
4155
4275
|
if (!matchesKey(data, "return"))
|
|
@@ -4196,10 +4316,20 @@ ${this.primaryAgent.prompt}` };
|
|
|
4196
4316
|
tasks: (data) => {
|
|
4197
4317
|
if (pendingTaskAction) {
|
|
4198
4318
|
if (matchesKey(data, "return")) {
|
|
4199
|
-
const
|
|
4319
|
+
const action = pendingTaskAction;
|
|
4200
4320
|
pendingTaskAction = undefined;
|
|
4201
|
-
|
|
4202
|
-
|
|
4321
|
+
if (action.type === "cancel") {
|
|
4322
|
+
this.cancelTasks(action.taskIds, ctx).then((cancelled) => {
|
|
4323
|
+
if (cancelled > 0) {
|
|
4324
|
+
ctx.ui.notify(`Cancelled ${cancelled} task${cancelled === 1 ? "" : "s"}`, "info");
|
|
4325
|
+
}
|
|
4326
|
+
refreshTasks();
|
|
4327
|
+
tui.requestRender();
|
|
4328
|
+
});
|
|
4329
|
+
return;
|
|
4330
|
+
}
|
|
4331
|
+
const deleted = this.deleteTasks(action.taskIds, ctx);
|
|
4332
|
+
for (const taskId of action.taskIds)
|
|
4203
4333
|
selectedTaskIds.delete(taskId);
|
|
4204
4334
|
refreshTasks();
|
|
4205
4335
|
if (deleted > 0) {
|
|
@@ -4215,7 +4345,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
4215
4345
|
return;
|
|
4216
4346
|
}
|
|
4217
4347
|
if (matchesKey(data, "escape"))
|
|
4218
|
-
|
|
4348
|
+
finish();
|
|
4219
4349
|
else if (matchesKey(data, "up"))
|
|
4220
4350
|
selectedTask = Math.max(0, selectedTask - 1);
|
|
4221
4351
|
else if (matchesKey(data, "down") && tasks.length > 0) {
|
|
@@ -4226,6 +4356,17 @@ ${this.primaryAgent.prompt}` };
|
|
|
4226
4356
|
return;
|
|
4227
4357
|
if (!selectedTaskIds.delete(task.id))
|
|
4228
4358
|
selectedTaskIds.add(task.id);
|
|
4359
|
+
} else if (data.toLowerCase() === "c") {
|
|
4360
|
+
const targets = taskTargets();
|
|
4361
|
+
const cancellable = targets.filter((t) => t.state === "running" || t.state === "queued");
|
|
4362
|
+
if (cancellable.length === 0) {
|
|
4363
|
+
ctx.ui.notify("No running or queued tasks selected to cancel", "warning");
|
|
4364
|
+
return;
|
|
4365
|
+
}
|
|
4366
|
+
pendingTaskAction = {
|
|
4367
|
+
type: "cancel",
|
|
4368
|
+
taskIds: cancellable.map((task) => task.id)
|
|
4369
|
+
};
|
|
4229
4370
|
} else if (data.toLowerCase() === "x") {
|
|
4230
4371
|
const targets = taskTargets();
|
|
4231
4372
|
if (targets.length === 0)
|
|
@@ -4247,13 +4388,22 @@ ${this.primaryAgent.prompt}` };
|
|
|
4247
4388
|
const task = tasks[selectedTask];
|
|
4248
4389
|
if (!task) {
|
|
4249
4390
|
if (matchesKey(data, "escape"))
|
|
4250
|
-
|
|
4391
|
+
finish();
|
|
4251
4392
|
return;
|
|
4252
4393
|
}
|
|
4253
4394
|
if (matchesKey(data, "escape")) {
|
|
4254
4395
|
tab = "tasks";
|
|
4255
4396
|
follow = false;
|
|
4256
4397
|
scroll = 0;
|
|
4398
|
+
} else if (data.toLowerCase() === "c" && (task.state === "running" || task.state === "queued")) {
|
|
4399
|
+
this.cancelTasks([task.id], ctx).then((count) => {
|
|
4400
|
+
if (count > 0) {
|
|
4401
|
+
ctx.ui.notify(`Cancelled task ${task.id.slice(0, 8)}`, "info");
|
|
4402
|
+
}
|
|
4403
|
+
refreshTasks();
|
|
4404
|
+
tui.requestRender();
|
|
4405
|
+
});
|
|
4406
|
+
return;
|
|
4257
4407
|
} else if (matchesKey(data, "return") && (task.state === "queued" || task.state === "running")) {
|
|
4258
4408
|
const input = new Input;
|
|
4259
4409
|
input.focused = true;
|
|
@@ -4314,7 +4464,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
4314
4464
|
},
|
|
4315
4465
|
help: (data) => {
|
|
4316
4466
|
if (matchesKey(data, "escape"))
|
|
4317
|
-
|
|
4467
|
+
finish();
|
|
4318
4468
|
return;
|
|
4319
4469
|
}
|
|
4320
4470
|
};
|
|
@@ -4408,7 +4558,9 @@ ${this.primaryAgent.prompt}` };
|
|
|
4408
4558
|
}
|
|
4409
4559
|
handleTabInput[tab](data);
|
|
4410
4560
|
},
|
|
4411
|
-
invalidate() {
|
|
4561
|
+
invalidate() {
|
|
4562
|
+
unsubscribe();
|
|
4563
|
+
}
|
|
4412
4564
|
};
|
|
4413
4565
|
}, {
|
|
4414
4566
|
overlay: true,
|
|
@@ -4460,6 +4612,199 @@ ${this.primaryAgent.prompt}` };
|
|
|
4460
4612
|
this.primaryAgentSwitching = false;
|
|
4461
4613
|
}
|
|
4462
4614
|
}
|
|
4615
|
+
createTasksTool(boundCtx) {
|
|
4616
|
+
return {
|
|
4617
|
+
name: "tasks",
|
|
4618
|
+
label: "Tasks",
|
|
4619
|
+
description: "Inspect, list, and cancel background tasks and subagents managed by Landstrip.",
|
|
4620
|
+
parameters: TasksToolSchema,
|
|
4621
|
+
executionMode: "parallel",
|
|
4622
|
+
renderCall: (input, theme) => {
|
|
4623
|
+
const action = input.action ?? "list";
|
|
4624
|
+
const target = input.task_id ? ` ${input.task_id.slice(0, 8)}` : "";
|
|
4625
|
+
return new Text(`${theme.fg("toolTitle", theme.bold("Tasks"))} ${theme.fg("accent", action)}${theme.fg("dim", target)}`, 0, 0);
|
|
4626
|
+
},
|
|
4627
|
+
execute: async (_toolCallId, input, _signal, _onUpdate, callCtx) => {
|
|
4628
|
+
const ctx = boundCtx ?? callCtx;
|
|
4629
|
+
if (!ctx)
|
|
4630
|
+
throw new Error("Task context is unavailable");
|
|
4631
|
+
const action = input.action;
|
|
4632
|
+
if (action === "list") {
|
|
4633
|
+
const list = [...this.tasks.values()].map((task) => {
|
|
4634
|
+
const details = taskDetails(task);
|
|
4635
|
+
return {
|
|
4636
|
+
id: details.taskId,
|
|
4637
|
+
kind: details.kind,
|
|
4638
|
+
agent: details.agent,
|
|
4639
|
+
state: details.state,
|
|
4640
|
+
command: details.command,
|
|
4641
|
+
description: details.description,
|
|
4642
|
+
duration: taskDuration(details),
|
|
4643
|
+
exitCode: details.exitCode
|
|
4644
|
+
};
|
|
4645
|
+
});
|
|
4646
|
+
return {
|
|
4647
|
+
content: [{ type: "text", text: JSON.stringify(list, null, 2) }],
|
|
4648
|
+
details: { count: list.length }
|
|
4649
|
+
};
|
|
4650
|
+
}
|
|
4651
|
+
if (action === "inspect") {
|
|
4652
|
+
if (!input.task_id)
|
|
4653
|
+
throw new Error("task_id is required for inspect action");
|
|
4654
|
+
const matched = matchingTasksById([...this.tasks.values()], input.task_id);
|
|
4655
|
+
if (matched.length === 0)
|
|
4656
|
+
throw new Error(`No task found matching prefix "${input.task_id}"`);
|
|
4657
|
+
if (matched.length > 1)
|
|
4658
|
+
throw new Error(`Task ID prefix "${input.task_id}" matches ${matched.length} tasks`);
|
|
4659
|
+
const task = matched[0];
|
|
4660
|
+
const details = taskDetails(task);
|
|
4661
|
+
const transcript = taskTranscript(task);
|
|
4662
|
+
const tail = transcript.slice(-100).join(`
|
|
4663
|
+
`);
|
|
4664
|
+
const header = task.kind === "command" ? `$ bash ${task.command ?? task.description}
|
|
4665
|
+
CWD: ${task.cwd}
|
|
4666
|
+
State: ${details.state}
|
|
4667
|
+
Duration: ${taskDuration(details) ?? "unknown"}
|
|
4668
|
+
Exit code: ${details.exitCode ?? "none"}` : `@${task.agent}: ${task.description}
|
|
4669
|
+
State: ${details.state}
|
|
4670
|
+
Duration: ${taskDuration(details) ?? "unknown"}
|
|
4671
|
+
Tool calls: ${task.toolCalls ?? 0}`;
|
|
4672
|
+
return {
|
|
4673
|
+
content: [{ type: "text", text: `${header}
|
|
4674
|
+
|
|
4675
|
+
Recent Output:
|
|
4676
|
+
${tail}` }],
|
|
4677
|
+
details
|
|
4678
|
+
};
|
|
4679
|
+
}
|
|
4680
|
+
if (action === "cancel") {
|
|
4681
|
+
if (!input.task_id)
|
|
4682
|
+
throw new Error("task_id is required for cancel action");
|
|
4683
|
+
const matched = matchingTasksById([...this.tasks.values()], input.task_id);
|
|
4684
|
+
if (matched.length === 0)
|
|
4685
|
+
throw new Error(`No task found matching prefix "${input.task_id}"`);
|
|
4686
|
+
if (matched.length > 1)
|
|
4687
|
+
throw new Error(`Task ID prefix "${input.task_id}" matches ${matched.length} tasks`);
|
|
4688
|
+
const count = await this.cancelTasks([matched[0].id], ctx);
|
|
4689
|
+
return {
|
|
4690
|
+
content: [
|
|
4691
|
+
{
|
|
4692
|
+
type: "text",
|
|
4693
|
+
text: `Cancelled task ${matched[0].id.slice(0, 8)} (${count} task cancelled)`
|
|
4694
|
+
}
|
|
4695
|
+
],
|
|
4696
|
+
details: { taskId: matched[0].id, cancelled: count > 0 }
|
|
4697
|
+
};
|
|
4698
|
+
}
|
|
4699
|
+
throw new Error(`Unknown action: ${action}`);
|
|
4700
|
+
}
|
|
4701
|
+
};
|
|
4702
|
+
}
|
|
4703
|
+
async runBackgroundShellTask(params, ctx) {
|
|
4704
|
+
const taskId = randomUUID();
|
|
4705
|
+
const sessionId = this.activeSessionId ?? ctx.sessionManager.getSessionId();
|
|
4706
|
+
const sessionDir = join7(getAgentDir4(), "sessions", "pi-landstrip", sessionId, taskId);
|
|
4707
|
+
mkdirSync3(sessionDir, { recursive: true });
|
|
4708
|
+
const outputFile = join7(sessionDir, "output.txt");
|
|
4709
|
+
const outputFd = openSync(outputFile, "w");
|
|
4710
|
+
const label = params.name?.trim() || truncateToWidth(params.command.trim(), 60);
|
|
4711
|
+
const task = {
|
|
4712
|
+
version: 1,
|
|
4713
|
+
id: taskId,
|
|
4714
|
+
kind: "command",
|
|
4715
|
+
agent: "bash",
|
|
4716
|
+
command: params.command,
|
|
4717
|
+
description: label,
|
|
4718
|
+
cwd: ctx.cwd,
|
|
4719
|
+
parentSessionId: sessionId,
|
|
4720
|
+
parentSessionFile: ctx.sessionManager.getSessionFile(),
|
|
4721
|
+
depth: 1,
|
|
4722
|
+
state: "running",
|
|
4723
|
+
startedAt: Date.now(),
|
|
4724
|
+
sessionDir,
|
|
4725
|
+
outputFile,
|
|
4726
|
+
background: true
|
|
4727
|
+
};
|
|
4728
|
+
this.tasks.set(taskId, task);
|
|
4729
|
+
this.persist(task);
|
|
4730
|
+
this.notifyTaskUpdate();
|
|
4731
|
+
this.updateTaskWidget(ctx);
|
|
4732
|
+
const controller = new AbortController;
|
|
4733
|
+
this.controllers.set(taskId, controller);
|
|
4734
|
+
(async () => {
|
|
4735
|
+
try {
|
|
4736
|
+
const result = await this.integration.runSandboxedCommand(params.command, ctx.cwd, {
|
|
4737
|
+
timeout: params.timeout,
|
|
4738
|
+
signal: controller.signal,
|
|
4739
|
+
promptOnBlock: false,
|
|
4740
|
+
onData: (chunk) => {
|
|
4741
|
+
try {
|
|
4742
|
+
writeSync(outputFd, chunk);
|
|
4743
|
+
} catch {}
|
|
4744
|
+
this.notifyTaskUpdate();
|
|
4745
|
+
}
|
|
4746
|
+
}, ctx);
|
|
4747
|
+
task.exitCode = result.exitCode;
|
|
4748
|
+
if (task.state !== "cancelled") {
|
|
4749
|
+
task.state = result.exitCode === 0 ? "completed" : "error";
|
|
4750
|
+
if (result.exitCode !== 0) {
|
|
4751
|
+
task.error = `Process exited with code ${result.exitCode}`;
|
|
4752
|
+
}
|
|
4753
|
+
}
|
|
4754
|
+
} catch (err) {
|
|
4755
|
+
if (task.state !== "cancelled") {
|
|
4756
|
+
task.state = "error";
|
|
4757
|
+
task.error = formatError(err);
|
|
4758
|
+
}
|
|
4759
|
+
} finally {
|
|
4760
|
+
try {
|
|
4761
|
+
closeSync(outputFd);
|
|
4762
|
+
} catch {}
|
|
4763
|
+
task.finishedAt = Date.now();
|
|
4764
|
+
this.controllers.delete(taskId);
|
|
4765
|
+
this.persist(task);
|
|
4766
|
+
const durationSec = taskDuration(taskDetails(task)) ?? "";
|
|
4767
|
+
const exitMsg = task.exitCode !== undefined && task.exitCode !== null ? task.exitCode === 0 ? "completed" : `failed (exit ${task.exitCode})` : task.state === "cancelled" ? "cancelled" : "error";
|
|
4768
|
+
this.recentCompletions.set(taskId, {
|
|
4769
|
+
label: task.description,
|
|
4770
|
+
status: task.state === "completed" ? "completed" : task.state === "cancelled" ? "cancelled" : "error",
|
|
4771
|
+
text: `$ bash ${task.description} ${exitMsg} (${durationSec})`,
|
|
4772
|
+
finishedAt: Date.now()
|
|
4773
|
+
});
|
|
4774
|
+
const commandOutput = task.outputFile && existsSync5(task.outputFile) ? readFileSync6(task.outputFile, "utf8").trim() : task.output ?? "";
|
|
4775
|
+
const deliveryText = `Background command ${task.state === "completed" ? "completed" : "failed"}: ${task.description}
|
|
4776
|
+
|
|
4777
|
+
${renderTaskResult(task.id, task.state, commandOutput || (task.error ?? ""))}`;
|
|
4778
|
+
const delivered = await this.deliverBackground(task, deliveryText);
|
|
4779
|
+
if (delivered) {
|
|
4780
|
+
task.delivered = true;
|
|
4781
|
+
this.persist(task);
|
|
4782
|
+
}
|
|
4783
|
+
if (task.state === "error") {
|
|
4784
|
+
ctx.ui.notify(`Background command failed: ${task.description}`, "error");
|
|
4785
|
+
}
|
|
4786
|
+
this.notifyTaskUpdate();
|
|
4787
|
+
this.updateTaskWidget(ctx);
|
|
4788
|
+
}
|
|
4789
|
+
})();
|
|
4790
|
+
return {
|
|
4791
|
+
content: [
|
|
4792
|
+
{
|
|
4793
|
+
type: "text",
|
|
4794
|
+
text: `Command launched in background.
|
|
4795
|
+
Task ID: ${taskId}
|
|
4796
|
+
Output file: ${outputFile}
|
|
4797
|
+
Use /landstrip tasks to view live progress or cancel.`
|
|
4798
|
+
}
|
|
4799
|
+
],
|
|
4800
|
+
details: {
|
|
4801
|
+
taskId,
|
|
4802
|
+
state: "running",
|
|
4803
|
+
description: label,
|
|
4804
|
+
outputFile
|
|
4805
|
+
}
|
|
4806
|
+
};
|
|
4807
|
+
}
|
|
4463
4808
|
createTaskTool(parentTask, callerRules, boundCtx) {
|
|
4464
4809
|
const cwd = boundCtx?.cwd ?? process.cwd();
|
|
4465
4810
|
const catalog = this.loadCatalog(cwd, getAgentDir4(), boundCtx ? isProjectTrusted(boundCtx) : false);
|
|
@@ -5198,6 +5543,13 @@ ${catalog.diagnostics.join(`
|
|
|
5198
5543
|
const output = await run;
|
|
5199
5544
|
if (this.shuttingDown || task.delivered || this.foregroundClaims.has(task.id))
|
|
5200
5545
|
return;
|
|
5546
|
+
const durationSec = taskDuration(taskDetails(task)) ?? "";
|
|
5547
|
+
this.recentCompletions.set(task.id, {
|
|
5548
|
+
label: task.description,
|
|
5549
|
+
status: "completed",
|
|
5550
|
+
text: `@${task.agent}: ${task.description} completed (${durationSec})`,
|
|
5551
|
+
finishedAt: Date.now()
|
|
5552
|
+
});
|
|
5201
5553
|
const delivered = await this.deliverBackground(task, `Background task completed: ${task.description}
|
|
5202
5554
|
|
|
5203
5555
|
${renderTaskResult(task.id, "completed", output)}`);
|
|
@@ -5205,10 +5557,18 @@ ${renderTaskResult(task.id, "completed", output)}`);
|
|
|
5205
5557
|
return;
|
|
5206
5558
|
task.delivered = true;
|
|
5207
5559
|
this.persist(task);
|
|
5560
|
+
this.notifyTaskUpdate();
|
|
5208
5561
|
} catch (error) {
|
|
5209
5562
|
if (this.shuttingDown || task.delivered || this.foregroundClaims.has(task.id))
|
|
5210
5563
|
return;
|
|
5211
5564
|
const message = formatError(error);
|
|
5565
|
+
const durationSec = taskDuration(taskDetails(task)) ?? "";
|
|
5566
|
+
this.recentCompletions.set(task.id, {
|
|
5567
|
+
label: task.description,
|
|
5568
|
+
status: task.state === "cancelled" ? "cancelled" : "error",
|
|
5569
|
+
text: `@${task.agent}: ${task.description} failed (${durationSec})`,
|
|
5570
|
+
finishedAt: Date.now()
|
|
5571
|
+
});
|
|
5212
5572
|
const delivered = await this.deliverBackground(task, `Background task failed: ${task.description}
|
|
5213
5573
|
|
|
5214
5574
|
${renderTaskResult(task.id, "error", message)}`);
|
|
@@ -5217,6 +5577,7 @@ ${renderTaskResult(task.id, "error", message)}`);
|
|
|
5217
5577
|
ctx.ui.notify(`Background task failed: ${task.description}`, "error");
|
|
5218
5578
|
task.delivered = true;
|
|
5219
5579
|
this.persist(task);
|
|
5580
|
+
this.notifyTaskUpdate();
|
|
5220
5581
|
}
|
|
5221
5582
|
}
|
|
5222
5583
|
async deliverBackground(task, content) {
|
|
@@ -5262,7 +5623,7 @@ ${renderTaskResult(task.id, "error", message)}`);
|
|
|
5262
5623
|
return;
|
|
5263
5624
|
const tasks = [...this.tasks.values()];
|
|
5264
5625
|
const active = tasks.filter((task) => task.state === "queued" || task.state === "running");
|
|
5265
|
-
if (active.length === 0) {
|
|
5626
|
+
if (active.length === 0 && this.recentCompletions.size === 0) {
|
|
5266
5627
|
ctx.ui.setWidget(TASK_WIDGET, undefined);
|
|
5267
5628
|
return;
|
|
5268
5629
|
}
|
|
@@ -5274,10 +5635,11 @@ ${renderTaskResult(task.id, "error", message)}`);
|
|
|
5274
5635
|
const summary = queued > 0 ? `${runningSummary} · ${queued} queued` : runningSummary;
|
|
5275
5636
|
ctx.ui.setWidget(TASK_WIDGET, (_tui, theme) => ({
|
|
5276
5637
|
render: (width) => {
|
|
5277
|
-
const header = theme.fg("accent", theme.bold("
|
|
5638
|
+
const header = theme.fg("accent", theme.bold("Tasks")) + theme.fg("dim", ` ${summary}`);
|
|
5278
5639
|
const tree = taskTreeLines(visible, (task) => {
|
|
5279
5640
|
const state = taskState(theme, task);
|
|
5280
|
-
const
|
|
5641
|
+
const tag = task.kind === "command" ? "$ bash" : `@${task.agent}`;
|
|
5642
|
+
const agent = theme.fg("accent", tag);
|
|
5281
5643
|
const record = byId.get(task.id);
|
|
5282
5644
|
const progress = taskProgress(record);
|
|
5283
5645
|
const suffix = progress.length > 0 ? theme.fg("dim", ` · ${progress.join(" · ")}`) : "";
|
|
@@ -5290,32 +5652,97 @@ ${renderTaskResult(task.id, "error", message)}`);
|
|
|
5290
5652
|
if (tree.length > shown.length) {
|
|
5291
5653
|
shown.push(theme.fg("dim", ` … ${tree.length - shown.length} more`));
|
|
5292
5654
|
}
|
|
5655
|
+
if (this.recentCompletions.size > 0 && shown.length < 8) {
|
|
5656
|
+
for (const comp of this.recentCompletions.values()) {
|
|
5657
|
+
if (shown.length >= 8)
|
|
5658
|
+
break;
|
|
5659
|
+
const icon = comp.status === "completed" ? theme.fg("success", "✓") : comp.status === "cancelled" ? theme.fg("muted", "⊘") : theme.fg("error", "✗");
|
|
5660
|
+
shown.push(` ${icon} ${theme.fg("dim", comp.text)}`);
|
|
5661
|
+
}
|
|
5662
|
+
}
|
|
5293
5663
|
return [header, ...shown].map((line) => truncateToWidth(line, Math.max(1, width)));
|
|
5294
5664
|
},
|
|
5295
5665
|
invalidate() {}
|
|
5296
5666
|
}));
|
|
5297
5667
|
}
|
|
5668
|
+
abortTaskProcess(taskId) {
|
|
5669
|
+
const running = this.running.get(taskId);
|
|
5670
|
+
if (running) {
|
|
5671
|
+
try {
|
|
5672
|
+
running.rpc.abort();
|
|
5673
|
+
} catch {}
|
|
5674
|
+
}
|
|
5675
|
+
const controller = this.controllers.get(taskId);
|
|
5676
|
+
if (controller) {
|
|
5677
|
+
controller.abort();
|
|
5678
|
+
this.controllers.delete(taskId);
|
|
5679
|
+
}
|
|
5680
|
+
}
|
|
5681
|
+
async cancelTasks(taskIds, ctx) {
|
|
5682
|
+
let cancelled = 0;
|
|
5683
|
+
const toCancel = new Set;
|
|
5684
|
+
const collectRecursive = (id) => {
|
|
5685
|
+
toCancel.add(id);
|
|
5686
|
+
for (const t of this.tasks.values()) {
|
|
5687
|
+
if (t.parentTaskId === id && !toCancel.has(t.id)) {
|
|
5688
|
+
collectRecursive(t.id);
|
|
5689
|
+
}
|
|
5690
|
+
}
|
|
5691
|
+
};
|
|
5692
|
+
for (const id of taskIds) {
|
|
5693
|
+
const task = this.tasks.get(id);
|
|
5694
|
+
if (task)
|
|
5695
|
+
collectRecursive(task.id);
|
|
5696
|
+
}
|
|
5697
|
+
for (const taskId of toCancel) {
|
|
5698
|
+
const task = this.tasks.get(taskId);
|
|
5699
|
+
if (!task)
|
|
5700
|
+
continue;
|
|
5701
|
+
if (task.state === "completed" || task.state === "error" || task.state === "cancelled") {
|
|
5702
|
+
continue;
|
|
5703
|
+
}
|
|
5704
|
+
task.state = "cancelled";
|
|
5705
|
+
task.finishedAt = Date.now();
|
|
5706
|
+
this.abortTaskProcess(taskId);
|
|
5707
|
+
this.persist(task);
|
|
5708
|
+
cancelled += 1;
|
|
5709
|
+
}
|
|
5710
|
+
if (cancelled > 0) {
|
|
5711
|
+
this.updateTaskWidget(ctx);
|
|
5712
|
+
this.notifyTaskUpdate();
|
|
5713
|
+
}
|
|
5714
|
+
return cancelled;
|
|
5715
|
+
}
|
|
5298
5716
|
deleteTasks(taskIds, ctx) {
|
|
5299
5717
|
let deleted = 0;
|
|
5300
5718
|
for (const taskId of taskIds) {
|
|
5301
5719
|
const task = this.tasks.get(taskId);
|
|
5302
5720
|
if (!task)
|
|
5303
5721
|
continue;
|
|
5722
|
+
if (task.state === "running" || task.state === "queued") {
|
|
5723
|
+
task.state = "cancelled";
|
|
5724
|
+
task.finishedAt = Date.now();
|
|
5725
|
+
this.abortTaskProcess(taskId);
|
|
5726
|
+
}
|
|
5304
5727
|
task.deleted = true;
|
|
5305
5728
|
task.delivered = true;
|
|
5306
|
-
this.controllers.get(taskId)?.abort();
|
|
5307
5729
|
if (task.sessionDir && existsSync5(task.sessionDir)) {
|
|
5308
|
-
|
|
5730
|
+
try {
|
|
5731
|
+
rmSync(task.sessionDir, { recursive: true, force: true });
|
|
5732
|
+
} catch {}
|
|
5309
5733
|
}
|
|
5310
5734
|
this.tasks.delete(taskId);
|
|
5311
5735
|
this.pendingPrompts.delete(taskId);
|
|
5312
5736
|
this.pendingSteers.delete(taskId);
|
|
5313
5737
|
this.foregroundClaims.delete(taskId);
|
|
5738
|
+
this.recentCompletions.delete(taskId);
|
|
5314
5739
|
this.persist(task);
|
|
5315
5740
|
deleted += 1;
|
|
5316
5741
|
}
|
|
5317
|
-
if (deleted > 0)
|
|
5742
|
+
if (deleted > 0) {
|
|
5318
5743
|
this.updateTaskWidget(ctx);
|
|
5744
|
+
this.notifyTaskUpdate();
|
|
5745
|
+
}
|
|
5319
5746
|
return deleted;
|
|
5320
5747
|
}
|
|
5321
5748
|
async cleanupOrphanSubagentSessions(ctx) {
|
|
@@ -5536,6 +5963,18 @@ function registerSubagents(pi, integration, permissionPrompts = new PermissionPr
|
|
|
5536
5963
|
}
|
|
5537
5964
|
|
|
5538
5965
|
// index.ts
|
|
5966
|
+
import { Type as Type2 } from "typebox";
|
|
5967
|
+
import { Text as Text2 } from "@earendil-works/pi-tui";
|
|
5968
|
+
var LandstripBashSchema = Type2.Object({
|
|
5969
|
+
command: Type2.String({ description: "Shell command to execute" }),
|
|
5970
|
+
timeout: Type2.Optional(Type2.Number({ description: "Timeout in seconds (optional, no default timeout)" })),
|
|
5971
|
+
background: Type2.Optional(Type2.Boolean({
|
|
5972
|
+
description: "Run command asynchronously in the background and return a task ID"
|
|
5973
|
+
})),
|
|
5974
|
+
name: Type2.Optional(Type2.String({
|
|
5975
|
+
description: "Concise label for the background command execution"
|
|
5976
|
+
}))
|
|
5977
|
+
});
|
|
5539
5978
|
function enforcesShellReadPolicy(shellReadAccess, platform = process.platform) {
|
|
5540
5979
|
return shellReadAccess !== "host" || platform === "win32";
|
|
5541
5980
|
}
|
|
@@ -5583,7 +6022,8 @@ function loadSandboxConfig(cwd, includeProject) {
|
|
|
5583
6022
|
const globalConfig = deepMerge(JSON.parse(readFileSync7(join8(packageDir2, "sandbox.json"), "utf-8")), globalOverrides);
|
|
5584
6023
|
return {
|
|
5585
6024
|
...deepMerge(globalConfig, projectOverrides),
|
|
5586
|
-
explicitDenyRead: mergeArray(globalOverrides.filesystem?.denyRead ?? [], projectOverrides.filesystem?.denyRead)
|
|
6025
|
+
explicitDenyRead: mergeArray(globalOverrides.filesystem?.denyRead ?? [], projectOverrides.filesystem?.denyRead),
|
|
6026
|
+
explicitAllowRead: mergeArray(globalOverrides.filesystem?.allowRead ?? [], projectOverrides.filesystem?.allowRead)
|
|
5587
6027
|
};
|
|
5588
6028
|
}
|
|
5589
6029
|
function windowsStatus() {
|
|
@@ -5927,7 +6367,8 @@ function pi_landstrip_default(pi) {
|
|
|
5927
6367
|
}
|
|
5928
6368
|
const permissionPrompts = new PermissionPromptCoordinator;
|
|
5929
6369
|
const integration = createLandstripIntegrationWithPrompts({}, permissionPrompts);
|
|
5930
|
-
registerSubagents(pi, integration, permissionPrompts);
|
|
6370
|
+
const subagentRuntime = registerSubagents(pi, integration, permissionPrompts);
|
|
6371
|
+
integration.setSubagentRuntime?.(subagentRuntime);
|
|
5931
6372
|
integration.register(pi);
|
|
5932
6373
|
}
|
|
5933
6374
|
function createLandstripIntegration(options = {}) {
|
|
@@ -5936,6 +6377,7 @@ function createLandstripIntegration(options = {}) {
|
|
|
5936
6377
|
function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
5937
6378
|
const shouldRegisterBashTool = options.registerBashTool ?? true;
|
|
5938
6379
|
const localCwd = options.cwd ?? process.cwd();
|
|
6380
|
+
let subagentRuntime = options.subagentRuntime;
|
|
5939
6381
|
let projectConfigTrusted = false;
|
|
5940
6382
|
function loadConfig(cwd) {
|
|
5941
6383
|
return loadSandboxConfig(cwd, projectConfigTrusted);
|
|
@@ -6135,9 +6577,13 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
6135
6577
|
if (scope !== filePath)
|
|
6136
6578
|
notify(ctx, `${verb} allowed (${choice}): ${scope}`, "info");
|
|
6137
6579
|
}
|
|
6138
|
-
async function applyReadChoice(ctx, choice, filePath, cwd, allowances) {
|
|
6580
|
+
async function applyReadChoice(ctx, choice, filePath, cwd, allowances, scope = sessionScopeFor(filePath, cwd)) {
|
|
6139
6581
|
const { globalPath, projectPath } = getPiConfigPaths(cwd, "sandbox.json");
|
|
6140
|
-
|
|
6582
|
+
if (allowances?.explicitDenyRead && !readAllowed(filePath, [scope], mergeArray(loadConfig(cwd).filesystem.denyRead, [
|
|
6583
|
+
...allowances.explicitDenyRead.patterns,
|
|
6584
|
+
...[...allowances.explicitDenyRead.globRoots.values()].flat()
|
|
6585
|
+
]), cwd))
|
|
6586
|
+
scope = filePath;
|
|
6141
6587
|
const configPath = choice === "project" ? projectPath : choice === "global" ? globalPath : undefined;
|
|
6142
6588
|
if (configPath) {
|
|
6143
6589
|
await addConfigValue(configPath, scope, (config) => config.filesystem?.allowRead ?? [], (config, allowRead) => {
|
|
@@ -6147,6 +6593,7 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
6147
6593
|
const target = choice === "once" ? allowances?.readPaths : sessionAllowedReadPaths;
|
|
6148
6594
|
if (target && !target.includes(scope))
|
|
6149
6595
|
target.push(scope);
|
|
6596
|
+
allowances?.explicitDenyRead?.approvedPaths.push(scope);
|
|
6150
6597
|
noteScope(ctx, "Read", choice, filePath, scope);
|
|
6151
6598
|
}
|
|
6152
6599
|
async function applyWriteChoice(ctx, choice, filePath, cwd, allowances) {
|
|
@@ -6291,8 +6738,8 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
|
|
|
6291
6738
|
});
|
|
6292
6739
|
});
|
|
6293
6740
|
}
|
|
6294
|
-
async function resolveTrapQuery(trap, ctx, cwd, allowances, promptOnBlock, signal) {
|
|
6295
|
-
if (
|
|
6741
|
+
async function resolveTrapQuery(trap, ctx, cwd, allowances, promptOnBlock, signal, readScope) {
|
|
6742
|
+
if (trap.kind !== "filesystem") {
|
|
6296
6743
|
const key = `${trap.operation}\x00${trap.target}`;
|
|
6297
6744
|
const current = () => sessionAllowedTargets.includes(key) || allowances.targets.includes(key) ? { action: "allow" } : undefined;
|
|
6298
6745
|
const existing = current();
|
|
@@ -6320,7 +6767,13 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
|
|
|
6320
6767
|
return { action: "deny", reason: "hard-deny" };
|
|
6321
6768
|
}
|
|
6322
6769
|
if (trap.operation === "read" && allowances.explicitDenyRead && matchesPattern(path, mergeArray(allowances.explicitDenyRead.patterns, config.explicitDenyRead), cwd, allowances.explicitDenyRead.globRoots)) {
|
|
6323
|
-
return
|
|
6770
|
+
return isPathReadAllowed(path, mergeArray(config.explicitAllowRead, [
|
|
6771
|
+
...sessionAllowedReadPaths,
|
|
6772
|
+
...allowances.explicitDenyRead.approvedPaths
|
|
6773
|
+
]), mergeArray(config.explicitDenyRead, [
|
|
6774
|
+
...allowances.explicitDenyRead.patterns,
|
|
6775
|
+
...[...allowances.explicitDenyRead.globRoots.values()].flat()
|
|
6776
|
+
]), cwd, { requireAllowMatch: true }) ? { action: "allow" } : undefined;
|
|
6324
6777
|
}
|
|
6325
6778
|
if (trap.operation === "write" && (matchesPattern(path, config.filesystem.denyWriteAlways, cwd) || matchesPattern(path, config.filesystem.denyWrite, cwd))) {
|
|
6326
6779
|
return { action: "deny", reason: "hard-deny" };
|
|
@@ -6339,7 +6792,7 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
|
|
|
6339
6792
|
if (choice === "abort" || promptSignal.aborted)
|
|
6340
6793
|
return { action: "deny", reason: "rejected" };
|
|
6341
6794
|
if (trap.operation === "read") {
|
|
6342
|
-
await applyReadChoice(ctx, choice, path, cwd, allowances);
|
|
6795
|
+
await applyReadChoice(ctx, choice, path, cwd, allowances, readScope);
|
|
6343
6796
|
} else {
|
|
6344
6797
|
await applyWriteChoice(ctx, choice, path, cwd, allowances);
|
|
6345
6798
|
}
|
|
@@ -6461,15 +6914,24 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
|
|
|
6461
6914
|
const explicitDenyGlobRoots = new Map;
|
|
6462
6915
|
processAllowances.explicitDenyRead = {
|
|
6463
6916
|
patterns: config.explicitDenyRead,
|
|
6464
|
-
globRoots: explicitDenyGlobRoots
|
|
6917
|
+
globRoots: explicitDenyGlobRoots,
|
|
6918
|
+
approvedPaths: []
|
|
6465
6919
|
};
|
|
6466
|
-
|
|
6920
|
+
const startupPaths = [
|
|
6921
|
+
...new Set([options.cwd, options.command, ...processAllowances.readPaths])
|
|
6922
|
+
];
|
|
6923
|
+
for (const path of startupPaths) {
|
|
6467
6924
|
if (/[*?[\]]/.test(path)) {
|
|
6468
6925
|
throw new Error(`Worker startup read path "${path}" must be a concrete path, not a glob`);
|
|
6469
6926
|
}
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6927
|
+
if (matchesPattern(path, mergeArray(config.filesystem.denyReadAlways, processAllowances.protectedPaths), options.cwd)) {
|
|
6928
|
+
throw new Error(`Worker startup read path "${path}" is denied by sandbox policy`);
|
|
6929
|
+
}
|
|
6930
|
+
}
|
|
6931
|
+
for (const path of startupPaths) {
|
|
6932
|
+
const result = await resolveTrapQuery({ kind: "filesystem", operation: "read", path }, options.ctx, options.cwd, processAllowances, true, options.signal, normalizeBlockedPath(path, options.cwd));
|
|
6933
|
+
if (result.action !== "allow") {
|
|
6934
|
+
throw new Error(`Worker startup read access denied: ${path} (${result.reason})`);
|
|
6473
6935
|
}
|
|
6474
6936
|
}
|
|
6475
6937
|
const { token: proxyToken, authorization: proxyAuthorization } = createProxyCredentials();
|
|
@@ -6527,14 +6989,7 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
|
|
|
6527
6989
|
if (spawned)
|
|
6528
6990
|
throw new Error("Prepared process can only be spawned once");
|
|
6529
6991
|
spawned = true;
|
|
6530
|
-
const landstripArgs = [
|
|
6531
|
-
"run",
|
|
6532
|
-
"-p",
|
|
6533
|
-
workerPolicy.path,
|
|
6534
|
-
"--",
|
|
6535
|
-
options.command,
|
|
6536
|
-
...options.args
|
|
6537
|
-
];
|
|
6992
|
+
const landstripArgs = ["-p", workerPolicy.path, "--", options.command, ...options.args];
|
|
6538
6993
|
const authPipe = Array.isArray(spawnOptions.stdio) && spawnOptions.stdio[WORKER_AUTH_FD] === "pipe";
|
|
6539
6994
|
if (authPipe && process.platform === "win32") {
|
|
6540
6995
|
throw new Error("Sandboxed worker authentication requires inherited Unix file descriptors");
|
|
@@ -6544,10 +6999,10 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
|
|
|
6544
6999
|
stdio.push(workerChildEnd ?? "ignore");
|
|
6545
7000
|
if (authPipe) {
|
|
6546
7001
|
stdio.push("pipe");
|
|
6547
|
-
landstripArgs.
|
|
7002
|
+
landstripArgs.unshift("--inherit", String(WORKER_AUTH_FD));
|
|
6548
7003
|
}
|
|
6549
7004
|
if (workerChildEnd)
|
|
6550
|
-
landstripArgs.
|
|
7005
|
+
landstripArgs.unshift("--trap", "3");
|
|
6551
7006
|
const child = spawn2(binaryPath(), landstripArgs, {
|
|
6552
7007
|
...spawnOptions,
|
|
6553
7008
|
cwd: options.cwd,
|
|
@@ -6755,16 +7210,9 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
|
|
|
6755
7210
|
};
|
|
6756
7211
|
processAllowances = withWindowsProcessReadAccess(processAllowances, invocation.executable, cwd);
|
|
6757
7212
|
policy = writePolicyFile(cwd, proxy?.port ?? null, "shell", processAllowances);
|
|
6758
|
-
landstripArgs = [
|
|
6759
|
-
"run",
|
|
6760
|
-
"-p",
|
|
6761
|
-
policy.path,
|
|
6762
|
-
"--",
|
|
6763
|
-
invocation.executable,
|
|
6764
|
-
...invocation.args
|
|
6765
|
-
];
|
|
7213
|
+
landstripArgs = ["-p", policy.path, "--", invocation.executable, ...invocation.args];
|
|
6766
7214
|
if (process.platform !== "win32")
|
|
6767
|
-
landstripArgs.
|
|
7215
|
+
landstripArgs.unshift("--trap", "3");
|
|
6768
7216
|
} catch (error) {
|
|
6769
7217
|
await teardownResources();
|
|
6770
7218
|
throw error;
|
|
@@ -7194,13 +7642,45 @@ ${message}
|
|
|
7194
7642
|
const localBash = createBashToolDefinition(cwd, {
|
|
7195
7643
|
shellPath: SettingsManager2.create(cwd).getShellPath()
|
|
7196
7644
|
});
|
|
7645
|
+
const baseRenderCall = localBash.renderCall;
|
|
7646
|
+
const baseRenderResult = localBash.renderResult;
|
|
7197
7647
|
return {
|
|
7198
7648
|
...localBash,
|
|
7199
7649
|
label: "bash (landstrip)",
|
|
7200
7650
|
description: "Execute commands with the active Landstrip shell provider.",
|
|
7201
|
-
|
|
7651
|
+
parameters: LandstripBashSchema,
|
|
7652
|
+
renderCall(args, theme, context) {
|
|
7653
|
+
const bashArgs = args;
|
|
7654
|
+
if (bashArgs.background) {
|
|
7655
|
+
const nameSuffix = bashArgs.name ? theme.fg("dim", ` (${bashArgs.name})`) : "";
|
|
7656
|
+
return new Text2(theme.fg("accent", "$ " + bashArgs.command) + theme.fg("muted", " [bg]") + nameSuffix, 0, 0);
|
|
7657
|
+
}
|
|
7658
|
+
return baseRenderCall ? baseRenderCall(args, theme, context) : new Text2(args.command, 0, 0);
|
|
7659
|
+
},
|
|
7660
|
+
renderResult(result, options, theme, context) {
|
|
7661
|
+
const details = result.details;
|
|
7662
|
+
if (details?.taskId) {
|
|
7663
|
+
const text = result.content[0]?.type === "text" ? result.content[0].text : "";
|
|
7664
|
+
return new Text2(theme.fg("success", `✓ Task ${details.taskId.slice(0, 8)} started`) + `
|
|
7665
|
+
` + theme.fg("dim", text), 0, 0);
|
|
7666
|
+
}
|
|
7667
|
+
return baseRenderResult ? baseRenderResult(result, options, theme, context) : new Text2("", 0, 0);
|
|
7668
|
+
},
|
|
7669
|
+
async execute(id, rawParams, signal, onUpdate, ctx) {
|
|
7202
7670
|
if (!ctx)
|
|
7203
7671
|
throw new Error("Sandbox context is unavailable");
|
|
7672
|
+
const params = rawParams;
|
|
7673
|
+
if (params.background) {
|
|
7674
|
+
if (!ensureSandboxState(ctx)) {
|
|
7675
|
+
if (sandboxState === "unavailable") {
|
|
7676
|
+
throw new Error("Sandbox is unavailable; refusing command");
|
|
7677
|
+
}
|
|
7678
|
+
}
|
|
7679
|
+
if (!subagentRuntime) {
|
|
7680
|
+
throw new Error("Background task runtime is unavailable");
|
|
7681
|
+
}
|
|
7682
|
+
return subagentRuntime.runBackgroundShellTask(params, ctx);
|
|
7683
|
+
}
|
|
7204
7684
|
if (!ensureSandboxState(ctx)) {
|
|
7205
7685
|
if (sandboxState === "unavailable") {
|
|
7206
7686
|
throw new Error("Sandbox is unavailable; refusing command");
|
|
@@ -7328,11 +7808,24 @@ ${message}
|
|
|
7328
7808
|
on,
|
|
7329
7809
|
register,
|
|
7330
7810
|
emit: emitEvent,
|
|
7811
|
+
runSandboxedCommand(command, cwd, execOptions, ctx) {
|
|
7812
|
+
const ops = ensureSandboxState(ctx) ? createLandstripBashOps(ctx, { promptOnBlock: execOptions.promptOnBlock ?? false }) : externalShellProvider !== undefined ? createProviderBashOps() : createLocalBashOperations();
|
|
7813
|
+
return ops.exec(command, cwd, {
|
|
7814
|
+
onData: execOptions.onData ?? (() => {}),
|
|
7815
|
+
signal: execOptions.signal,
|
|
7816
|
+
timeout: execOptions.timeout,
|
|
7817
|
+
env: execOptions.env
|
|
7818
|
+
});
|
|
7819
|
+
},
|
|
7820
|
+
setSubagentRuntime(runtime) {
|
|
7821
|
+
subagentRuntime = runtime;
|
|
7822
|
+
},
|
|
7331
7823
|
sandboxCallbacks
|
|
7332
7824
|
};
|
|
7333
7825
|
return integration;
|
|
7334
7826
|
}
|
|
7335
7827
|
export {
|
|
7828
|
+
LandstripBashSchema,
|
|
7336
7829
|
POSIX_SHELL_PROVIDER_ID,
|
|
7337
7830
|
controlResponseLine,
|
|
7338
7831
|
createLandstripIntegration,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-landstrip",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.20",
|
|
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-api": "^0.19.
|
|
42
|
+
"@landstrip/landstrip-api": "^0.19.20",
|
|
43
43
|
"jsonc-parser": "^3.3.1",
|
|
44
44
|
"minimatch": "^10.2.5",
|
|
45
45
|
"undici": "^8.9.0",
|