shariq-pi-extensions 0.2.6 → 0.2.8

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 CHANGED
@@ -31,6 +31,7 @@ The package contains:
31
31
  - Firecrawl search and scraping
32
32
  - Git status UI
33
33
  - persistent task goals
34
+ - configurable steer, interrupt, or follow-up input behavior
34
35
  - dedicated multi-agent orchestration
35
36
  - Pi Memory
36
37
  - per-response TPS, TTFT, elapsed-time, and output status
@@ -38,7 +38,7 @@ The goal extension adds persistent, branch-safe objectives, progress evidence, b
38
38
 
39
39
  The subagent extension runs flat Pi child agents with profiles, capability policies, continuation, result delivery, optional worktrees, and a dashboard. Configuration lives in `<agent-dir>/subagents.json`; trusted projects may override it through their Pi config directory. The configured concurrency ceiling is 50.
40
40
 
41
- The extension supplies tools including `spawn_agent`, `task`, `check_agent`, `list_agents`, `wait_agent`, `send_message`, `close_agent`, `reply_question`, and `apply_agent_changes`. Child settlement is handed to Pi immediately as a turn-triggering follow-up, so Pi queues it while the parent is active or starts a new parent turn when idle; status tools are for explicit inspection, not waiting.
41
+ The extension supplies tools including `spawn_agent`, `task`, `check_agent`, `list_agents`, `wait_agent`, `send_message`, `close_agent`, `reply_question`, and `apply_agent_changes`. Child settlement is handed to Pi immediately as an extension-originated user follow-up, so Pi queues it while the parent is active or starts a new parent turn when idle with the summary guaranteed in model context; status tools are for explicit inspection, not waiting.
42
42
 
43
43
  ### [Orchestration](../extensions/orchestration/README.md)
44
44
 
@@ -50,7 +50,7 @@ The model-facing `create_orchestration` tool starts planning only after an expli
50
50
 
51
51
  Managed PTYs support servers, watchers, long builds, downloads, and interactive processes. The extension tracks up to eight concurrent terminals, retains bounded output, stores full logs in restrictive temporary directories, and stops process groups during shutdown or reload.
52
52
 
53
- Its tools are `start_terminal`, `read_terminal`, `write_terminal`, `list_terminals`, and `stop_terminal`. A model-started terminal immediately hands its completion or failure to Pi as a turn-triggering follow-up, which Pi queues while the parent is active or uses to start a new parent turn when idle. Reading a terminal does not suppress that delivery; agents should inspect only for explicit progress requests or immediate interaction.
53
+ Its tools are `start_terminal`, `read_terminal`, `write_terminal`, `list_terminals`, and `stop_terminal`. A model-started terminal immediately hands its completion or failure to Pi as an extension-originated user follow-up, which Pi queues while the parent is active or uses to start a new parent turn when idle with the bounded output guaranteed in model context. Reading a terminal does not suppress that delivery; agents should inspect only for explicit progress requests or immediate interaction.
54
54
 
55
55
  ## Web access
56
56
 
@@ -80,6 +80,10 @@ Adds repository state to Pi's interface. `/lg` opens the local Git view and `/pr
80
80
 
81
81
  `/copy-all` copies the current conversation in a readable form while omitting tool protocol noise that does not belong in the transcript.
82
82
 
83
+ ### [Input mode](../extensions/input-mode/README.md)
84
+
85
+ `/input-mode` chooses what ordinary Enter does while Pi is running: steer before the next model step, interrupt the active run, or wait as a follow-up. The global selection lives in `<agent-dir>/input-mode.json`; explicit Alt+Enter follow-ups and non-interactive inputs retain native behavior. Pi does not expose extension rows in core `/settings`, so this is a dedicated extension settings picker rather than a private TUI patch.
86
+
83
87
  ### [Shell shortcuts](../extensions/shell-shortcuts/README.md)
84
88
 
85
89
  Adds `/exit` as an alias for Pi's normal quit command. Keep this extension limited to small, low-risk conveniences.
@@ -10,7 +10,7 @@ Session-scoped background pseudo-terminals for Pi. The extension combines Codex-
10
10
  - `list_terminals` — list running and settled terminals.
