@astralyn/pi 0.84.3 → 0.84.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/CHANGELOG.md +54 -0
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +11 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/auth-command.d.ts.map +1 -1
- package/dist/cli/auth-command.js +5 -4
- package/dist/cli/auth-command.js.map +1 -1
- package/dist/cli/project-trust.d.ts.map +1 -1
- package/dist/cli/project-trust.js +1 -4
- package/dist/cli/project-trust.js.map +1 -1
- package/dist/core/agent-session.d.ts +6 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +7 -6
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/experimental.d.ts +4 -0
- package/dist/core/experimental.d.ts.map +1 -1
- package/dist/core/experimental.js +4 -0
- package/dist/core/experimental.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +4 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +10 -14
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/keybindings.d.ts +29 -0
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +4 -0
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/model-config.d.ts +5 -0
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +1 -0
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-registry.d.ts +2 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +4 -0
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/project-trust.js +2 -2
- package/dist/core/project-trust.js.map +1 -1
- package/dist/core/sdk.d.ts +5 -3
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +2 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.js +2 -2
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +6 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +12 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.js +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +2 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +3 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +2 -0
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.js +1 -1
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.js +1 -1
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +2 -0
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +2 -0
- package/dist/core/tools/write.js.map +1 -1
- package/dist/extensions/background/index.d.ts +74 -0
- package/dist/extensions/background/index.d.ts.map +1 -0
- package/dist/extensions/background/index.js +332 -0
- package/dist/extensions/background/index.js.map +1 -0
- package/dist/extensions/background/manager.d.ts +81 -0
- package/dist/extensions/background/manager.d.ts.map +1 -0
- package/dist/extensions/background/manager.js +320 -0
- package/dist/extensions/background/manager.js.map +1 -0
- package/dist/extensions/background/registry.d.ts +138 -0
- package/dist/extensions/background/registry.d.ts.map +1 -0
- package/dist/extensions/background/registry.js +358 -0
- package/dist/extensions/background/registry.js.map +1 -0
- package/dist/extensions/background/render.d.ts +26 -0
- package/dist/extensions/background/render.d.ts.map +1 -0
- package/dist/extensions/background/render.js +118 -0
- package/dist/extensions/background/render.js.map +1 -0
- package/dist/extensions/index.d.ts.map +1 -1
- package/dist/extensions/index.js +2 -0
- package/dist/extensions/index.js.map +1 -1
- package/dist/extensions/subagent/activity.d.ts +0 -1
- package/dist/extensions/subagent/activity.d.ts.map +1 -1
- package/dist/extensions/subagent/activity.js +2 -5
- package/dist/extensions/subagent/activity.js.map +1 -1
- package/dist/extensions/subagent/agents-command.d.ts +4 -0
- package/dist/extensions/subagent/agents-command.d.ts.map +1 -0
- package/dist/extensions/subagent/agents-command.js +385 -0
- package/dist/extensions/subagent/agents-command.js.map +1 -0
- package/dist/extensions/subagent/agents.d.ts +3 -6
- package/dist/extensions/subagent/agents.d.ts.map +1 -1
- package/dist/extensions/subagent/agents.js +22 -190
- package/dist/extensions/subagent/agents.js.map +1 -1
- package/dist/extensions/subagent/constants.d.ts +4 -5
- package/dist/extensions/subagent/constants.d.ts.map +1 -1
- package/dist/extensions/subagent/constants.js +11 -7
- package/dist/extensions/subagent/constants.js.map +1 -1
- package/dist/extensions/subagent/index.d.ts +0 -3
- package/dist/extensions/subagent/index.d.ts.map +1 -1
- package/dist/extensions/subagent/index.js +58 -315
- package/dist/extensions/subagent/index.js.map +1 -1
- package/dist/extensions/subagent/render.d.ts +1 -1
- package/dist/extensions/subagent/render.d.ts.map +1 -1
- package/dist/extensions/subagent/render.js +208 -585
- package/dist/extensions/subagent/render.js.map +1 -1
- package/dist/extensions/subagent/resolve.d.ts +2 -2
- package/dist/extensions/subagent/resolve.d.ts.map +1 -1
- package/dist/extensions/subagent/resolve.js +23 -15
- package/dist/extensions/subagent/resolve.js.map +1 -1
- package/dist/extensions/subagent/runner.d.ts +1 -2
- package/dist/extensions/subagent/runner.d.ts.map +1 -1
- package/dist/extensions/subagent/runner.js +109 -131
- package/dist/extensions/subagent/runner.js.map +1 -1
- package/dist/extensions/subagent/schema.d.ts +4 -10
- package/dist/extensions/subagent/schema.d.ts.map +1 -1
- package/dist/extensions/subagent/schema.js +8 -13
- package/dist/extensions/subagent/schema.js.map +1 -1
- package/dist/extensions/subagent/sdk-runner.d.ts.map +1 -1
- package/dist/extensions/subagent/sdk-runner.js +9 -20
- package/dist/extensions/subagent/sdk-runner.js.map +1 -1
- package/dist/extensions/subagent/settings.d.ts +2 -3
- package/dist/extensions/subagent/settings.d.ts.map +1 -1
- package/dist/extensions/subagent/settings.js +30 -21
- package/dist/extensions/subagent/settings.js.map +1 -1
- package/dist/extensions/subagent/text.d.ts +3 -0
- package/dist/extensions/subagent/text.d.ts.map +1 -0
- package/dist/extensions/subagent/text.js +16 -0
- package/dist/extensions/subagent/text.js.map +1 -0
- package/dist/extensions/subagent/types.d.ts +13 -33
- package/dist/extensions/subagent/types.d.ts.map +1 -1
- package/dist/extensions/subagent/types.js.map +1 -1
- package/dist/extensions/subagent/ui/model-list.d.ts +68 -0
- package/dist/extensions/subagent/ui/model-list.d.ts.map +1 -0
- package/dist/extensions/subagent/ui/model-list.js +248 -0
- package/dist/extensions/subagent/ui/model-list.js.map +1 -0
- package/dist/extensions/todo/constants.d.ts +6 -17
- package/dist/extensions/todo/constants.d.ts.map +1 -1
- package/dist/extensions/todo/constants.js +20 -31
- package/dist/extensions/todo/constants.js.map +1 -1
- package/dist/extensions/todo/index.d.ts.map +1 -1
- package/dist/extensions/todo/index.js +36 -57
- package/dist/extensions/todo/index.js.map +1 -1
- package/dist/extensions/todo/schema.d.ts +20 -59
- package/dist/extensions/todo/schema.d.ts.map +1 -1
- package/dist/extensions/todo/schema.js +23 -69
- package/dist/extensions/todo/schema.js.map +1 -1
- package/dist/extensions/todo/state.d.ts +23 -65
- package/dist/extensions/todo/state.d.ts.map +1 -1
- package/dist/extensions/todo/state.js +238 -1262
- package/dist/extensions/todo/state.js.map +1 -1
- package/dist/extensions/todo/view.d.ts +19 -10
- package/dist/extensions/todo/view.d.ts.map +1 -1
- package/dist/extensions/todo/view.js +411 -463
- package/dist/extensions/todo/view.js.map +1 -1
- package/dist/extensions/todo/widget.d.ts +15 -0
- package/dist/extensions/todo/widget.d.ts.map +1 -0
- package/dist/extensions/todo/widget.js +50 -0
- package/dist/extensions/todo/widget.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +5 -1
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts +1 -2
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js +5 -11
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +2 -1
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +10 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +8 -2
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +77 -25
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/model-catalog-refresh.d.ts +7 -0
- package/dist/modes/interactive/model-catalog-refresh.d.ts.map +1 -0
- package/dist/modes/interactive/model-catalog-refresh.js +36 -0
- package/dist/modes/interactive/model-catalog-refresh.js.map +1 -0
- package/dist/modes/interactive/theme/dark.json +2 -0
- package/dist/modes/interactive/theme/light.json +2 -0
- package/dist/modes/interactive/theme/theme-controller.d.ts +10 -2
- package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme-controller.js +23 -10
- package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
- package/dist/modes/interactive/theme/theme-schema.json +9 -1
- package/dist/modes/interactive/theme/theme.d.ts +5 -3
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +12 -2
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/json-event.d.ts +4 -1
- package/dist/modes/json-event.d.ts.map +1 -1
- package/dist/modes/json-event.js +5 -2
- package/dist/modes/json-event.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +1 -8
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/package-manager-cli.js +1 -1
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/server/create-harness.d.ts.map +1 -1
- package/dist/server/create-harness.js +2 -0
- package/dist/server/create-harness.js.map +1 -1
- package/dist/utils/tools-manager.d.ts +10 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +14 -19
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/bundled/README.md +2 -1
- package/docs/bundled/extensions/background.md +64 -0
- package/docs/bundled/extensions/subagent.md +84 -57
- package/docs/bundled/extensions/todo.md +84 -24
- package/docs/bundled/tool-presentation.md +1 -1
- package/docs/compaction.md +6 -6
- package/docs/custom-provider.md +1 -1
- package/docs/docs.json +4 -0
- package/docs/environment-variables.md +8 -2
- package/docs/extensions.md +6 -2
- package/docs/json.md +6 -4
- package/docs/keybindings.md +9 -2
- package/docs/rpc.md +15 -4
- package/docs/sdk.md +9 -0
- package/docs/settings.md +31 -0
- package/docs/terminal-setup.md +23 -2
- package/docs/themes.md +24 -3
- package/docs/tui.md +2 -2
- package/docs/usage.md +4 -3
- package/examples/README.md +1 -1
- package/examples/extensions/README.md +0 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +170 -221
- package/package.json +6 -6
- package/dist/extensions/subagent/picker.d.ts +0 -31
- package/dist/extensions/subagent/picker.d.ts.map +0 -1
- package/dist/extensions/subagent/picker.js +0 -109
- package/dist/extensions/subagent/picker.js.map +0 -1
- package/dist/extensions/todo/overlay.d.ts +0 -24
- package/dist/extensions/todo/overlay.d.ts.map +0 -1
- package/dist/extensions/todo/overlay.js +0 -135
- package/dist/extensions/todo/overlay.js.map +0 -1
- package/examples/extensions/subagent/README.md +0 -175
- package/examples/extensions/subagent/agents/planner.md +0 -37
- package/examples/extensions/subagent/agents/reviewer.md +0 -35
- package/examples/extensions/subagent/agents/scout.md +0 -50
- package/examples/extensions/subagent/agents/worker.md +0 -24
- package/examples/extensions/subagent/agents.ts +0 -126
- package/examples/extensions/subagent/index.ts +0 -1009
- package/examples/extensions/subagent/prompts/implement-and-review.md +0 -10
- package/examples/extensions/subagent/prompts/implement.md +0 -10
- package/examples/extensions/subagent/prompts/scout-and-plan.md +0 -9
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* background — run bash commands in the background.
|
|
3
|
+
*
|
|
4
|
+
* Tools: bg_bash starts a task and returns immediately; the result arrives as
|
|
5
|
+
* a <background-task> notification (queued behind the current run while
|
|
6
|
+
* streaming, waking the agent when idle). bg_logs reads a bounded slice of a
|
|
7
|
+
* task's output file; bg_kill stops a single task. /bg opens the interactive
|
|
8
|
+
* task manager. Running counts surface in the footer via ctx.ui.setStatus.
|
|
9
|
+
*/
|
|
10
|
+
import { Type } from "typebox";
|
|
11
|
+
import { getAgentDir } from "../../config.js";
|
|
12
|
+
import { SettingsManager } from "../../core/settings-manager.js";
|
|
13
|
+
import { createLocalBashOperations, resolveSpawnContext } from "../../core/tools/bash.js";
|
|
14
|
+
import { DEFAULT_MAX_BYTES, formatSize } from "../../core/tools/truncate.js";
|
|
15
|
+
import { sanitizeBinaryOutput } from "../../utils/shell.js";
|
|
16
|
+
import { BackgroundTasksMenu } from "./manager.js";
|
|
17
|
+
import { BackgroundTaskRegistry, formatDuration, readOutputSlice, } from "./registry.js";
|
|
18
|
+
import { commandLabel, renderBackgroundNotification, renderBgBashCall, renderBgBashResult, renderBgKillCall, renderBgLogsCall, } from "./render.js";
|
|
19
|
+
export const BG_NOTIFICATION_TYPE = "background-task";
|
|
20
|
+
const BG_LOGS_DEFAULT_BYTES = 8 * 1024;
|
|
21
|
+
const BG_LOGS_MIN_BYTES = 256;
|
|
22
|
+
const bgBashSchema = Type.Object({
|
|
23
|
+
command: Type.String({ description: "Bash command to run in the background" }),
|
|
24
|
+
timeout: Type.Optional(Type.Number({
|
|
25
|
+
description: "Timeout in seconds (optional, no default; must be positive). On expiry the task is killed and reported as timeout.",
|
|
26
|
+
})),
|
|
27
|
+
});
|
|
28
|
+
const bgLogsSchema = Type.Object({
|
|
29
|
+
taskId: Type.String({ description: "Task id; a unique prefix is accepted (e.g. 'bg-3f' or '3f')" }),
|
|
30
|
+
mode: Type.Optional(Type.Union([Type.Literal("tail"), Type.Literal("head")], {
|
|
31
|
+
description: "Read from the end (default) or the start of the output",
|
|
32
|
+
})),
|
|
33
|
+
bytes: Type.Optional(Type.Number({
|
|
34
|
+
description: `Max bytes to return (default ${BG_LOGS_DEFAULT_BYTES}, max ${DEFAULT_MAX_BYTES})`,
|
|
35
|
+
})),
|
|
36
|
+
});
|
|
37
|
+
const bgKillSchema = Type.Object({
|
|
38
|
+
taskId: Type.String({ description: "Task id; a unique prefix is accepted" }),
|
|
39
|
+
});
|
|
40
|
+
export function escapeXml(text) {
|
|
41
|
+
return text
|
|
42
|
+
.replaceAll("&", "&")
|
|
43
|
+
.replaceAll("<", "<")
|
|
44
|
+
.replaceAll(">", ">")
|
|
45
|
+
.replaceAll('"', """)
|
|
46
|
+
.replaceAll("'", "'");
|
|
47
|
+
}
|
|
48
|
+
export function buildNotificationContent(notification) {
|
|
49
|
+
const task = notification.task;
|
|
50
|
+
const runtime = formatDuration((task.endedAt ?? task.startedAt) - task.startedAt);
|
|
51
|
+
const exitCode = task.exitCode === undefined || task.exitCode === null ? "" : ` exitCode="${task.exitCode}"`;
|
|
52
|
+
const lines = [
|
|
53
|
+
`<background-task id="${task.id}" status="${task.status}"${exitCode} runtime="${runtime}">`,
|
|
54
|
+
`<command>${escapeXml(task.command)}</command>`,
|
|
55
|
+
`<output-file>${escapeXml(task.outputPath)}</output-file>`,
|
|
56
|
+
];
|
|
57
|
+
if (task.error) {
|
|
58
|
+
lines.push(`<error>${escapeXml(task.error)}</error>`);
|
|
59
|
+
}
|
|
60
|
+
if (notification.tailError) {
|
|
61
|
+
lines.push(`<output-tail unavailable="${escapeXml(notification.tailError)}"/>`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
const attrs = [
|
|
65
|
+
`bytes="${notification.tailBytes}"`,
|
|
66
|
+
`totalBytes="${notification.totalBytes}"`,
|
|
67
|
+
notification.tailTruncated ? 'truncated="true"' : "",
|
|
68
|
+
notification.tailStartsMidLine ? 'startsMidLine="true"' : "",
|
|
69
|
+
]
|
|
70
|
+
.filter(Boolean)
|
|
71
|
+
.join(" ");
|
|
72
|
+
const tail = notification.tailText.length > 0 ? escapeXml(notification.tailText) : "(no output)";
|
|
73
|
+
lines.push(`<output-tail ${attrs}>`, tail.trimEnd(), "</output-tail>");
|
|
74
|
+
}
|
|
75
|
+
lines.push("</background-task>");
|
|
76
|
+
return lines.join("\n");
|
|
77
|
+
}
|
|
78
|
+
export function formatStatusline(counts) {
|
|
79
|
+
if (counts.total === 0)
|
|
80
|
+
return undefined;
|
|
81
|
+
const ended = counts.total - counts.running;
|
|
82
|
+
const parts = [];
|
|
83
|
+
if (counts.running > 0)
|
|
84
|
+
parts.push(`${counts.running} running`);
|
|
85
|
+
if (ended > 0)
|
|
86
|
+
parts.push(`${ended} done`);
|
|
87
|
+
return `bg ${parts.join(" · ")}`;
|
|
88
|
+
}
|
|
89
|
+
function describeResolveFailure(input, result) {
|
|
90
|
+
if (result.reason === "ambiguous") {
|
|
91
|
+
const lines = result.candidates.map((task) => ` ${task.id} (${task.status}) ${commandLabel(task.command, 60)}`);
|
|
92
|
+
return `Task id "${input}" is ambiguous. Candidates:\n${lines.join("\n")}`;
|
|
93
|
+
}
|
|
94
|
+
return `No background task matches "${input}". Check the id returned by bg_bash or open /bg.`;
|
|
95
|
+
}
|
|
96
|
+
function describeTaskLine(task, now) {
|
|
97
|
+
const duration = formatDuration((task.endedAt ?? now) - task.startedAt);
|
|
98
|
+
const exit = task.exitCode !== undefined && task.exitCode !== null ? ` exit ${task.exitCode}` : "";
|
|
99
|
+
return ` ${task.id} ${task.status}${exit} ${duration} ${commandLabel(task.command, 40)}`;
|
|
100
|
+
}
|
|
101
|
+
function toNotificationDetails(notification) {
|
|
102
|
+
const task = notification.task;
|
|
103
|
+
return {
|
|
104
|
+
taskId: task.id,
|
|
105
|
+
command: task.command,
|
|
106
|
+
status: task.status,
|
|
107
|
+
exitCode: task.exitCode,
|
|
108
|
+
runtimeMs: (task.endedAt ?? task.startedAt) - task.startedAt,
|
|
109
|
+
outputPath: task.outputPath,
|
|
110
|
+
totalBytes: notification.totalBytes,
|
|
111
|
+
tailText: notification.tailText,
|
|
112
|
+
tailTruncated: notification.tailTruncated,
|
|
113
|
+
error: task.error,
|
|
114
|
+
tailError: notification.tailError,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** Local shell operations honoring the session's configured shell path. */
|
|
118
|
+
function createSessionBashOperations(ctx) {
|
|
119
|
+
const settings = SettingsManager.create(ctx.cwd, getAgentDir(), {
|
|
120
|
+
projectTrusted: ctx.isProjectTrusted(),
|
|
121
|
+
});
|
|
122
|
+
return createLocalBashOperations({ shellPath: settings.getShellPath() });
|
|
123
|
+
}
|
|
124
|
+
/** Factory with injectable seams for tests; the default export uses production wiring. */
|
|
125
|
+
export function createBackgroundExtension(overrides) {
|
|
126
|
+
return (pi) => {
|
|
127
|
+
let registry;
|
|
128
|
+
let sessionCtx;
|
|
129
|
+
const requireRegistry = () => {
|
|
130
|
+
if (!registry)
|
|
131
|
+
throw new Error("Background tasks are not available yet (no active session).");
|
|
132
|
+
return registry;
|
|
133
|
+
};
|
|
134
|
+
const resolveTaskOrThrow = (taskId) => {
|
|
135
|
+
const resolved = requireRegistry().resolveTask(taskId);
|
|
136
|
+
if (!resolved.ok)
|
|
137
|
+
throw new Error(describeResolveFailure(taskId, resolved));
|
|
138
|
+
return resolved.task;
|
|
139
|
+
};
|
|
140
|
+
const updateStatus = () => {
|
|
141
|
+
if (!registry || registry.isShuttingDown)
|
|
142
|
+
return;
|
|
143
|
+
sessionCtx?.ui.setStatus("background", formatStatusline(registry.counts()));
|
|
144
|
+
};
|
|
145
|
+
pi.on("session_start", (_event, ctx) => {
|
|
146
|
+
sessionCtx = ctx;
|
|
147
|
+
// Defensive: a leftover registry (session_shutdown not seen) must not leak processes.
|
|
148
|
+
if (registry)
|
|
149
|
+
void registry.shutdown();
|
|
150
|
+
registry = new BackgroundTaskRegistry({
|
|
151
|
+
operations: overrides?.operations ?? createSessionBashOperations(ctx),
|
|
152
|
+
outputDir: overrides?.outputDir,
|
|
153
|
+
maxOutputBytes: overrides?.maxOutputBytes,
|
|
154
|
+
onNotify: (notification) => {
|
|
155
|
+
pi.sendMessage({
|
|
156
|
+
customType: BG_NOTIFICATION_TYPE,
|
|
157
|
+
content: buildNotificationContent(notification),
|
|
158
|
+
display: true,
|
|
159
|
+
details: toNotificationDetails(notification),
|
|
160
|
+
}, { deliverAs: "followUp", triggerTurn: true });
|
|
161
|
+
},
|
|
162
|
+
onChange: updateStatus,
|
|
163
|
+
});
|
|
164
|
+
updateStatus();
|
|
165
|
+
});
|
|
166
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
167
|
+
ctx.ui.setStatus("background", undefined);
|
|
168
|
+
sessionCtx = undefined;
|
|
169
|
+
const active = registry;
|
|
170
|
+
registry = undefined;
|
|
171
|
+
await active?.shutdown();
|
|
172
|
+
});
|
|
173
|
+
pi.registerTool({
|
|
174
|
+
name: "bg_bash",
|
|
175
|
+
label: "bg bash",
|
|
176
|
+
description: "Run a bash command in the background and return immediately with a task id and output file path. " +
|
|
177
|
+
"The result (status, exit code, output tail) arrives automatically as a <background-task> notification " +
|
|
178
|
+
"when the task ends. Use for long-running commands such as dev servers, watch builds, or slow tests. " +
|
|
179
|
+
"Optionally provide a timeout in seconds.",
|
|
180
|
+
promptSnippet: "Run bash commands in the background (bg_bash); results arrive automatically",
|
|
181
|
+
promptGuidelines: [
|
|
182
|
+
"Use bg_bash for long-running commands (servers, builds, slow tests). Never poll for completion with sleep loops or repeated bg_logs; the <background-task> notification arrives automatically when the task ends. Use bg_logs for an early peek at a running task and bg_kill to stop one.",
|
|
183
|
+
],
|
|
184
|
+
parameters: bgBashSchema,
|
|
185
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
186
|
+
if (params.timeout !== undefined && (!Number.isFinite(params.timeout) || params.timeout <= 0)) {
|
|
187
|
+
throw new Error("Invalid timeout: must be a positive number of seconds.");
|
|
188
|
+
}
|
|
189
|
+
// Same PI_* session variables as the built-in bash tool, snapshotted at start.
|
|
190
|
+
const spawnContext = resolveSpawnContext(params.command, ctx.cwd, undefined, true, ctx);
|
|
191
|
+
// Deliberately ignore the turn signal: aborting the current turn must
|
|
192
|
+
// not kill the background task (that is what bg_kill is for).
|
|
193
|
+
const task = requireRegistry().startTask({
|
|
194
|
+
command: params.command,
|
|
195
|
+
cwd: ctx.cwd,
|
|
196
|
+
timeoutSeconds: params.timeout,
|
|
197
|
+
env: spawnContext.env,
|
|
198
|
+
});
|
|
199
|
+
const text = [
|
|
200
|
+
`Started background task ${task.id}.`,
|
|
201
|
+
`Output file: ${task.outputPath}`,
|
|
202
|
+
"",
|
|
203
|
+
"Do NOT poll for completion (no sleep loops, no repeated bg_logs). A <background-task> " +
|
|
204
|
+
"notification with the result and output tail arrives automatically when the task ends — " +
|
|
205
|
+
"continue independent work or end the turn. Use bg_logs for an early peek, bg_kill to stop it.",
|
|
206
|
+
].join("\n");
|
|
207
|
+
return {
|
|
208
|
+
content: [{ type: "text", text }],
|
|
209
|
+
details: { taskId: task.id, outputPath: task.outputPath, command: params.command },
|
|
210
|
+
};
|
|
211
|
+
},
|
|
212
|
+
renderCall(args, theme, context) {
|
|
213
|
+
return renderBgBashCall(args, theme, context);
|
|
214
|
+
},
|
|
215
|
+
renderResult(result, options, theme, context) {
|
|
216
|
+
return renderBgBashResult(result, options, theme, context);
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
pi.registerTool({
|
|
220
|
+
name: "bg_logs",
|
|
221
|
+
label: "bg logs",
|
|
222
|
+
description: `Read a bounded slice of a background task's output file (default: last ${formatSize(BG_LOGS_DEFAULT_BYTES)}, ` +
|
|
223
|
+
`max ${formatSize(DEFAULT_MAX_BYTES)}). Works while the task is still running. ` +
|
|
224
|
+
"Do not call this in a polling loop; completion arrives as a notification.",
|
|
225
|
+
parameters: bgLogsSchema,
|
|
226
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
227
|
+
const task = resolveTaskOrThrow(params.taskId);
|
|
228
|
+
const mode = params.mode ?? "tail";
|
|
229
|
+
const maxBytes = Math.min(DEFAULT_MAX_BYTES, Math.max(BG_LOGS_MIN_BYTES, Math.floor(params.bytes ?? BG_LOGS_DEFAULT_BYTES)));
|
|
230
|
+
let slice;
|
|
231
|
+
try {
|
|
232
|
+
slice = await readOutputSlice(task.outputPath, { mode, maxBytes });
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
236
|
+
throw new Error(`Could not read output for ${task.id} (${task.status}): ${message}\nOutput file: ${task.outputPath}`);
|
|
237
|
+
}
|
|
238
|
+
const text = sanitizeBinaryOutput(slice.text);
|
|
239
|
+
const noticeParts = slice.truncated
|
|
240
|
+
? [
|
|
241
|
+
`${mode} ${formatSize(slice.sliceBytes)} of ${formatSize(slice.totalBytes)}`,
|
|
242
|
+
`task ${task.id} ${task.status}`,
|
|
243
|
+
...(slice.startsMidLine ? ["first line may be partial"] : []),
|
|
244
|
+
`full output: ${task.outputPath}`,
|
|
245
|
+
]
|
|
246
|
+
: [
|
|
247
|
+
`task ${task.id} ${task.status}`,
|
|
248
|
+
`${formatSize(slice.totalBytes)} total`,
|
|
249
|
+
`output: ${task.outputPath}`,
|
|
250
|
+
];
|
|
251
|
+
const body = text.length > 0 ? text : "(no output yet)";
|
|
252
|
+
return {
|
|
253
|
+
content: [{ type: "text", text: `[${noticeParts.join(" · ")}]\n${body}` }],
|
|
254
|
+
details: {
|
|
255
|
+
taskId: task.id,
|
|
256
|
+
mode,
|
|
257
|
+
sliceBytes: slice.sliceBytes,
|
|
258
|
+
totalBytes: slice.totalBytes,
|
|
259
|
+
outputPath: task.outputPath,
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
},
|
|
263
|
+
renderCall(args, theme) {
|
|
264
|
+
return renderBgLogsCall(args, theme);
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
pi.registerTool({
|
|
268
|
+
name: "bg_kill",
|
|
269
|
+
label: "bg kill",
|
|
270
|
+
description: "Kill a running background task (the whole process tree). The terminal status and output tail " +
|
|
271
|
+
"still arrive as the task's completion notification.",
|
|
272
|
+
parameters: bgKillSchema,
|
|
273
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
274
|
+
const task = resolveTaskOrThrow(params.taskId);
|
|
275
|
+
const result = requireRegistry().killTask(task.id);
|
|
276
|
+
if (!result.killed) {
|
|
277
|
+
const exit = task.exitCode !== undefined && task.exitCode !== null ? `, exit ${task.exitCode}` : "";
|
|
278
|
+
throw new Error(`Task ${task.id} is not running (status: ${task.status}${exit}).`);
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
content: [
|
|
282
|
+
{
|
|
283
|
+
type: "text",
|
|
284
|
+
text: `Killed task ${task.id}. A completion notification follows once the process tree is reaped.`,
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
details: { taskId: task.id, command: task.command },
|
|
288
|
+
};
|
|
289
|
+
},
|
|
290
|
+
renderCall(args, theme) {
|
|
291
|
+
return renderBgKillCall(args, theme);
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
pi.registerMessageRenderer(BG_NOTIFICATION_TYPE, renderBackgroundNotification);
|
|
295
|
+
pi.registerCommand("bg", {
|
|
296
|
+
description: "View and manage background tasks",
|
|
297
|
+
handler: async (_args, ctx) => {
|
|
298
|
+
const active = registry;
|
|
299
|
+
if (!active || active.counts().total === 0) {
|
|
300
|
+
ctx.ui.notify("No background tasks.", "info");
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (ctx.mode !== "tui") {
|
|
304
|
+
// RPC/print: ctx.ui.custom is unavailable — show a bounded summary.
|
|
305
|
+
const lines = active
|
|
306
|
+
.listTasks()
|
|
307
|
+
.slice(0, 10)
|
|
308
|
+
.map((task) => describeTaskLine(task, Date.now()));
|
|
309
|
+
ctx.ui.notify(`Background tasks:\n${lines.join("\n")}\nUse bg_kill <id> to stop a task.`, "info");
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
await showBackgroundManager(ctx, active);
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
async function showBackgroundManager(ctx, registry) {
|
|
318
|
+
// No overlay options: the component mounts inline in the editor slot, like /model.
|
|
319
|
+
await ctx.ui.custom((tui, theme, keybindings, done) => new BackgroundTasksMenu({
|
|
320
|
+
tui,
|
|
321
|
+
theme,
|
|
322
|
+
keybindings,
|
|
323
|
+
host: {
|
|
324
|
+
listTasks: () => registry.listTasks(),
|
|
325
|
+
killTask: (id) => registry.killTask(id),
|
|
326
|
+
readSlice: (filePath, options) => readOutputSlice(filePath, options),
|
|
327
|
+
},
|
|
328
|
+
onClose: () => done(undefined),
|
|
329
|
+
}));
|
|
330
|
+
}
|
|
331
|
+
export default createBackgroundExtension();
|
|
332
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/extensions/background/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * background — run bash commands in the background.\n *\n * Tools: bg_bash starts a task and returns immediately; the result arrives as\n * a <background-task> notification (queued behind the current run while\n * streaming, waking the agent when idle). bg_logs reads a bounded slice of a\n * task's output file; bg_kill stops a single task. /bg opens the interactive\n * task manager. Running counts surface in the footer via ctx.ui.setStatus.\n */\n\nimport { type Static, Type } from \"typebox\";\nimport { getAgentDir } from \"../../config.ts\";\nimport type {\n\tAgentToolResult,\n\tExtensionAPI,\n\tExtensionCommandContext,\n\tExtensionContext,\n} from \"../../core/extensions/types.ts\";\nimport { SettingsManager } from \"../../core/settings-manager.ts\";\nimport { type BashOperations, createLocalBashOperations, resolveSpawnContext } from \"../../core/tools/bash.ts\";\nimport { DEFAULT_MAX_BYTES, formatSize } from \"../../core/tools/truncate.ts\";\nimport { sanitizeBinaryOutput } from \"../../utils/shell.ts\";\nimport { BackgroundTasksMenu } from \"./manager.ts\";\nimport {\n\tBackgroundTaskRegistry,\n\ttype BgTask,\n\ttype BgTaskNotification,\n\ttype BgTaskStatus,\n\tformatDuration,\n\ttype ResolveTaskResult,\n\treadOutputSlice,\n} from \"./registry.ts\";\nimport {\n\tcommandLabel,\n\trenderBackgroundNotification,\n\trenderBgBashCall,\n\trenderBgBashResult,\n\trenderBgKillCall,\n\trenderBgLogsCall,\n} from \"./render.ts\";\n\nexport const BG_NOTIFICATION_TYPE = \"background-task\";\nconst BG_LOGS_DEFAULT_BYTES = 8 * 1024;\nconst BG_LOGS_MIN_BYTES = 256;\n\nconst bgBashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Bash command to run in the background\" }),\n\ttimeout: Type.Optional(\n\t\tType.Number({\n\t\t\tdescription:\n\t\t\t\t\"Timeout in seconds (optional, no default; must be positive). On expiry the task is killed and reported as timeout.\",\n\t\t}),\n\t),\n});\n\nconst bgLogsSchema = Type.Object({\n\ttaskId: Type.String({ description: \"Task id; a unique prefix is accepted (e.g. 'bg-3f' or '3f')\" }),\n\tmode: Type.Optional(\n\t\tType.Union([Type.Literal(\"tail\"), Type.Literal(\"head\")], {\n\t\t\tdescription: \"Read from the end (default) or the start of the output\",\n\t\t}),\n\t),\n\tbytes: Type.Optional(\n\t\tType.Number({\n\t\t\tdescription: `Max bytes to return (default ${BG_LOGS_DEFAULT_BYTES}, max ${DEFAULT_MAX_BYTES})`,\n\t\t}),\n\t),\n});\n\nconst bgKillSchema = Type.Object({\n\ttaskId: Type.String({ description: \"Task id; a unique prefix is accepted\" }),\n});\n\nexport type BgBashInput = Static<typeof bgBashSchema>;\nexport type BgLogsInput = Static<typeof bgLogsSchema>;\nexport type BgKillInput = Static<typeof bgKillSchema>;\n\nexport interface BgBashDetails {\n\ttaskId: string;\n\toutputPath: string;\n\tcommand: string;\n}\n\nexport interface BgLogsDetails {\n\ttaskId: string;\n\tmode: \"head\" | \"tail\";\n\tsliceBytes: number;\n\ttotalBytes: number;\n\toutputPath: string;\n}\n\nexport interface BgKillDetails {\n\ttaskId: string;\n\tcommand: string;\n}\n\nexport interface BgNotificationDetails {\n\ttaskId: string;\n\tcommand: string;\n\tstatus: BgTaskStatus;\n\texitCode: number | null | undefined;\n\truntimeMs: number;\n\toutputPath: string;\n\ttotalBytes: number;\n\ttailText: string;\n\ttailTruncated: boolean;\n\terror?: string;\n\ttailError?: string;\n}\n\nexport function escapeXml(text: string): string {\n\treturn text\n\t\t.replaceAll(\"&\", \"&\")\n\t\t.replaceAll(\"<\", \"<\")\n\t\t.replaceAll(\">\", \">\")\n\t\t.replaceAll('\"', \""\")\n\t\t.replaceAll(\"'\", \"'\");\n}\n\nexport function buildNotificationContent(notification: BgTaskNotification): string {\n\tconst task = notification.task;\n\tconst runtime = formatDuration((task.endedAt ?? task.startedAt) - task.startedAt);\n\tconst exitCode = task.exitCode === undefined || task.exitCode === null ? \"\" : ` exitCode=\"${task.exitCode}\"`;\n\tconst lines = [\n\t\t`<background-task id=\"${task.id}\" status=\"${task.status}\"${exitCode} runtime=\"${runtime}\">`,\n\t\t`<command>${escapeXml(task.command)}</command>`,\n\t\t`<output-file>${escapeXml(task.outputPath)}</output-file>`,\n\t];\n\tif (task.error) {\n\t\tlines.push(`<error>${escapeXml(task.error)}</error>`);\n\t}\n\tif (notification.tailError) {\n\t\tlines.push(`<output-tail unavailable=\"${escapeXml(notification.tailError)}\"/>`);\n\t} else {\n\t\tconst attrs = [\n\t\t\t`bytes=\"${notification.tailBytes}\"`,\n\t\t\t`totalBytes=\"${notification.totalBytes}\"`,\n\t\t\tnotification.tailTruncated ? 'truncated=\"true\"' : \"\",\n\t\t\tnotification.tailStartsMidLine ? 'startsMidLine=\"true\"' : \"\",\n\t\t]\n\t\t\t.filter(Boolean)\n\t\t\t.join(\" \");\n\t\tconst tail = notification.tailText.length > 0 ? escapeXml(notification.tailText) : \"(no output)\";\n\t\tlines.push(`<output-tail ${attrs}>`, tail.trimEnd(), \"</output-tail>\");\n\t}\n\tlines.push(\"</background-task>\");\n\treturn lines.join(\"\\n\");\n}\n\nexport function formatStatusline(counts: { running: number; total: number }): string | undefined {\n\tif (counts.total === 0) return undefined;\n\tconst ended = counts.total - counts.running;\n\tconst parts: string[] = [];\n\tif (counts.running > 0) parts.push(`${counts.running} running`);\n\tif (ended > 0) parts.push(`${ended} done`);\n\treturn `bg ${parts.join(\" · \")}`;\n}\n\nfunction describeResolveFailure(input: string, result: ResolveTaskResult & { ok: false }): string {\n\tif (result.reason === \"ambiguous\") {\n\t\tconst lines = result.candidates.map((task) => ` ${task.id} (${task.status}) ${commandLabel(task.command, 60)}`);\n\t\treturn `Task id \"${input}\" is ambiguous. Candidates:\\n${lines.join(\"\\n\")}`;\n\t}\n\treturn `No background task matches \"${input}\". Check the id returned by bg_bash or open /bg.`;\n}\n\nfunction describeTaskLine(task: BgTask, now: number): string {\n\tconst duration = formatDuration((task.endedAt ?? now) - task.startedAt);\n\tconst exit = task.exitCode !== undefined && task.exitCode !== null ? ` exit ${task.exitCode}` : \"\";\n\treturn ` ${task.id} ${task.status}${exit} ${duration} ${commandLabel(task.command, 40)}`;\n}\n\nfunction toNotificationDetails(notification: BgTaskNotification): BgNotificationDetails {\n\tconst task = notification.task;\n\treturn {\n\t\ttaskId: task.id,\n\t\tcommand: task.command,\n\t\tstatus: task.status,\n\t\texitCode: task.exitCode,\n\t\truntimeMs: (task.endedAt ?? task.startedAt) - task.startedAt,\n\t\toutputPath: task.outputPath,\n\t\ttotalBytes: notification.totalBytes,\n\t\ttailText: notification.tailText,\n\t\ttailTruncated: notification.tailTruncated,\n\t\terror: task.error,\n\t\ttailError: notification.tailError,\n\t};\n}\n\nexport interface BackgroundExtensionOverrides {\n\toperations?: BashOperations;\n\toutputDir?: string;\n\tmaxOutputBytes?: number;\n}\n\n/** Local shell operations honoring the session's configured shell path. */\nfunction createSessionBashOperations(ctx: ExtensionContext): BashOperations {\n\tconst settings = SettingsManager.create(ctx.cwd, getAgentDir(), {\n\t\tprojectTrusted: ctx.isProjectTrusted(),\n\t});\n\treturn createLocalBashOperations({ shellPath: settings.getShellPath() });\n}\n\n/** Factory with injectable seams for tests; the default export uses production wiring. */\nexport function createBackgroundExtension(overrides?: BackgroundExtensionOverrides): (pi: ExtensionAPI) => void {\n\treturn (pi: ExtensionAPI) => {\n\t\tlet registry: BackgroundTaskRegistry | undefined;\n\t\tlet sessionCtx: ExtensionContext | undefined;\n\n\t\tconst requireRegistry = (): BackgroundTaskRegistry => {\n\t\t\tif (!registry) throw new Error(\"Background tasks are not available yet (no active session).\");\n\t\t\treturn registry;\n\t\t};\n\n\t\tconst resolveTaskOrThrow = (taskId: string): BgTask => {\n\t\t\tconst resolved = requireRegistry().resolveTask(taskId);\n\t\t\tif (!resolved.ok) throw new Error(describeResolveFailure(taskId, resolved));\n\t\t\treturn resolved.task;\n\t\t};\n\n\t\tconst updateStatus = () => {\n\t\t\tif (!registry || registry.isShuttingDown) return;\n\t\t\tsessionCtx?.ui.setStatus(\"background\", formatStatusline(registry.counts()));\n\t\t};\n\n\t\tpi.on(\"session_start\", (_event, ctx) => {\n\t\t\tsessionCtx = ctx;\n\t\t\t// Defensive: a leftover registry (session_shutdown not seen) must not leak processes.\n\t\t\tif (registry) void registry.shutdown();\n\t\t\tregistry = new BackgroundTaskRegistry({\n\t\t\t\toperations: overrides?.operations ?? createSessionBashOperations(ctx),\n\t\t\t\toutputDir: overrides?.outputDir,\n\t\t\t\tmaxOutputBytes: overrides?.maxOutputBytes,\n\t\t\t\tonNotify: (notification) => {\n\t\t\t\t\tpi.sendMessage(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcustomType: BG_NOTIFICATION_TYPE,\n\t\t\t\t\t\t\tcontent: buildNotificationContent(notification),\n\t\t\t\t\t\t\tdisplay: true,\n\t\t\t\t\t\t\tdetails: toNotificationDetails(notification),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{ deliverAs: \"followUp\", triggerTurn: true },\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t\tonChange: updateStatus,\n\t\t\t});\n\t\t\tupdateStatus();\n\t\t});\n\n\t\tpi.on(\"session_shutdown\", async (_event, ctx) => {\n\t\t\tctx.ui.setStatus(\"background\", undefined);\n\t\t\tsessionCtx = undefined;\n\t\t\tconst active = registry;\n\t\t\tregistry = undefined;\n\t\t\tawait active?.shutdown();\n\t\t});\n\n\t\tpi.registerTool<typeof bgBashSchema, BgBashDetails>({\n\t\t\tname: \"bg_bash\",\n\t\t\tlabel: \"bg bash\",\n\t\t\tdescription:\n\t\t\t\t\"Run a bash command in the background and return immediately with a task id and output file path. \" +\n\t\t\t\t\"The result (status, exit code, output tail) arrives automatically as a <background-task> notification \" +\n\t\t\t\t\"when the task ends. Use for long-running commands such as dev servers, watch builds, or slow tests. \" +\n\t\t\t\t\"Optionally provide a timeout in seconds.\",\n\t\t\tpromptSnippet: \"Run bash commands in the background (bg_bash); results arrive automatically\",\n\t\t\tpromptGuidelines: [\n\t\t\t\t\"Use bg_bash for long-running commands (servers, builds, slow tests). Never poll for completion with sleep loops or repeated bg_logs; the <background-task> notification arrives automatically when the task ends. Use bg_logs for an early peek at a running task and bg_kill to stop one.\",\n\t\t\t],\n\t\t\tparameters: bgBashSchema,\n\t\t\tasync execute(_toolCallId, params, _signal, _onUpdate, ctx): Promise<AgentToolResult<BgBashDetails>> {\n\t\t\t\tif (params.timeout !== undefined && (!Number.isFinite(params.timeout) || params.timeout <= 0)) {\n\t\t\t\t\tthrow new Error(\"Invalid timeout: must be a positive number of seconds.\");\n\t\t\t\t}\n\t\t\t\t// Same PI_* session variables as the built-in bash tool, snapshotted at start.\n\t\t\t\tconst spawnContext = resolveSpawnContext(params.command, ctx.cwd, undefined, true, ctx);\n\t\t\t\t// Deliberately ignore the turn signal: aborting the current turn must\n\t\t\t\t// not kill the background task (that is what bg_kill is for).\n\t\t\t\tconst task = requireRegistry().startTask({\n\t\t\t\t\tcommand: params.command,\n\t\t\t\t\tcwd: ctx.cwd,\n\t\t\t\t\ttimeoutSeconds: params.timeout,\n\t\t\t\t\tenv: spawnContext.env,\n\t\t\t\t});\n\t\t\t\tconst text = [\n\t\t\t\t\t`Started background task ${task.id}.`,\n\t\t\t\t\t`Output file: ${task.outputPath}`,\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"Do NOT poll for completion (no sleep loops, no repeated bg_logs). A <background-task> \" +\n\t\t\t\t\t\t\"notification with the result and output tail arrives automatically when the task ends — \" +\n\t\t\t\t\t\t\"continue independent work or end the turn. Use bg_logs for an early peek, bg_kill to stop it.\",\n\t\t\t\t].join(\"\\n\");\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text }],\n\t\t\t\t\tdetails: { taskId: task.id, outputPath: task.outputPath, command: params.command },\n\t\t\t\t};\n\t\t\t},\n\t\t\trenderCall(args, theme, context) {\n\t\t\t\treturn renderBgBashCall(args, theme, context);\n\t\t\t},\n\t\t\trenderResult(result, options, theme, context) {\n\t\t\t\treturn renderBgBashResult(result, options, theme, context);\n\t\t\t},\n\t\t});\n\n\t\tpi.registerTool<typeof bgLogsSchema, BgLogsDetails>({\n\t\t\tname: \"bg_logs\",\n\t\t\tlabel: \"bg logs\",\n\t\t\tdescription:\n\t\t\t\t`Read a bounded slice of a background task's output file (default: last ${formatSize(BG_LOGS_DEFAULT_BYTES)}, ` +\n\t\t\t\t`max ${formatSize(DEFAULT_MAX_BYTES)}). Works while the task is still running. ` +\n\t\t\t\t\"Do not call this in a polling loop; completion arrives as a notification.\",\n\t\t\tparameters: bgLogsSchema,\n\t\t\tasync execute(_toolCallId, params, _signal, _onUpdate, _ctx): Promise<AgentToolResult<BgLogsDetails>> {\n\t\t\t\tconst task = resolveTaskOrThrow(params.taskId);\n\t\t\t\tconst mode = params.mode ?? \"tail\";\n\t\t\t\tconst maxBytes = Math.min(\n\t\t\t\t\tDEFAULT_MAX_BYTES,\n\t\t\t\t\tMath.max(BG_LOGS_MIN_BYTES, Math.floor(params.bytes ?? BG_LOGS_DEFAULT_BYTES)),\n\t\t\t\t);\n\t\t\t\tlet slice: Awaited<ReturnType<typeof readOutputSlice>>;\n\t\t\t\ttry {\n\t\t\t\t\tslice = await readOutputSlice(task.outputPath, { mode, maxBytes });\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Could not read output for ${task.id} (${task.status}): ${message}\\nOutput file: ${task.outputPath}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconst text = sanitizeBinaryOutput(slice.text);\n\t\t\t\tconst noticeParts = slice.truncated\n\t\t\t\t\t? [\n\t\t\t\t\t\t\t`${mode} ${formatSize(slice.sliceBytes)} of ${formatSize(slice.totalBytes)}`,\n\t\t\t\t\t\t\t`task ${task.id} ${task.status}`,\n\t\t\t\t\t\t\t...(slice.startsMidLine ? [\"first line may be partial\"] : []),\n\t\t\t\t\t\t\t`full output: ${task.outputPath}`,\n\t\t\t\t\t\t]\n\t\t\t\t\t: [\n\t\t\t\t\t\t\t`task ${task.id} ${task.status}`,\n\t\t\t\t\t\t\t`${formatSize(slice.totalBytes)} total`,\n\t\t\t\t\t\t\t`output: ${task.outputPath}`,\n\t\t\t\t\t\t];\n\t\t\t\tconst body = text.length > 0 ? text : \"(no output yet)\";\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: `[${noticeParts.join(\" · \")}]\\n${body}` }],\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\ttaskId: task.id,\n\t\t\t\t\t\tmode,\n\t\t\t\t\t\tsliceBytes: slice.sliceBytes,\n\t\t\t\t\t\ttotalBytes: slice.totalBytes,\n\t\t\t\t\t\toutputPath: task.outputPath,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t},\n\t\t\trenderCall(args, theme) {\n\t\t\t\treturn renderBgLogsCall(args, theme);\n\t\t\t},\n\t\t});\n\n\t\tpi.registerTool<typeof bgKillSchema, BgKillDetails>({\n\t\t\tname: \"bg_kill\",\n\t\t\tlabel: \"bg kill\",\n\t\t\tdescription:\n\t\t\t\t\"Kill a running background task (the whole process tree). The terminal status and output tail \" +\n\t\t\t\t\"still arrive as the task's completion notification.\",\n\t\t\tparameters: bgKillSchema,\n\t\t\tasync execute(_toolCallId, params, _signal, _onUpdate, _ctx): Promise<AgentToolResult<BgKillDetails>> {\n\t\t\t\tconst task = resolveTaskOrThrow(params.taskId);\n\t\t\t\tconst result = requireRegistry().killTask(task.id);\n\t\t\t\tif (!result.killed) {\n\t\t\t\t\tconst exit = task.exitCode !== undefined && task.exitCode !== null ? `, exit ${task.exitCode}` : \"\";\n\t\t\t\t\tthrow new Error(`Task ${task.id} is not running (status: ${task.status}${exit}).`);\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `Killed task ${task.id}. A completion notification follows once the process tree is reaped.`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { taskId: task.id, command: task.command },\n\t\t\t\t};\n\t\t\t},\n\t\t\trenderCall(args, theme) {\n\t\t\t\treturn renderBgKillCall(args, theme);\n\t\t\t},\n\t\t});\n\n\t\tpi.registerMessageRenderer<BgNotificationDetails>(BG_NOTIFICATION_TYPE, renderBackgroundNotification);\n\n\t\tpi.registerCommand(\"bg\", {\n\t\t\tdescription: \"View and manage background tasks\",\n\t\t\thandler: async (_args, ctx: ExtensionCommandContext) => {\n\t\t\t\tconst active = registry;\n\t\t\t\tif (!active || active.counts().total === 0) {\n\t\t\t\t\tctx.ui.notify(\"No background tasks.\", \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (ctx.mode !== \"tui\") {\n\t\t\t\t\t// RPC/print: ctx.ui.custom is unavailable — show a bounded summary.\n\t\t\t\t\tconst lines = active\n\t\t\t\t\t\t.listTasks()\n\t\t\t\t\t\t.slice(0, 10)\n\t\t\t\t\t\t.map((task) => describeTaskLine(task, Date.now()));\n\t\t\t\t\tctx.ui.notify(`Background tasks:\\n${lines.join(\"\\n\")}\\nUse bg_kill <id> to stop a task.`, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tawait showBackgroundManager(ctx, active);\n\t\t\t},\n\t\t});\n\t};\n}\n\nasync function showBackgroundManager(ctx: ExtensionCommandContext, registry: BackgroundTaskRegistry): Promise<void> {\n\t// No overlay options: the component mounts inline in the editor slot, like /model.\n\tawait ctx.ui.custom(\n\t\t(tui, theme, keybindings, done) =>\n\t\t\tnew BackgroundTasksMenu({\n\t\t\t\ttui,\n\t\t\t\ttheme,\n\t\t\t\tkeybindings,\n\t\t\t\thost: {\n\t\t\t\t\tlistTasks: () => registry.listTasks(),\n\t\t\t\t\tkillTask: (id) => registry.killTask(id),\n\t\t\t\t\treadSlice: (filePath, options) => readOutputSlice(filePath, options),\n\t\t\t\t},\n\t\t\t\tonClose: () => done(undefined),\n\t\t\t}),\n\t);\n}\n\nexport default createBackgroundExtension();\n"]}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /bg task manager — an inline, /model-style menu that mounts in the editor
|
|
3
|
+
* slot. Two views in one component: a task list, and a live output viewport
|
|
4
|
+
* for the selected task (Enter to open, Esc to go back). The host dependency
|
|
5
|
+
* is narrowed to three callbacks so the component stays testable.
|
|
6
|
+
*/
|
|
7
|
+
import { type Component, type Focusable } from "@earendil-works/pi-tui";
|
|
8
|
+
import type { KeybindingsManager } from "../../core/keybindings.ts";
|
|
9
|
+
import type { Theme } from "../../modes/interactive/theme/theme.ts";
|
|
10
|
+
import { type BgTask, type OutputSlice } from "./registry.ts";
|
|
11
|
+
export interface BackgroundManagerHost {
|
|
12
|
+
listTasks(): BgTask[];
|
|
13
|
+
killTask(id: string): {
|
|
14
|
+
killed: boolean;
|
|
15
|
+
};
|
|
16
|
+
readSlice(filePath: string, options: {
|
|
17
|
+
mode: "tail";
|
|
18
|
+
maxBytes: number;
|
|
19
|
+
}): Promise<OutputSlice>;
|
|
20
|
+
}
|
|
21
|
+
interface MenuTui {
|
|
22
|
+
requestRender(): void;
|
|
23
|
+
terminal: {
|
|
24
|
+
rows: number;
|
|
25
|
+
columns: number;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export interface BackgroundTasksMenuOptions {
|
|
29
|
+
tui: MenuTui;
|
|
30
|
+
theme: Theme;
|
|
31
|
+
keybindings: Pick<KeybindingsManager, "matches" | "getKeys">;
|
|
32
|
+
host: BackgroundManagerHost;
|
|
33
|
+
onClose: () => void;
|
|
34
|
+
pollIntervalMs?: number;
|
|
35
|
+
}
|
|
36
|
+
export declare class BackgroundTasksMenu implements Component, Focusable {
|
|
37
|
+
private readonly tui;
|
|
38
|
+
private readonly theme;
|
|
39
|
+
private readonly keybindings;
|
|
40
|
+
private readonly host;
|
|
41
|
+
private readonly onClose;
|
|
42
|
+
private readonly pollIntervalMs;
|
|
43
|
+
private view;
|
|
44
|
+
private tasks;
|
|
45
|
+
private selectedTaskId;
|
|
46
|
+
private listScrollTop;
|
|
47
|
+
private follow;
|
|
48
|
+
private tailOffsetLines;
|
|
49
|
+
private tailCache;
|
|
50
|
+
private killFeedback;
|
|
51
|
+
private pollTimer;
|
|
52
|
+
private readBusy;
|
|
53
|
+
private disposed;
|
|
54
|
+
private _focused;
|
|
55
|
+
constructor(options: BackgroundTasksMenuOptions);
|
|
56
|
+
get focused(): boolean;
|
|
57
|
+
set focused(value: boolean);
|
|
58
|
+
invalidate(): void;
|
|
59
|
+
dispose(): void;
|
|
60
|
+
handleInput(data: string): void;
|
|
61
|
+
private handleListInput;
|
|
62
|
+
private handleDetailInput;
|
|
63
|
+
render(width: number): string[];
|
|
64
|
+
private renderList;
|
|
65
|
+
private renderTaskRow;
|
|
66
|
+
private countsLabel;
|
|
67
|
+
private renderDetail;
|
|
68
|
+
private detailHeader;
|
|
69
|
+
private detailBodyHeight;
|
|
70
|
+
private footerLine;
|
|
71
|
+
private selectedTask;
|
|
72
|
+
private moveSelection;
|
|
73
|
+
private resetFollow;
|
|
74
|
+
private currentLines;
|
|
75
|
+
private maxTailOffset;
|
|
76
|
+
private killSelected;
|
|
77
|
+
private tick;
|
|
78
|
+
private refreshViewport;
|
|
79
|
+
}
|
|
80
|
+
export {};
|
|
81
|
+
//# sourceMappingURL=manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../../src/extensions/background/manager.ts"],"names":[],"mappings":"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"}
|