pi-landstrip 0.20.3 → 0.20.5
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 +1 -1
- package/dist/index.ts +116 -176
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ and `/landstrip-subagents` for subagent/task management (`Tasks`, `Log`, `Subage
|
|
|
65
65
|
`Primary`, `Settings`, `Help`). Task IDs accept unique prefixes; bare IDs open logs.
|
|
66
66
|
`/landstrip-subagents tasks kill <id>` terminates a task. `Ctrl+Shift+A` cycles
|
|
67
67
|
primary agents while idle. The `task` tool accepts `background: true`, `include_context: true`,
|
|
68
|
-
and `task_id` to continue a saved task; the `tasks` tool supports `list`, `inspect`,
|
|
68
|
+
and `task_id` to continue a saved task; the `tasks` tool supports `list`, `inspect`,
|
|
69
69
|
`steer`, and `cancel`. Press `S` in `Tasks` or `Enter` in running/queued logs to steer workers.
|
|
70
70
|
|
|
71
71
|
### Worker environment and authentication
|
package/dist/index.ts
CHANGED
|
@@ -2711,23 +2711,25 @@ var taskParameters = Type.Object({
|
|
|
2711
2711
|
subagent_type: Type.String({ description: "Subagent name" }),
|
|
2712
2712
|
task_id: Type.Optional(Type.String({ description: "Task ID to resume" })),
|
|
2713
2713
|
command: Type.Optional(Type.String({ description: "Originating command, if any" })),
|
|
2714
|
-
background: Type.Optional(Type.Boolean({
|
|
2714
|
+
background: Type.Optional(Type.Boolean({
|
|
2715
|
+
description: "Return immediately and run in background; completion automatically triggers a follow-up turn"
|
|
2716
|
+
})),
|
|
2715
2717
|
include_context: Type.Optional(Type.Boolean({
|
|
2716
2718
|
description: "Seed the subagent prompt with a bounded summary of recent parent conversation"
|
|
2717
2719
|
}))
|
|
2718
2720
|
});
|
|
2721
|
+
var BACKGROUND_LAUNCH_GUIDANCE = [
|
|
2722
|
+
"Terminal notification: enabled.",
|
|
2723
|
+
"Automatic follow-up turn: enabled.",
|
|
2724
|
+
"Next action: do not poll or sleep to wait; continue only independent useful work, otherwise end this turn and wait for the completion notification."
|
|
2725
|
+
].join(`
|
|
2726
|
+
`);
|
|
2719
2727
|
var TasksToolSchema = Type.Object({
|
|
2720
|
-
action: Type.Union([
|
|
2721
|
-
|
|
2722
|
-
Type.Literal("inspect"),
|
|
2723
|
-
Type.Literal("wait"),
|
|
2724
|
-
Type.Literal("steer"),
|
|
2725
|
-
Type.Literal("cancel")
|
|
2726
|
-
], {
|
|
2727
|
-
description: 'Task management action: "list" to view task sessions, "inspect" for status/output, "wait" to await completion, "steer" to guide a running subagent, or "cancel" to stop an active task'
|
|
2728
|
+
action: Type.Union([Type.Literal("list"), Type.Literal("inspect"), Type.Literal("steer"), Type.Literal("cancel")], {
|
|
2729
|
+
description: 'Task management action: "list" to view task sessions, "inspect" for point-in-time status/output, "steer" to guide a running subagent, or "cancel" to stop an active task'
|
|
2728
2730
|
}),
|
|
2729
2731
|
task_id: Type.Optional(Type.String({
|
|
2730
|
-
description: 'Task ID or unique prefix (required for "inspect", "
|
|
2732
|
+
description: 'Task ID or unique prefix (required for "inspect", "steer", and "cancel")'
|
|
2731
2733
|
})),
|
|
2732
2734
|
mode: Type.Optional(Type.Union([Type.Literal("all"), Type.Literal("background"), Type.Literal("foreground")], {
|
|
2733
2735
|
description: 'Optional execution mode filter for "list": "all", "background", or "foreground"'
|
|
@@ -2745,17 +2747,12 @@ var TasksToolSchema = Type.Object({
|
|
|
2745
2747
|
lines: Type.Optional(Type.Integer({
|
|
2746
2748
|
minimum: 1,
|
|
2747
2749
|
maximum: 500,
|
|
2748
|
-
description: 'Number of transcript lines to return for "inspect"
|
|
2750
|
+
description: 'Number of transcript lines to return for "inspect" (default 100)'
|
|
2749
2751
|
})),
|
|
2750
2752
|
offset: Type.Optional(Type.Integer({
|
|
2751
2753
|
minimum: 0,
|
|
2752
2754
|
description: '0-indexed starting line offset for "inspect" (omit for tail)'
|
|
2753
2755
|
})),
|
|
2754
|
-
timeout_ms: Type.Optional(Type.Integer({
|
|
2755
|
-
minimum: 1,
|
|
2756
|
-
maximum: 600000,
|
|
2757
|
-
description: 'Optional timeout in milliseconds for "wait"'
|
|
2758
|
-
})),
|
|
2759
2756
|
message: Type.Optional(Type.String({
|
|
2760
2757
|
description: 'Steering message to send to a running subagent (required for "steer")'
|
|
2761
2758
|
}))
|
|
@@ -3537,6 +3534,10 @@ function registerSubagentWorker(pi, config) {
|
|
|
3537
3534
|
name: "task",
|
|
3538
3535
|
label: "Task",
|
|
3539
3536
|
description: "Delegate a task to a Pi subagent process.",
|
|
3537
|
+
promptSnippet: "Delegate a task to a sandboxed Pi subagent process",
|
|
3538
|
+
promptGuidelines: [
|
|
3539
|
+
"After launching a task with background: true, continue only independent work that does not wait on the subagent; otherwise end the current turn and wait for the automatic completion follow-up turn. Do not poll tasks to wait."
|
|
3540
|
+
],
|
|
3540
3541
|
parameters: taskParameters,
|
|
3541
3542
|
executionMode: "parallel",
|
|
3542
3543
|
async execute(_id, input, signal, _onUpdate, ctx) {
|
|
@@ -3964,6 +3965,32 @@ ${this.primaryAgent.prompt}` };
|
|
|
3964
3965
|
unsubscribe();
|
|
3965
3966
|
done(undefined);
|
|
3966
3967
|
};
|
|
3968
|
+
const runSaving = (errorPrefix, action) => {
|
|
3969
|
+
saving = true;
|
|
3970
|
+
action().catch((error) => ctx.ui.notify(`${errorPrefix}: ${formatError(error)}`, "error")).finally(() => {
|
|
3971
|
+
saving = false;
|
|
3972
|
+
tui.requestRender();
|
|
3973
|
+
});
|
|
3974
|
+
};
|
|
3975
|
+
const startSteering = (task) => {
|
|
3976
|
+
const input = new Input;
|
|
3977
|
+
input.focused = true;
|
|
3978
|
+
input.onEscape = () => {
|
|
3979
|
+
steering = undefined;
|
|
3980
|
+
tui.requestRender();
|
|
3981
|
+
};
|
|
3982
|
+
input.onSubmit = (value) => {
|
|
3983
|
+
const message = value.trim();
|
|
3984
|
+
if (!message) {
|
|
3985
|
+
ctx.ui.notify("Steering message must not be empty", "warning");
|
|
3986
|
+
return;
|
|
3987
|
+
}
|
|
3988
|
+
steering = undefined;
|
|
3989
|
+
this.steerTask(task, message).then(() => ctx.ui.notify(`Steered task ${task.id.slice(0, 8)}`, "info")).catch((error) => ctx.ui.notify(`Could not steer task: ${formatError(error)}`, "error")).finally(() => tui.requestRender());
|
|
3990
|
+
tui.requestRender();
|
|
3991
|
+
};
|
|
3992
|
+
steering = input;
|
|
3993
|
+
};
|
|
3967
3994
|
const renderAgentsTab = (frame) => {
|
|
3968
3995
|
const { tabs, pane, contentWidth, pad } = frame;
|
|
3969
3996
|
const listedAgents = agentsForTab();
|
|
@@ -4118,8 +4145,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
4118
4145
|
ctx.ui.notify("Agent editing requires a trusted project", "warning");
|
|
4119
4146
|
return;
|
|
4120
4147
|
}
|
|
4121
|
-
|
|
4122
|
-
(async () => {
|
|
4148
|
+
runSaving("Could not edit agent", async () => {
|
|
4123
4149
|
const document = prepareProjectAgentEditor(ctx.cwd, agent);
|
|
4124
4150
|
const edited = await ctx.ui.editor(document.title, document.content);
|
|
4125
4151
|
if (edited === undefined)
|
|
@@ -4127,9 +4153,6 @@ ${this.primaryAgent.prompt}` };
|
|
|
4127
4153
|
await document.save(edited);
|
|
4128
4154
|
await refreshAgentRuntime(agent.name);
|
|
4129
4155
|
ctx.ui.notify(`Updated project agent ${agent.name}`, "info");
|
|
4130
|
-
})().catch((error) => ctx.ui.notify(`Could not edit agent: ${formatError(error)}`, "error")).finally(() => {
|
|
4131
|
-
saving = false;
|
|
4132
|
-
tui.requestRender();
|
|
4133
4156
|
});
|
|
4134
4157
|
return;
|
|
4135
4158
|
} else if (data === " " && agent && !saving) {
|
|
@@ -4138,13 +4161,10 @@ ${this.primaryAgent.prompt}` };
|
|
|
4138
4161
|
return;
|
|
4139
4162
|
}
|
|
4140
4163
|
const disabled = !agent.disabled;
|
|
4141
|
-
|
|
4142
|
-
|
|
4164
|
+
runSaving("Could not update agent", async () => {
|
|
4165
|
+
await setAgentDisabledForScope(ctx.cwd, agent.name, disabled, "project");
|
|
4143
4166
|
await refreshAgentRuntime(agent.name);
|
|
4144
4167
|
ctx.ui.notify(`${agent.name} ${disabled ? "disabled" : "enabled"} in project settings`, "info");
|
|
4145
|
-
}).catch((error) => ctx.ui.notify(`Could not update agent: ${formatError(error)}`, "error")).finally(() => {
|
|
4146
|
-
saving = false;
|
|
4147
|
-
tui.requestRender();
|
|
4148
4168
|
});
|
|
4149
4169
|
return;
|
|
4150
4170
|
} else if (data.toLowerCase() === "i" && agent && !saving) {
|
|
@@ -4154,13 +4174,10 @@ ${this.primaryAgent.prompt}` };
|
|
|
4154
4174
|
}
|
|
4155
4175
|
if (!disabledOverrides.project.has(agent.name))
|
|
4156
4176
|
return;
|
|
4157
|
-
|
|
4158
|
-
|
|
4177
|
+
runSaving("Could not update agent", async () => {
|
|
4178
|
+
await clearAgentDisabledForScope(ctx.cwd, agent.name, "project");
|
|
4159
4179
|
await refreshAgentRuntime(agent.name);
|
|
4160
4180
|
ctx.ui.notify(`${agent.name} now inherits its global state`, "info");
|
|
4161
|
-
}).catch((error) => ctx.ui.notify(`Could not update agent: ${formatError(error)}`, "error")).finally(() => {
|
|
4162
|
-
saving = false;
|
|
4163
|
-
tui.requestRender();
|
|
4164
4181
|
});
|
|
4165
4182
|
return;
|
|
4166
4183
|
} else if (tab === "primary" && matchesKey(data, "return") && agent && !agent.disabled && isPrimaryAgent(agent)) {
|
|
@@ -4218,16 +4235,16 @@ ${this.primaryAgent.prompt}` };
|
|
|
4218
4235
|
...footer
|
|
4219
4236
|
]);
|
|
4220
4237
|
};
|
|
4238
|
+
const item = (label, value) => ` ${theme.fg("dim", label.padEnd(20))} ${value}`;
|
|
4239
|
+
const shellReadLabel = (current) => current.windowsMode !== undefined ? "Policy (required)" : current.shellReadMode === "host" ? "Host-aligned" : "Policy";
|
|
4221
4240
|
const renderTab = {
|
|
4222
4241
|
overview: (frame) => renderSandboxTab(frame, () => {
|
|
4223
4242
|
if (!overview)
|
|
4224
4243
|
return [];
|
|
4225
4244
|
const { contentWidth } = frame;
|
|
4226
4245
|
const status = overview.noSandboxFlag ? { color: "warning", label: "Disabled by --no-sandbox" } : !overview.enabled ? { color: "warning", label: "Disabled by configuration" } : !overview.running ? { color: "warning", label: "Inactive" } : { color: "success", label: "Active" };
|
|
4227
|
-
const shellReadMode = overview.windowsMode !== undefined ? "Policy (required)" : overview.shellReadMode === "host" ? "Host-aligned" : "Policy";
|
|
4228
4246
|
const changePath = overview.changeScope === "project" ? overview.paths.project : overview.paths.global;
|
|
4229
4247
|
const valueWidth = Math.max(10, contentWidth - 23);
|
|
4230
|
-
const item = (label, value) => ` ${theme.fg("dim", label.padEnd(20))} ${value}`;
|
|
4231
4248
|
const body = [
|
|
4232
4249
|
`${theme.fg(status.color, "●")} ${theme.fg("text", status.label)}`,
|
|
4233
4250
|
item("Configured", overview.enabled ? theme.fg("text", "Enabled") : theme.fg("warning", "Disabled")),
|
|
@@ -4236,7 +4253,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
4236
4253
|
"",
|
|
4237
4254
|
theme.fg("accent", "Protection"),
|
|
4238
4255
|
item("Network", overview.networkMode),
|
|
4239
|
-
item("Shell reads",
|
|
4256
|
+
item("Shell reads", shellReadLabel(overview)),
|
|
4240
4257
|
item("Read rules", `${overview.config.filesystem.denyRead.length + overview.config.filesystem.denyReadAlways.length + overview.config.filesystem.allowRead.length} · ${overview.readRuleScope}`),
|
|
4241
4258
|
item("Write rules", String(overview.config.filesystem.allowWrite.length + overview.config.filesystem.denyWrite.length + overview.config.filesystem.denyWriteAlways.length)),
|
|
4242
4259
|
item("Session grants", String(overview.sessionDomains.length + overview.sessionReadPaths.length + overview.sessionWritePaths.length)),
|
|
@@ -4256,8 +4273,6 @@ ${this.primaryAgent.prompt}` };
|
|
|
4256
4273
|
if (!overview)
|
|
4257
4274
|
return [];
|
|
4258
4275
|
const { listValue } = frame;
|
|
4259
|
-
const shellReadMode = overview.windowsMode !== undefined ? "Policy (required)" : overview.shellReadMode === "host" ? "Host-aligned" : "Policy";
|
|
4260
|
-
const item = (label, value) => ` ${theme.fg("dim", label.padEnd(20))} ${value}`;
|
|
4261
4276
|
return [
|
|
4262
4277
|
theme.fg("accent", "Network"),
|
|
4263
4278
|
item("Mode", overview.networkMode),
|
|
@@ -4266,7 +4281,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
4266
4281
|
item("Unix sockets", overview.config.network.allowAllUnixSockets ? "all" : listValue(overview.config.network.allowUnixSockets)),
|
|
4267
4282
|
"",
|
|
4268
4283
|
theme.fg("accent", "Filesystem"),
|
|
4269
|
-
item("Shell reads",
|
|
4284
|
+
item("Shell reads", shellReadLabel(overview)),
|
|
4270
4285
|
item("Read rules for", overview.readRuleScope),
|
|
4271
4286
|
item("Denied reads", listValue(overview.config.filesystem.denyRead)),
|
|
4272
4287
|
item("Always denied reads", listValue(overview.config.filesystem.denyReadAlways)),
|
|
@@ -4557,35 +4572,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
4557
4572
|
tab = "log";
|
|
4558
4573
|
follow = true;
|
|
4559
4574
|
scroll = 0;
|
|
4560
|
-
|
|
4561
|
-
input.focused = true;
|
|
4562
|
-
input.onEscape = () => {
|
|
4563
|
-
steering = undefined;
|
|
4564
|
-
tui.requestRender();
|
|
4565
|
-
};
|
|
4566
|
-
input.onSubmit = (value) => {
|
|
4567
|
-
const message = value.trim();
|
|
4568
|
-
if (!message) {
|
|
4569
|
-
ctx.ui.notify("Steering message must not be empty", "warning");
|
|
4570
|
-
return;
|
|
4571
|
-
}
|
|
4572
|
-
steering = undefined;
|
|
4573
|
-
(async () => {
|
|
4574
|
-
const running = this.running.get(task.id);
|
|
4575
|
-
if (running) {
|
|
4576
|
-
await running.rpc.steer(message);
|
|
4577
|
-
} else if ((task.state === "queued" || task.state === "running") && this.runPromises.has(task.id)) {
|
|
4578
|
-
const pending = this.pendingSteers.get(task.id) ?? [];
|
|
4579
|
-
pending.push(message);
|
|
4580
|
-
this.pendingSteers.set(task.id, pending);
|
|
4581
|
-
} else {
|
|
4582
|
-
throw new Error("Task is no longer active");
|
|
4583
|
-
}
|
|
4584
|
-
ctx.ui.notify(`Steered task ${task.id.slice(0, 8)}`, "info");
|
|
4585
|
-
})().catch((error) => ctx.ui.notify(`Could not steer task: ${formatError(error)}`, "error")).finally(() => tui.requestRender());
|
|
4586
|
-
tui.requestRender();
|
|
4587
|
-
};
|
|
4588
|
-
steering = input;
|
|
4575
|
+
startSteering(task);
|
|
4589
4576
|
} else if (data.toLowerCase() === "r") {
|
|
4590
4577
|
const finished = tasks.filter((t) => t.state === "completed" || t.state === "error" || t.state === "cancelled" || t.state === "interrupted");
|
|
4591
4578
|
if (finished.length === 0) {
|
|
@@ -4634,35 +4621,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
4634
4621
|
});
|
|
4635
4622
|
return;
|
|
4636
4623
|
} else if (matchesKey(data, "return") && (task.state === "queued" || task.state === "running") && task.kind !== "command") {
|
|
4637
|
-
|
|
4638
|
-
input.focused = true;
|
|
4639
|
-
input.onEscape = () => {
|
|
4640
|
-
steering = undefined;
|
|
4641
|
-
tui.requestRender();
|
|
4642
|
-
};
|
|
4643
|
-
input.onSubmit = (value) => {
|
|
4644
|
-
const message = value.trim();
|
|
4645
|
-
if (!message) {
|
|
4646
|
-
ctx.ui.notify("Steering message must not be empty", "warning");
|
|
4647
|
-
return;
|
|
4648
|
-
}
|
|
4649
|
-
steering = undefined;
|
|
4650
|
-
(async () => {
|
|
4651
|
-
const running = this.running.get(task.id);
|
|
4652
|
-
if (running) {
|
|
4653
|
-
await running.rpc.steer(message);
|
|
4654
|
-
} else if ((task.state === "queued" || task.state === "running") && this.runPromises.has(task.id)) {
|
|
4655
|
-
const pending = this.pendingSteers.get(task.id) ?? [];
|
|
4656
|
-
pending.push(message);
|
|
4657
|
-
this.pendingSteers.set(task.id, pending);
|
|
4658
|
-
} else {
|
|
4659
|
-
throw new Error("Task is no longer active");
|
|
4660
|
-
}
|
|
4661
|
-
ctx.ui.notify(`Steered task ${task.id.slice(0, 8)}`, "info");
|
|
4662
|
-
})().catch((error) => ctx.ui.notify(`Could not steer task: ${formatError(error)}`, "error")).finally(() => tui.requestRender());
|
|
4663
|
-
tui.requestRender();
|
|
4664
|
-
};
|
|
4665
|
-
steering = input;
|
|
4624
|
+
startSteering(task);
|
|
4666
4625
|
} else if (data.toLowerCase() === "f") {
|
|
4667
4626
|
follow = !follow;
|
|
4668
4627
|
} else if (matchesKey(data, "up")) {
|
|
@@ -4760,15 +4719,12 @@ ${this.primaryAgent.prompt}` };
|
|
|
4760
4719
|
tui.requestRender();
|
|
4761
4720
|
return;
|
|
4762
4721
|
}
|
|
4763
|
-
|
|
4764
|
-
|
|
4722
|
+
runSaving("Could not delete agent", async () => {
|
|
4723
|
+
const deleted = await deleteProjectAgent(ctx.cwd, agent);
|
|
4765
4724
|
if (!deleted)
|
|
4766
4725
|
return;
|
|
4767
4726
|
await refreshAgentRuntime(name);
|
|
4768
4727
|
ctx.ui.notify(`Deleted project agent ${name}`, "info");
|
|
4769
|
-
}).catch((error) => ctx.ui.notify(`Could not delete agent: ${formatError(error)}`, "error")).finally(() => {
|
|
4770
|
-
saving = false;
|
|
4771
|
-
tui.requestRender();
|
|
4772
4728
|
});
|
|
4773
4729
|
return;
|
|
4774
4730
|
}
|
|
@@ -4863,7 +4819,12 @@ ${this.primaryAgent.prompt}` };
|
|
|
4863
4819
|
return {
|
|
4864
4820
|
name: "tasks",
|
|
4865
4821
|
label: "Tasks",
|
|
4866
|
-
description: "Inspect, list, and cancel background tasks and subagents managed by Landstrip.",
|
|
4822
|
+
description: "Inspect, list, steer, and cancel background tasks and subagents managed by Landstrip. Point-in-time management only, not a waiting primitive; background tasks automatically notify and trigger a follow-up turn when done.",
|
|
4823
|
+
promptSnippet: "Inspect, list, steer, or cancel background tasks and subagents without polling",
|
|
4824
|
+
promptGuidelines: [
|
|
4825
|
+
"Use tasks for point-in-time inspection, steering, or cancellation, not as a waiting primitive.",
|
|
4826
|
+
"Do not poll tasks with list or inspect while waiting for a background task to finish; completion automatically triggers a follow-up turn."
|
|
4827
|
+
],
|
|
4867
4828
|
parameters: TasksToolSchema,
|
|
4868
4829
|
executionMode: "parallel",
|
|
4869
4830
|
renderCall: (input, theme) => {
|
|
@@ -4871,7 +4832,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
4871
4832
|
const target = input.task_id ? ` ${input.task_id.slice(0, 8)}` : "";
|
|
4872
4833
|
return new Text(`${theme.fg("toolTitle", theme.bold("Tasks"))} ${theme.fg("accent", action)}${theme.fg("dim", target)}`, 0, 0);
|
|
4873
4834
|
},
|
|
4874
|
-
execute: async (_toolCallId, input,
|
|
4835
|
+
execute: async (_toolCallId, input, _signal, _onUpdate, callCtx) => {
|
|
4875
4836
|
const ctx = boundCtx ?? callCtx;
|
|
4876
4837
|
if (!ctx)
|
|
4877
4838
|
throw new Error("Task context is unavailable");
|
|
@@ -4886,7 +4847,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
4886
4847
|
throw new Error(`Task ID prefix "${input.task_id}" matches ${matched.length} tasks`);
|
|
4887
4848
|
return matched[0];
|
|
4888
4849
|
};
|
|
4889
|
-
const formatInspectResult = (task
|
|
4850
|
+
const formatInspectResult = (task) => {
|
|
4890
4851
|
const details = taskDetails(task);
|
|
4891
4852
|
const transcript = taskTranscript(task);
|
|
4892
4853
|
const maxLines = Math.max(1, Math.min(500, input.lines ?? 100));
|
|
@@ -4894,18 +4855,18 @@ ${this.primaryAgent.prompt}` };
|
|
|
4894
4855
|
const outputText = sliced.join(`
|
|
4895
4856
|
`);
|
|
4896
4857
|
const modeLabel = details.background ? "background" : "foreground";
|
|
4897
|
-
const
|
|
4898
|
-
|
|
4858
|
+
const activeGuidance = details.background && (details.state === "queued" || details.state === "running") ? `
|
|
4859
|
+
Next action: task is still active; do not poll inspect in a loop—end this turn and wait for the automatic completion follow-up turn.` : "";
|
|
4899
4860
|
const header = task.kind === "command" ? `$ bash ${task.command ?? task.description}
|
|
4900
4861
|
Mode: ${modeLabel}
|
|
4901
4862
|
CWD: ${task.cwd}
|
|
4902
4863
|
State: ${details.state}
|
|
4903
4864
|
Duration: ${taskDuration(details) ?? "unknown"}
|
|
4904
|
-
Exit code: ${details.exitCode ?? "none"}${
|
|
4865
|
+
Exit code: ${details.exitCode ?? "none"}${activeGuidance}` : `@${task.agent}: ${task.description}
|
|
4905
4866
|
Mode: ${modeLabel}
|
|
4906
4867
|
State: ${details.state}
|
|
4907
4868
|
Duration: ${taskDuration(details) ?? "unknown"}
|
|
4908
|
-
Tool calls: ${task.toolCalls ?? 0}${
|
|
4869
|
+
Tool calls: ${task.toolCalls ?? 0}${activeGuidance}`;
|
|
4909
4870
|
return {
|
|
4910
4871
|
content: [
|
|
4911
4872
|
{ type: "text", text: `${header}
|
|
@@ -4951,49 +4912,6 @@ ${outputText}` }
|
|
|
4951
4912
|
const task = resolveSingleTask("inspect");
|
|
4952
4913
|
return formatInspectResult(task);
|
|
4953
4914
|
}
|
|
4954
|
-
if (action === "wait") {
|
|
4955
|
-
const task = resolveSingleTask("wait");
|
|
4956
|
-
let timedOut = false;
|
|
4957
|
-
const runPromise = this.runPromises.get(task.id);
|
|
4958
|
-
if (runPromise && (task.state === "queued" || task.state === "running")) {
|
|
4959
|
-
const waiters = [
|
|
4960
|
-
runPromise.then(() => {
|
|
4961
|
-
return;
|
|
4962
|
-
}, () => {
|
|
4963
|
-
return;
|
|
4964
|
-
})
|
|
4965
|
-
];
|
|
4966
|
-
let timer;
|
|
4967
|
-
if (input.timeout_ms && input.timeout_ms > 0) {
|
|
4968
|
-
waiters.push(new Promise((resolveWait) => {
|
|
4969
|
-
timer = setTimeout(() => {
|
|
4970
|
-
timedOut = true;
|
|
4971
|
-
resolveWait();
|
|
4972
|
-
}, input.timeout_ms);
|
|
4973
|
-
}));
|
|
4974
|
-
}
|
|
4975
|
-
let onAbort;
|
|
4976
|
-
if (signal) {
|
|
4977
|
-
waiters.push(new Promise((_, rejectWait) => {
|
|
4978
|
-
if (signal.aborted) {
|
|
4979
|
-
rejectWait(new Error("Wait aborted"));
|
|
4980
|
-
return;
|
|
4981
|
-
}
|
|
4982
|
-
onAbort = () => rejectWait(new Error("Wait aborted"));
|
|
4983
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
4984
|
-
}));
|
|
4985
|
-
}
|
|
4986
|
-
try {
|
|
4987
|
-
await Promise.race(waiters);
|
|
4988
|
-
} finally {
|
|
4989
|
-
if (timer !== undefined)
|
|
4990
|
-
clearTimeout(timer);
|
|
4991
|
-
if (onAbort && signal)
|
|
4992
|
-
signal.removeEventListener("abort", onAbort);
|
|
4993
|
-
}
|
|
4994
|
-
}
|
|
4995
|
-
return formatInspectResult(task, timedOut);
|
|
4996
|
-
}
|
|
4997
4915
|
if (action === "steer") {
|
|
4998
4916
|
const task = resolveSingleTask("steer");
|
|
4999
4917
|
const message = input.message?.trim();
|
|
@@ -5004,16 +4922,7 @@ ${outputText}` }
|
|
|
5004
4922
|
if (task.state !== "running" && task.state !== "queued") {
|
|
5005
4923
|
throw new Error(`Task ${task.id.slice(0, 8)} is no longer active (${task.state})`);
|
|
5006
4924
|
}
|
|
5007
|
-
|
|
5008
|
-
if (running) {
|
|
5009
|
-
await running.rpc.steer(message);
|
|
5010
|
-
} else if (this.runPromises.has(task.id)) {
|
|
5011
|
-
const pending = this.pendingSteers.get(task.id) ?? [];
|
|
5012
|
-
pending.push(message);
|
|
5013
|
-
this.pendingSteers.set(task.id, pending);
|
|
5014
|
-
} else {
|
|
5015
|
-
throw new Error(`Task ${task.id.slice(0, 8)} is no longer active`);
|
|
5016
|
-
}
|
|
4925
|
+
await this.steerTask(task, message, `Task ${task.id.slice(0, 8)} is no longer active`);
|
|
5017
4926
|
return {
|
|
5018
4927
|
content: [
|
|
5019
4928
|
{
|
|
@@ -5041,6 +4950,18 @@ ${outputText}` }
|
|
|
5041
4950
|
}
|
|
5042
4951
|
};
|
|
5043
4952
|
}
|
|
4953
|
+
async steerTask(task, message, inactiveMessage = "Task is no longer active") {
|
|
4954
|
+
const running = this.running.get(task.id);
|
|
4955
|
+
if (running) {
|
|
4956
|
+
await running.rpc.steer(message);
|
|
4957
|
+
} else if ((task.state === "queued" || task.state === "running") && this.runPromises.has(task.id)) {
|
|
4958
|
+
const pending = this.pendingSteers.get(task.id) ?? [];
|
|
4959
|
+
pending.push(message);
|
|
4960
|
+
this.pendingSteers.set(task.id, pending);
|
|
4961
|
+
} else {
|
|
4962
|
+
throw new Error(inactiveMessage);
|
|
4963
|
+
}
|
|
4964
|
+
}
|
|
5044
4965
|
async runBackgroundShellTask(params, ctx) {
|
|
5045
4966
|
const taskId = randomUUID();
|
|
5046
4967
|
const sessionId = this.activeSessionId ?? ctx.sessionManager.getSessionId();
|
|
@@ -5113,17 +5034,22 @@ ${outputText}` }
|
|
|
5113
5034
|
text: `$ bash ${task.description} ${exitMsg} (${durationSec})`,
|
|
5114
5035
|
finishedAt: Date.now()
|
|
5115
5036
|
});
|
|
5116
|
-
|
|
5117
|
-
|
|
5037
|
+
if (!this.shuttingDown && !task.delivered && task.state !== "cancelled") {
|
|
5038
|
+
const rawOutput = task.outputFile && existsSync5(task.outputFile) ? readFileSync6(task.outputFile, "utf8").trim() : task.output ?? "";
|
|
5039
|
+
const commandOutput = rawOutput ? this.storeTaskText(task, rawOutput, "output") : "";
|
|
5040
|
+
if (commandOutput)
|
|
5041
|
+
task.output = commandOutput;
|
|
5042
|
+
const deliveryText = `Background command ${task.state === "completed" ? "completed" : "failed"}: ${task.description}
|
|
5118
5043
|
|
|
5119
5044
|
${renderTaskResult(task.id, task.state, commandOutput || (task.error ?? ""))}`;
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5045
|
+
const delivered = await this.deliverBackground(task, deliveryText);
|
|
5046
|
+
if (delivered) {
|
|
5047
|
+
task.delivered = true;
|
|
5048
|
+
this.persist(task);
|
|
5049
|
+
}
|
|
5050
|
+
if (task.state === "error") {
|
|
5051
|
+
ctx.ui.notify(`Background command failed: ${task.description}`, "error");
|
|
5052
|
+
}
|
|
5127
5053
|
}
|
|
5128
5054
|
this.notifyTaskUpdate();
|
|
5129
5055
|
this.updateTaskWidget(ctx);
|
|
@@ -5135,10 +5061,14 @@ ${renderTaskResult(task.id, task.state, commandOutput || (task.error ?? ""))}`;
|
|
|
5135
5061
|
content: [
|
|
5136
5062
|
{
|
|
5137
5063
|
type: "text",
|
|
5138
|
-
text:
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5064
|
+
text: [
|
|
5065
|
+
"Command launched in background.",
|
|
5066
|
+
`Task ID: ${taskId}`,
|
|
5067
|
+
`Output file: ${outputFile}`,
|
|
5068
|
+
BACKGROUND_LAUNCH_GUIDANCE,
|
|
5069
|
+
"Use /landstrip-subagents tasks to view live progress or cancel."
|
|
5070
|
+
].join(`
|
|
5071
|
+
`)
|
|
5142
5072
|
}
|
|
5143
5073
|
],
|
|
5144
5074
|
details: {
|
|
@@ -5162,6 +5092,10 @@ Use /landstrip-subagents tasks to view live progress or cancel.`
|
|
|
5162
5092
|
|
|
5163
5093
|
Available subagents:
|
|
5164
5094
|
${descriptions}` : ""),
|
|
5095
|
+
promptSnippet: "Delegate a task to a sandboxed Pi subagent process",
|
|
5096
|
+
promptGuidelines: [
|
|
5097
|
+
"After launching a task with background: true, continue only independent work that does not wait on the subagent; otherwise end the current turn and wait for the automatic completion follow-up turn. Do not poll tasks to wait."
|
|
5098
|
+
],
|
|
5165
5099
|
parameters: taskParameters,
|
|
5166
5100
|
executionMode: "parallel",
|
|
5167
5101
|
renderCall: (input, theme) => {
|
|
@@ -5326,7 +5260,8 @@ ${catalog.diagnostics.join(`
|
|
|
5326
5260
|
this.notifyWhenDone(task, existingRun, ctx);
|
|
5327
5261
|
}
|
|
5328
5262
|
const state = task.state === "queued" ? "queued" : "running";
|
|
5329
|
-
const status = state === "queued" ? "Background task queued" : "Background task updated"
|
|
5263
|
+
const status = `${state === "queued" ? "Background task queued" : "Background task updated"}
|
|
5264
|
+
${BACKGROUND_LAUNCH_GUIDANCE}`;
|
|
5330
5265
|
return { task, text: renderTaskResult(task.id, state, status), state };
|
|
5331
5266
|
}
|
|
5332
5267
|
this.claimForeground(task.id);
|
|
@@ -5379,7 +5314,8 @@ ${catalog.diagnostics.join(`
|
|
|
5379
5314
|
this.notifyWhenDone(task, run, ctx);
|
|
5380
5315
|
return {
|
|
5381
5316
|
task,
|
|
5382
|
-
text: renderTaskResult(task.id, "queued",
|
|
5317
|
+
text: renderTaskResult(task.id, "queued", `Background task queued
|
|
5318
|
+
${BACKGROUND_LAUNCH_GUIDANCE}`),
|
|
5383
5319
|
state: "queued"
|
|
5384
5320
|
};
|
|
5385
5321
|
}
|
|
@@ -6351,7 +6287,7 @@ var LandstripBashSchema = Type2.Object({
|
|
|
6351
6287
|
command: Type2.String({ description: "Shell command to execute" }),
|
|
6352
6288
|
timeout: Type2.Optional(Type2.Number({ description: "Timeout in seconds (optional, no default timeout)" })),
|
|
6353
6289
|
background: Type2.Optional(Type2.Boolean({
|
|
6354
|
-
description: "Run command asynchronously in the background and return a task ID"
|
|
6290
|
+
description: "Run command asynchronously in the background and return a task ID immediately; completion automatically triggers a follow-up turn"
|
|
6355
6291
|
})),
|
|
6356
6292
|
name: Type2.Optional(Type2.String({
|
|
6357
6293
|
description: "Concise label for the background command execution"
|
|
@@ -8042,6 +7978,10 @@ ${message}
|
|
|
8042
7978
|
...localBash,
|
|
8043
7979
|
label: "bash (landstrip)",
|
|
8044
7980
|
description: "Execute commands with the active Landstrip shell provider.",
|
|
7981
|
+
promptGuidelines: [
|
|
7982
|
+
...localBash.promptGuidelines ?? [],
|
|
7983
|
+
"After launching a bash command with background: true, continue only independent work that does not wait on the command; otherwise end the current turn and wait for the automatic completion follow-up turn. Do not sleep or poll tasks to wait."
|
|
7984
|
+
],
|
|
8045
7985
|
parameters: LandstripBashSchema,
|
|
8046
7986
|
renderCall(args, theme, context) {
|
|
8047
7987
|
const bashArgs = args;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-landstrip",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.5",
|
|
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.20.
|
|
42
|
+
"@landstrip/landstrip-api": "^0.20.5",
|
|
43
43
|
"jsonc-parser": "^3.3.1",
|
|
44
44
|
"minimatch": "^10.2.5",
|
|
45
45
|
"undici": "^8.9.0",
|