11
11
  - `stop_terminal` — stop complete process groups with TERM-to-KILL escalation.
12
12
 
13
- Each output response carries a byte cursor. Pass it to the next read/write operation to avoid repeating output. Long or uncertain commands should use `start_terminal` instead of a large blocking `bash` timeout. Settlement is handed to Pi immediately: it queues a follow-up while the parent is active or starts a new parent turn when idle, so the parent can continue other work or end its turn rather than poll.
13
+ Each output response carries a byte cursor. Pass it to the next read/write operation to avoid repeating output. Long or uncertain commands should use `start_terminal` instead of a large blocking `bash` timeout. Settlement is handed to Pi immediately as an extension-originated user follow-up: it queues while the parent is active or starts a new parent turn when idle with bounded output guaranteed in model context, so the parent can continue other work or end its turn rather than poll.
14
14
 
15
15
  ## User interface
16
16
 
@@ -34,7 +34,7 @@ Each output response carries a byte cursor. Pass it to the next read/write opera
34
34
  - Output is sanitized before TUI or model rendering.
35
35
  - Processes run in their own PTY process group and are stopped on session shutdown, replacement, or reload.
36
36
  - Shutdown and stop operations are bounded and escalate from SIGTERM to SIGKILL.
37
- - Model-started terminals immediately hand one completion/failure follow-up to Pi; Pi queues it while the parent is active or starts the next parent turn when idle.
37
+ - Model-started terminals immediately hand one model-visible completion/failure follow-up to Pi; Pi queues it while the parent is active or starts the next parent turn when idle.
38
38
  - Reading settled output does not consume or suppress the automatic completion delivery.
39
39
  - Completion delivery is keyed by terminal id to prevent duplicate follow-ups.
40
40
 
