@agnishc/edb-agent-steer 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ### Added
6
+ - Initial release: steer / queue / discard / edit prompt for mid-turn messages
7
+ - `/steer <text>` command for direct steering without the prompt
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Agnish Chakraborty
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # @agnishc/edb-agent-steer
2
+
3
+ A Pi CLI extension that intercepts messages sent while the agent is already running and presents a compact single-keypress prompt:
4
+
5
+ ```
6
+ ↵ "your message here"
7
+
8
+ s steer q queue d discard e edit
9
+ ```
10
+
11
+ | Action | Behaviour |
12
+ |--------|-----------|
13
+ | `s` steer | Delivers the message before the next LLM call (same turn) |
14
+ | `q` queue | Delivers the message after the agent fully finishes |
15
+ | `d` discard | Throws the message away |
16
+ | `e` edit / Esc | Restores the text to the editor |
17
+
18
+ When the agent is **idle**, messages pass through normally — no prompt appears.
19
+
20
+ Also registers a `/steer <text>` command that bypasses the prompt and steers directly.
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ pi install npm:@agnishc/edb-agent-steer
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ Just type while the agent is running. The prompt appears automatically.
31
+
32
+ ```
33
+ /steer reconsider the approach — use a queue instead
34
+ ```
35
+
36
+ ## License
37
+
38
+ [MIT](LICENSE) © Agnish Chakraborty
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@agnishc/edb-agent-steer",
3
+ "version": "0.1.0",
4
+ "description": "Pi extension: intercepts mid-turn messages with a steer / queue / discard / edit prompt",
5
+ "keywords": ["pi-package", "pi-extension", "edb"],
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "author": "Agnish Chakraborty",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/agnishcc/pi-extention-monorepo.git",
12
+ "directory": "packages/edb-agent-steer"
13
+ },
14
+ "homepage": "https://github.com/agnishcc/pi-extention-monorepo/tree/main/packages/edb-agent-steer#readme",
15
+ "bugs": { "url": "https://github.com/agnishcc/pi-extention-monorepo/issues" },
16
+ "publishConfig": { "access": "public" },
17
+ "scripts": { "test": "vitest run" },
18
+ "files": ["src", "README.md", "LICENSE", "CHANGELOG.md"],
19
+ "pi": {
20
+ "extensions": ["./src/index.ts"]
21
+ },
22
+ "peerDependencies": {
23
+ "@mariozechner/pi-coding-agent": "*",
24
+ "@mariozechner/pi-tui": "*"
25
+ }
26
+ }
@@ -0,0 +1,52 @@
1
+ import { matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
2
+ import type { SteerChoice } from "./types";
3
+
4
+ // ── Component ──────────────────────────────────────────────────────────────────
5
+
6
+ export class SteerPromptComponent {
7
+ private cachedWidth?: number;
8
+ private cachedLines?: string[];
9
+
10
+ constructor(
11
+ private readonly message: string,
12
+ private readonly theme: any,
13
+ private readonly done: (choice: SteerChoice) => void,
14
+ ) {}
15
+
16
+ handleInput(data: string): void {
17
+ if (matchesKey(data, "s")) this.done("steer");
18
+ else if (matchesKey(data, "q")) this.done("queue");
19
+ else if (matchesKey(data, "d")) this.done("discard");
20
+ else if (matchesKey(data, "e") || matchesKey(data, "escape")) this.done("edit");
21
+ }
22
+
23
+ render(width: number): string[] {
24
+ if (this.cachedLines && this.cachedWidth === width) return this.cachedLines;
25
+
26
+ const th = this.theme;
27
+ const maxMsgW = Math.max(10, width - 8);
28
+ const preview = this.message.length > maxMsgW ? `${this.message.slice(0, maxMsgW - 1)}…` : this.message;
29
+
30
+ const key = (k: string, label: string) => `${th.fg("accent", k)}${th.fg("dim", ` ${label}`)}`;
31
+
32
+ const lines: string[] = [
33
+ "",
34
+ truncateToWidth(` ${th.fg("muted", "↵")} ${th.fg("dim", `"${preview}"`)}`, width),
35
+ "",
36
+ truncateToWidth(
37
+ ` ${key("s", "steer")} ${key("q", "queue")} ${key("d", "discard")} ${key("e", "edit")}`,
38
+ width,
39
+ ),
40
+ "",
41
+ ];
42
+
43
+ this.cachedWidth = width;
44
+ this.cachedLines = lines;
45
+ return lines;
46
+ }
47
+
48
+ invalidate(): void {
49
+ this.cachedWidth = undefined;
50
+ this.cachedLines = undefined;
51
+ }
52
+ }
package/src/index.ts ADDED
@@ -0,0 +1,93 @@
1
+ /**
2
+ * pi-agent-steer
3
+ *
4
+ * When the user submits a message while the agent is already running, a
5
+ * compact inline prompt appears showing four single-key options:
6
+ *
7
+ * s steer — deliver before the next LLM call (same session)
8
+ * q queue — deliver after the agent fully finishes
9
+ * d discard — throw the message away
10
+ * e edit — restore the text to the editor (also: Esc)
11
+ *
12
+ * A single keypress acts immediately — no Enter, no dialog, no Esc conflicts.
13
+ * When the agent is idle the message is passed through normally — no prompt.
14
+ * The /steer <text> slash command skips the prompt entirely and always steers directly.
15
+ */
16
+
17
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
18
+ import { SteerPromptComponent } from "./component";
19
+ import type { SteerChoice } from "./types";
20
+
21
+ // ── Extension ──────────────────────────────────────────────────────────────────
22
+
23
+ export default function steerExtension(pi: ExtensionAPI): void {
24
+ // ── Input interceptor ─────────────────────────────────────────────────────
25
+ pi.on("input", async (event, ctx) => {
26
+ // Only intercept typed messages, not extension-injected ones.
27
+ if (event.source !== "interactive") return { action: "continue" };
28
+
29
+ // Don't intercept empty input — Esc in the editor fires input with
30
+ // event.text === "" and we must not block that.
31
+ if (!event.text?.trim()) return { action: "continue" };
32
+
33
+ // Agent is idle — pass through normally.
34
+ if (ctx.isIdle()) return { action: "continue" };
35
+
36
+ // No interactive UI (print / JSON mode) — pass through.
37
+ if (!ctx.hasUI) return { action: "continue" };
38
+
39
+ // Show the single-keypress prompt.
40
+ const choice = await ctx.ui.custom<SteerChoice>(
41
+ (_tui, theme, _kb, done) => new SteerPromptComponent(event.text, theme, done),
42
+ );
43
+
44
+ // edit / Esc / unexpected null → restore text to editor, do nothing else.
45
+ if (!choice || choice === "edit") {
46
+ ctx.ui.setEditorText(event.text);
47
+ return { action: "handled" };
48
+ }
49
+
50
+ if (choice === "discard") {
51
+ ctx.ui.notify("Message discarded", "info");
52
+ return { action: "handled" };
53
+ }
54
+
55
+ if (choice === "steer") {
56
+ pi.sendUserMessage(event.text, { deliverAs: "steer" });
57
+ ctx.ui.notify("◀ Steered — before the next LLM call", "info");
58
+ return { action: "handled" };
59
+ }
60
+
61
+ if (choice === "queue") {
62
+ pi.sendUserMessage(event.text, { deliverAs: "followUp" });
63
+ ctx.ui.notify("⏳ Queued — after agent finishes", "info");
64
+ return { action: "handled" };
65
+ }
66
+
67
+ return { action: "continue" };
68
+ });
69
+
70
+ // ── /steer slash command — explicit steer, bypasses the prompt ────────────
71
+ pi.registerCommand("steer", {
72
+ description: "Steer a message directly into the running agent turn. " + "Usage: /steer <text>",
73
+
74
+ handler: async (args, ctx) => {
75
+ const content = args?.trim();
76
+
77
+ if (!content) {
78
+ ctx.ui.notify("/steer <text> — steer text before the next LLM call", "info");
79
+ return;
80
+ }
81
+
82
+ const running = !ctx.isIdle();
83
+
84
+ if (running) {
85
+ pi.sendUserMessage(content, { deliverAs: "steer" });
86
+ } else {
87
+ pi.sendUserMessage(content);
88
+ }
89
+
90
+ ctx.ui.notify(running ? "◀ Steered — before the next LLM call" : "◀ Steered — triggering response", "info");
91
+ },
92
+ });
93
+ }
package/src/types.ts ADDED
@@ -0,0 +1,3 @@
1
+ // ── Types ──────────────────────────────────────────────────────────────────────
2
+
3
+ export type SteerChoice = "steer" | "queue" | "discard" | "edit";