pi-landstrip 0.20.4 → 0.20.6
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 +65 -84
- 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) {
|
|
@@ -4818,7 +4819,12 @@ ${this.primaryAgent.prompt}` };
|
|
|
4818
4819
|
return {
|
|
4819
4820
|
name: "tasks",
|
|
4820
4821
|
label: "Tasks",
|
|
4821
|
-
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
|
+
],
|
|
4822
4828
|
parameters: TasksToolSchema,
|
|
4823
4829
|
executionMode: "parallel",
|
|
4824
4830
|
renderCall: (input, theme) => {
|
|
@@ -4826,7 +4832,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
4826
4832
|
const target = input.task_id ? ` ${input.task_id.slice(0, 8)}` : "";
|
|
4827
4833
|
return new Text(`${theme.fg("toolTitle", theme.bold("Tasks"))} ${theme.fg("accent", action)}${theme.fg("dim", target)}`, 0, 0);
|
|
4828
4834
|
},
|
|
4829
|
-
execute: async (_toolCallId, input,
|
|
4835
|
+
execute: async (_toolCallId, input, _signal, _onUpdate, callCtx) => {
|
|
4830
4836
|
const ctx = boundCtx ?? callCtx;
|
|
4831
4837
|
if (!ctx)
|
|
4832
4838
|
throw new Error("Task context is unavailable");
|
|
@@ -4841,7 +4847,7 @@ ${this.primaryAgent.prompt}` };
|
|
|
4841
4847
|
throw new Error(`Task ID prefix "${input.task_id}" matches ${matched.length} tasks`);
|
|
4842
4848
|
return matched[0];
|
|
4843
4849
|
};
|
|
4844
|
-
const formatInspectResult = (task
|
|
4850
|
+
const formatInspectResult = (task) => {
|
|
4845
4851
|
const details = taskDetails(task);
|
|
4846
4852
|
const transcript = taskTranscript(task);
|
|
4847
4853
|
const maxLines = Math.max(1, Math.min(500, input.lines ?? 100));
|
|
@@ -4849,18 +4855,18 @@ ${this.primaryAgent.prompt}` };
|
|
|
4849
4855
|
const outputText = sliced.join(`
|
|
4850
4856
|
`);
|
|
4851
4857
|
const modeLabel = details.background ? "background" : "foreground";
|
|
4852
|
-
const
|
|
4853
|
-
|
|
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.` : "";
|
|
4854
4860
|
const header = task.kind === "command" ? `$ bash ${task.command ?? task.description}
|
|
4855
4861
|
Mode: ${modeLabel}
|
|
4856
4862
|
CWD: ${task.cwd}
|
|
4857
4863
|
State: ${details.state}
|
|
4858
4864
|
Duration: ${taskDuration(details) ?? "unknown"}
|
|
4859
|
-
Exit code: ${details.exitCode ?? "none"}${
|
|
4865
|
+
Exit code: ${details.exitCode ?? "none"}${activeGuidance}` : `@${task.agent}: ${task.description}
|
|
4860
4866
|
Mode: ${modeLabel}
|
|
4861
4867
|
State: ${details.state}
|
|
4862
4868
|
Duration: ${taskDuration(details) ?? "unknown"}
|
|
4863
|
-
Tool calls: ${task.toolCalls ?? 0}${
|
|
4869
|
+
Tool calls: ${task.toolCalls ?? 0}${activeGuidance}`;
|
|
4864
4870
|
return {
|
|
4865
4871
|
content: [
|
|
4866
4872
|
{ type: "text", text: `${header}
|
|
@@ -4906,49 +4912,6 @@ ${outputText}` }
|
|
|
4906
4912
|
const task = resolveSingleTask("inspect");
|
|
4907
4913
|
return formatInspectResult(task);
|
|
4908
4914
|
}
|
|
4909
|
-
if (action === "wait") {
|
|
4910
|
-
const task = resolveSingleTask("wait");
|
|
4911
|
-
let timedOut = false;
|
|
4912
|
-
const runPromise = this.runPromises.get(task.id);
|
|
4913
|
-
if (runPromise && (task.state === "queued" || task.state === "running")) {
|
|
4914
|
-
const waiters = [
|
|
4915
|
-
runPromise.then(() => {
|
|
4916
|
-
return;
|
|
4917
|
-
}, () => {
|
|
4918
|
-
return;
|
|
4919
|
-
})
|
|
4920
|
-
];
|
|
4921
|
-
let timer;
|
|
4922
|
-
if (input.timeout_ms && input.timeout_ms > 0) {
|
|
4923
|
-
waiters.push(new Promise((resolveWait) => {
|
|
4924
|
-
timer = setTimeout(() => {
|
|
4925
|
-
timedOut = true;
|
|
4926
|
-
resolveWait();
|
|
4927
|
-
}, input.timeout_ms);
|
|
4928
|
-
}));
|
|
4929
|
-
}
|
|
4930
|
-
let onAbort;
|
|
4931
|
-
if (signal) {
|
|
4932
|
-
waiters.push(new Promise((_, rejectWait) => {
|
|
4933
|
-
if (signal.aborted) {
|
|
4934
|
-
rejectWait(new Error("Wait aborted"));
|
|
4935
|
-
return;
|
|
4936
|
-
}
|
|
4937
|
-
onAbort = () => rejectWait(new Error("Wait aborted"));
|
|
4938
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
4939
|
-
}));
|
|
4940
|
-
}
|
|
4941
|
-
try {
|
|
4942
|
-
await Promise.race(waiters);
|
|
4943
|
-
} finally {
|
|
4944
|
-
if (timer !== undefined)
|
|
4945
|
-
clearTimeout(timer);
|
|
4946
|
-
if (onAbort && signal)
|
|
4947
|
-
signal.removeEventListener("abort", onAbort);
|
|
4948
|
-
}
|
|
4949
|
-
}
|
|
4950
|
-
return formatInspectResult(task, timedOut);
|
|
4951
|
-
}
|
|
4952
4915
|
if (action === "steer") {
|
|
4953
4916
|
const task = resolveSingleTask("steer");
|
|
4954
4917
|
const message = input.message?.trim();
|
|
@@ -5071,17 +5034,22 @@ ${outputText}` }
|
|
|
5071
5034
|
text: `$ bash ${task.description} ${exitMsg} (${durationSec})`,
|
|
5072
5035
|
finishedAt: Date.now()
|
|
5073
5036
|
});
|
|
5074
|
-
|
|
5075
|
-
|
|
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}
|
|
5076
5043
|
|
|
5077
5044
|
${renderTaskResult(task.id, task.state, commandOutput || (task.error ?? ""))}`;
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
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
|
+
}
|
|
5085
5053
|
}
|
|
5086
5054
|
this.notifyTaskUpdate();
|
|
5087
5055
|
this.updateTaskWidget(ctx);
|
|
@@ -5093,10 +5061,14 @@ ${renderTaskResult(task.id, task.state, commandOutput || (task.error ?? ""))}`;
|
|
|
5093
5061
|
content: [
|
|
5094
5062
|
{
|
|
5095
5063
|
type: "text",
|
|
5096
|
-
text:
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
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
|
+
`)
|
|
5100
5072
|
}
|
|
5101
5073
|
],
|
|
5102
5074
|
details: {
|
|
@@ -5120,6 +5092,10 @@ Use /landstrip-subagents tasks to view live progress or cancel.`
|
|
|
5120
5092
|
|
|
5121
5093
|
Available subagents:
|
|
5122
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
|
+
],
|
|
5123
5099
|
parameters: taskParameters,
|
|
5124
5100
|
executionMode: "parallel",
|
|
5125
5101
|
renderCall: (input, theme) => {
|
|
@@ -5284,7 +5260,8 @@ ${catalog.diagnostics.join(`
|
|
|
5284
5260
|
this.notifyWhenDone(task, existingRun, ctx);
|
|
5285
5261
|
}
|
|
5286
5262
|
const state = task.state === "queued" ? "queued" : "running";
|
|
5287
|
-
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}`;
|
|
5288
5265
|
return { task, text: renderTaskResult(task.id, state, status), state };
|
|
5289
5266
|
}
|
|
5290
5267
|
this.claimForeground(task.id);
|
|
@@ -5337,7 +5314,8 @@ ${catalog.diagnostics.join(`
|
|
|
5337
5314
|
this.notifyWhenDone(task, run, ctx);
|
|
5338
5315
|
return {
|
|
5339
5316
|
task,
|
|
5340
|
-
text: renderTaskResult(task.id, "queued",
|
|
5317
|
+
text: renderTaskResult(task.id, "queued", `Background task queued
|
|
5318
|
+
${BACKGROUND_LAUNCH_GUIDANCE}`),
|
|
5341
5319
|
state: "queued"
|
|
5342
5320
|
};
|
|
5343
5321
|
}
|
|
@@ -6167,8 +6145,7 @@ ${renderTaskResult(task.id, failed ? "error" : "completed", value)}`;
|
|
|
6167
6145
|
this.primaryRules = undefined;
|
|
6168
6146
|
this.primaryConfigurationError = false;
|
|
6169
6147
|
const catalog = this.loadCatalog(ctx.cwd, getAgentDir4(), isProjectTrusted(ctx));
|
|
6170
|
-
this.
|
|
6171
|
-
this.semaphore = new Semaphore(this.maxSubagents);
|
|
6148
|
+
this.setMaxSubagents(catalog.maxSubagents);
|
|
6172
6149
|
if (catalog.diagnostics.length > 0) {
|
|
6173
6150
|
this.invalidatePrimaryAgent(catalog, ctx);
|
|
6174
6151
|
return;
|
|
@@ -6309,7 +6286,7 @@ var LandstripBashSchema = Type2.Object({
|
|
|
6309
6286
|
command: Type2.String({ description: "Shell command to execute" }),
|
|
6310
6287
|
timeout: Type2.Optional(Type2.Number({ description: "Timeout in seconds (optional, no default timeout)" })),
|
|
6311
6288
|
background: Type2.Optional(Type2.Boolean({
|
|
6312
|
-
description: "Run command asynchronously in the background and return a task ID"
|
|
6289
|
+
description: "Run command asynchronously in the background and return a task ID immediately; completion automatically triggers a follow-up turn"
|
|
6313
6290
|
})),
|
|
6314
6291
|
name: Type2.Optional(Type2.String({
|
|
6315
6292
|
description: "Concise label for the background command execution"
|
|
@@ -8000,6 +7977,10 @@ ${message}
|
|
|
8000
7977
|
...localBash,
|
|
8001
7978
|
label: "bash (landstrip)",
|
|
8002
7979
|
description: "Execute commands with the active Landstrip shell provider.",
|
|
7980
|
+
promptGuidelines: [
|
|
7981
|
+
...localBash.promptGuidelines ?? [],
|
|
7982
|
+
"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."
|
|
7983
|
+
],
|
|
8003
7984
|
parameters: LandstripBashSchema,
|
|
8004
7985
|
renderCall(args, theme, context) {
|
|
8005
7986
|
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.6",
|
|
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.6",
|
|
43
43
|
"jsonc-parser": "^3.3.1",
|
|
44
44
|
"minimatch": "^10.2.5",
|
|
45
45
|
"undici": "^8.9.0",
|