@@ -0,0 +1,13 @@
1
+ # Input mode
2
+
3
+ Controls what ordinary **Enter** does when the interactive Pi agent is already running:
4
+
5
+ - `steer` (default) queues the message before the agent's next model step.
6
+ - `interrupt` signals Pi's active abort controller immediately, then preserves the submitted text, images, prompt-template expansion, and normal delivery as the next input.
7
+ - `follow-up` queues the message until the active run finishes.
8
+
9
+ Use `/input-mode` for the picker or `/input-mode steer|interrupt|follow-up` for direct selection. The global choice is stored with restrictive permissions in `<agent-dir>/input-mode.json`; non-default modes appear in Pi's status area.
10
+
11
+ Pi does not expose an extension API for adding rows to its built-in `/settings` selector, so this extension owns a dedicated settings command rather than patching private TUI internals. The existing core **Steering mode** and **Follow-up mode** settings remain batching controls (`all` versus `one-at-a-time`).
12
+
13
+ Explicit Alt+Enter follow-ups, extension-originated messages, commands, idle input, compaction input, and RPC input retain Pi's native behavior. Interrupt cancellation is cooperative: Pi stops model streaming and abort-aware tools, but it cannot undo an external side effect that already completed or force a third-party operation that ignores its abort signal to stop.
@@ -0,0 +1,46 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
4
+
5
+ export const INPUT_MODES = ["steer", "interrupt", "follow-up"] as const;
6
+ export type InputMode = (typeof INPUT_MODES)[number];
7
+
8
+ interface InputModeDocument {
9
+ version: 1;
10
+ mode: InputMode;
11
+ }
12
+
13
+ export function inputModePath(): string {
14
+ return path.join(getAgentDir(), "input-mode.json");
15
+ }
16
+
17
+ export function isInputMode(value: unknown): value is InputMode {
18
+ return typeof value === "string" && INPUT_MODES.includes(value as InputMode);
19
+ }
20
+
21
+ export function loadInputMode(file = inputModePath()): InputMode {
22
+ try {
23
+ const document = JSON.parse(fs.readFileSync(file, "utf8")) as Partial<InputModeDocument>;
24
+ return document.version === 1 && isInputMode(document.mode) ? document.mode : "steer";
25
+ } catch {
26
+ return "steer";
27
+ }
28
+ }
29
+
30
+ export function saveInputMode(mode: InputMode, file = inputModePath()): void {
31
+ const directory = path.dirname(file);
32
+ fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
33
+ const temporary = `${file}.${process.pid}.${Date.now()}.tmp`;
34
+ const document: InputModeDocument = { version: 1, mode };
35
+ try {
36
+ fs.writeFileSync(temporary, `${JSON.stringify(document, null, 2)}\n`, { mode: 0o600 });
37
+ fs.renameSync(temporary, file);
38
+ fs.chmodSync(file, 0o600);
39
+ } finally {
40
+ try {
41
+ fs.rmSync(temporary, { force: true });
42
+ } catch {
43
+ // Best-effort cleanup after a failed atomic replacement.
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,104 @@
1
+ import type {
2
+ ExtensionAPI,
3
+ ExtensionCommandContext,
4
+ ExtensionUIContext,
5
+ InputEvent,
6
+ InputEventResult,
7
+ } from "@earendil-works/pi-coding-agent";
8
+ import {
9
+ INPUT_MODES,
10
+ isInputMode,
11
+ loadInputMode,
12
+ saveInputMode,
13
+ type InputMode,
14
+ } from "./config.ts";
15
+
16
+ const STATUS_KEY = "input-mode";
17
+
18
+ interface InputModeExtensionOptions {
19
+ configFile?: string;
20
+ }
21
+
22
+ function modelInput(event: InputEvent) {
23
+ if (!event.images?.length) return event.text;
24
+ return [{ type: "text" as const, text: event.text }, ...event.images];
25
+ }
26
+
27
+ export function createInputModeExtension(options: InputModeExtensionOptions = {}) {
28
+ return (pi: ExtensionAPI) => {
29
+ let mode = loadInputMode(options.configFile);
30
+ let ui: ExtensionUIContext | undefined;
31
+
32
+ const updateStatus = () => {
33
+ if (!ui) return;
34
+ ui.setStatus(STATUS_KEY, mode === "steer" ? undefined : `input: ${mode}`);
35
+ };
36
+
37
+ const selectMode = async (args: string, ctx: ExtensionCommandContext) => {
38
+ const requested = args.trim().toLowerCase();
39
+ let selected: InputMode | undefined;
40
+ if (requested) {
41
+ if (!isInputMode(requested)) {
42
+ ctx.ui.notify(`Usage: /input-mode [${INPUT_MODES.join("|")}]`, "warning");
43
+ return;
44
+ }
45
+ selected = requested;
46
+ } else if (ctx.hasUI) {
47
+ selected = await ctx.ui.select(
48
+ `Input behavior while agent is running (current: ${mode})`,
49
+ [...INPUT_MODES],
50
+ ) as InputMode | undefined;
51
+ } else {
52
+ ctx.ui.notify(`Input mode: ${mode}. Usage: /input-mode [${INPUT_MODES.join("|")}]`, "info");
53
+ return;
54
+ }
55
+ if (!selected) return;
56
+ mode = selected;
57
+ saveInputMode(mode, options.configFile);
58
+ updateStatus();
59
+ const explanation = mode === "interrupt"
60
+ ? "new Enter input aborts the active run before it is delivered"
61
+ : mode === "follow-up"
62
+ ? "new Enter input waits until the active run finishes"
63
+ : "new Enter input is injected before the agent's next step";
64
+ ctx.ui.notify(`Input mode: ${mode} — ${explanation}.`, "info");
65
+ };
66
+
67
+ pi.registerCommand("input-mode", {
68
+ description: "Choose Enter behavior while the agent runs: steer, interrupt, or follow-up",
69
+ handler: selectMode,
70
+ });
71
+
72
+ pi.on("session_start", (_event, ctx) => {
73
+ if (ctx.hasUI) ui = ctx.ui;
74
+ updateStatus();
75
+ });
76
+
77
+ pi.on("session_shutdown", () => {
78
+ ui?.setStatus(STATUS_KEY, undefined);
79
+ ui = undefined;
80
+ });
81
+
82
+ pi.on("input", (event, ctx): InputEventResult => {
83
+ // Extension-originated results and explicit Alt+Enter follow-ups retain
84
+ // their requested delivery. Idle input and commands use Pi unchanged.
85
+ if (event.source !== "interactive" || event.streamingBehavior !== "steer") {
86
+ return { action: "continue" };
87
+ }
88
+ if (mode === "steer") return { action: "continue" };
89
+ if (mode === "follow-up") {
90
+ pi.sendUserMessage(modelInput(event), { deliverAs: "followUp" });
91
+ return { action: "handled" };
92
+ }
93
+
94
+ // Abort is signalled synchronously. Returning continue preserves Pi's
95
+ // normal template expansion, image handling, history, and queue logic;
96
+ // the prompt becomes a fresh turn if abort settlement wins the race, or
97
+ // a steering message consumed immediately after the aborted run.
98
+ ctx.abort();
99
+ return { action: "continue" };
100
+ });
101
+ };
102
+ }
103
+
104
+ export default createInputModeExtension();
@@ -8,7 +8,7 @@ Internal runtime utilities used by more than one extension. This directory is no
8
8
  - `child-session.ts` owns trust-aware child resources and bounded session shutdown.
9
9
  - `context-utilization.ts` formats model-context usage and capacity.
10
10
  - `dashboard-state.ts` keeps list selection stable as live rows change.
11
- - `settlement-delivery.ts` immediately hands asynchronous results to Pi as turn-triggering follow-ups.
11
+ - `settlement-delivery.ts` immediately hands asynchronous results to Pi as extension-originated user follow-ups, guaranteeing model-visible settlement context.
12
12
  - `tool-call-timeout.ts` applies cancellation-aware execution limits to registered tools.
13
13
  - `tui-dashboard.ts` provides bounded, sanitized terminal-dashboard rendering helpers.
14
14
 
@@ -3,9 +3,17 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
  type SettlementMessage = Parameters<ExtensionAPI["sendMessage"]>[0];
4
4
 
5
5
  /**
6
- * Hand settlement to Pi immediately. Pi starts a turn when idle and queues the
7
- * same message as a follow-up when the parent is still running.
6
+ * Hand settlement to Pi as an extension-originated user follow-up. A custom
7
+ * message can wake the parent yet fail to appear in the invoked model turn;
8
+ * sendUserMessage guarantees that the bounded result is model-visible. Pi
9
+ * starts a turn when idle and queues the same input while the parent is active.
8
10
  */
9
11
  export function deliverSettlement(pi: ExtensionAPI, message: SettlementMessage): void {
10
- pi.sendMessage(message, { deliverAs: "followUp", triggerTurn: true });
12
+ const content = typeof message.content === "string"
13
+ ? message.content
14
+ : message.content
15
+ .filter((part) => part.type === "text")
16
+ .map((part) => part.text)
17
+ .join("\n");
18
+ pi.sendUserMessage(content, { deliverAs: "followUp" });
11
19
  }
@@ -19,7 +19,7 @@ The system is deliberately flat. Only the main Pi thread can spawn subagents. Ch
19
19
  - `reply_question` — answer a child’s blocking `ask_parent` request
20
20
  - `task` — atomically reserve capacity for up to the configured limit (maximum 50), start the fan-out in the background, and return child ids immediately
21
21
 
22
- Child sessions receive `message_parent`, `ask_parent`, `list_peers`, and `message_peer`. Peer messages are routed through the main-thread manager and can steer a running child or continue a settled one; they cannot create agents. Child settlement is handed to Pi immediately: it queues a follow-up while the parent is active or starts a new parent turn when idle, so the main turn can continue independent work or end and remain available to the user.
22
+ Child sessions receive `message_parent`, `ask_parent`, `list_peers`, and `message_peer`. Peer messages are routed through the main-thread manager and can steer a running child or continue a settled one; they cannot create agents. Child settlement is handed to Pi immediately as an extension-originated user follow-up: it queues while the parent is active or starts a new parent turn when idle with the summary guaranteed in model context, so the main turn can continue independent work or end and remain available to the user.
23
23
 
24
24
  ## Profiles and capabilities
25
25
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shariq-pi-extensions",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "Cross-platform extension suite for the Pi coding agent.",
5
5
  "license": "MIT",
6
6
  "author": "Shariq Riaz",
@@ -47,6 +47,7 @@
47
47
  "./extensions/firecrawl-web/index.ts",
48
48
  "./extensions/git-info/index.ts",
49
49
  "./extensions/goal/index.ts",
50
+ "./extensions/input-mode/index.ts",
50
51
  "./extensions/orchestration/index.ts",
51
52
  "./extensions/performance-status/index.ts",
52
53
  "./extensions/pi-memory/index.ts",
@@ -24,9 +24,9 @@ Provide:
24
24
  - the working directory when it differs from the current directory;
25
25
  - an initial wait only when startup output is needed for the next decision.
26
26
 
27
- After startup, continue only genuinely useful independent work. If none remains, end the turn immediately. Ending the turn is the waiting mechanism: terminal settlement is handed to Pi immediately, queued as a follow-up if the parent is still active, and otherwise starts the next parent turn with the final status and bounded output. Do not keep the current turn alive to wait, invent monitoring work, or call terminal tools merely to see whether the process finished.
27
+ After startup, continue only genuinely useful independent work. If none remains, end the turn immediately. Ending the turn is the waiting mechanism: terminal settlement is handed to Pi immediately as an extension-originated user follow-up, queued if the parent is still active, and otherwise starts the next parent turn with the final status and bounded output visible in model context. Do not keep the current turn alive to wait, invent monitoring work, or call terminal tools merely to see whether the process finished.
28
28
 
29
- When that completion follow-up invokes the next turn, treat its attached output as the terminal result and continue the original task immediately. Do not wait for another user message, announce that you are still waiting, or call `read_terminal` to retrieve the same result again. If `start_terminal` itself returns a settled result, the output is already synchronous and no second completion notice is needed.
29
+ When that completion follow-up invokes the next turn, treat its model-visible output as the terminal result and continue the original task immediately. Do not wait for another user message, announce that you are still waiting, or call `read_terminal` to retrieve the same result again. If `start_terminal` itself returns a settled result, the output is already synchronous and no second completion notice is needed.
30
30
 
31
31
  ## Inspect and interact only when necessary
32
32
 
@@ -24,14 +24,14 @@ This skill governs temporary Pi child agents. The `codex-thread-orchestrator` sk
24
24
 
25
25
  ## Wait by notification; inspect progress only when justified
26
26
 
27
- A successful `spawn_agent` or `task` call starts asynchronous work and returns control to the parent. When a child finishes, its settlement is handed to Pi immediately, queued as a follow-up if the parent is still active, and otherwise starts the next main-agent turn. The parent does not need to remain active or check once before ending its turn.
27
+ A successful `spawn_agent` or `task` call starts asynchronous work and returns control to the parent. When a child finishes, its settlement is handed to Pi immediately as an extension-originated user follow-up, queued if the parent is still active, and otherwise starts the next main-agent turn with the summary visible in model context. The parent does not need to remain active or check once before ending its turn.
28
28
 
29
29
  After dispatch:
30
30
 
31
31
  1. Continue only parent work that is independently useful to the requested result.
32
32
  2. If no such work remains, end the turn immediately. A short progress note is enough when the user needs one.
33
33
  3. Do not call `wait_agent`, `list_agents`, or `check_agent` in the same turn merely because the child was just launched. Ending the turn is the waiting mechanism.
34
- 4. When a child completion notice invokes the main agent, treat the attached summary as the child result and continue the original task immediately. Reconcile completed results, launch any intentionally queued work if capacity requires waves, and otherwise keep waiting through notifications. Do not wait for the user to prompt you again or call a status tool to retrieve the same result.
34
+ 4. When a child completion notice invokes the main agent, treat its model-visible summary as the child result and continue the original task immediately. Reconcile completed results, launch any intentionally queued work if capacity requires waves, and otherwise keep waiting through notifications. Do not wait for the user to prompt you again or call a status tool to retrieve the same result.
35
35
 
36
36
  A progress check is reasonable when the user asks for status, a child has run materially longer than expected for its task and model, an interruption left its state unclear, or current status will change an immediate coordination decision. Prefer `check_agent` for one known child and `list_agents` for a batch overview. Use `wait_agent` to collect results already expected to be available, not as a running-status probe.
37
37