Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/coding-agent/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@
### Added

- Added silent background self-updates on every startup for global npm, pnpm, yarn, and bun installs. Pi checks npm for a newer release and updates in the background without blocking startup or showing output.
- Added a `quit` tool that lets the agent gracefully shut down the application.

### Changed

- Removed all user-facing ways to stop or exit the agent: double Ctrl+C no longer exits, Ctrl+D no longer exits, Escape no longer aborts streaming/bash/retry/compaction, and the `/quit` command is gone. The agent can quit via the `quit` tool.

### Removed

- Removed interactive slash commands `/export`, `/import`, `/session`, `/changelog`, `/debug`, `/arminsayshi`, and `/dementedelves`.
- Removed the `app.exit` keybinding (Ctrl+D exit).

## [0.83.0] - 2026-09-01

Expand Down
3 changes: 1 addition & 2 deletions packages/coding-agent/docs/keybindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,8 @@ Modifier combinations: `ctrl+shift+x`, `alt+ctrl+x`, `ctrl+shift+alt+x`, `ctrl+1

| Keybinding id | Default | Description |
|--------|---------|-------------|
| `app.interrupt` | `escape` | Cancel / abort |
| `app.interrupt` | `escape` | Cancel autocomplete |
| `app.clear` | `ctrl+c` | Clear editor |
| `app.exit` | `ctrl+d` | Exit (when editor empty) |
| `app.suspend` | `ctrl+z` (none on Windows) | Suspend to background |
| `app.editor.external` | `ctrl+g` | Open in external editor (`externalEditor`, `$VISUAL`, `$EDITOR`, Notepad on Windows, or `nano` elsewhere) |
| `app.clipboard.pasteImage` | `ctrl+v` (`alt+v` on Windows) | Paste image from clipboard |
Expand Down
2 changes: 0 additions & 2 deletions packages/coding-agent/docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,13 @@ Type `/` in the editor to open command completion. Extensions can register custo
| `/reload` | Reload keybindings, extensions, skills, prompts, and context files |
| `/hotkeys` | Show all keyboard shortcuts |
| `/changelog` | Display version history |
| `/quit` | Quit pi |

## Message Queue

You can submit messages while the agent is still working:

- **Enter** queues a steering message, delivered after the current assistant turn finishes executing its tool calls.
- **Alt+Enter** queues a follow-up message, delivered after the agent finishes all work.
- **Escape** aborts and restores queued messages to the editor.
- **Alt+Up** retrieves queued messages back to the editor.

On Windows Terminal, Alt+Enter is fullscreen by default. Remap it as described in [Terminal setup](terminal-setup.md) if you want pi to receive the shortcut.
Expand Down
2 changes: 1 addition & 1 deletion packages/coding-agent/src/core/agent-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2408,7 +2408,7 @@ export class AgentSession {

const defaultActiveToolNames = this._baseToolsOverride
? Object.keys(this._baseToolsOverride)
: ["read", "bash", "edit", "write"];
: ["read", "bash", "edit", "write", "quit"];
const baseActiveToolNames = options.activeToolNames ?? defaultActiveToolNames;
this._refreshToolRegistry({
activeToolNames: baseActiveToolNames,
Expand Down
1 change: 0 additions & 1 deletion packages/coding-agent/src/core/extensions/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ import type {
const RESERVED_KEYBINDINGS_FOR_EXTENSION_CONFLICTS = [
"app.interrupt",
"app.clear",
"app.exit",
"app.suspend",
"app.thinking.cycle",
"app.tools.expand",
Expand Down
5 changes: 1 addition & 4 deletions packages/coding-agent/src/core/keybindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { getAgentDir } from "../config.ts";
export interface AppKeybindings {
"app.interrupt": true;
"app.clear": true;
"app.exit": true;
"app.suspend": true;
"app.thinking.cycle": true;
"app.tools.expand": true;
Expand Down Expand Up @@ -53,9 +52,8 @@ declare module "@southbag/code-tui" {

export const KEYBINDINGS = {
...TUI_KEYBINDINGS,
"app.interrupt": { defaultKeys: "escape", description: "Cancel or abort" },
"app.interrupt": { defaultKeys: "escape", description: "Cancel autocomplete" },
"app.clear": { defaultKeys: "ctrl+c", description: "Clear editor" },
"app.exit": { defaultKeys: "ctrl+d", description: "Exit when editor is empty" },
"app.suspend": {
defaultKeys: process.platform === "win32" ? [] : "ctrl+z",
description: "Suspend to background",
Expand Down Expand Up @@ -193,7 +191,6 @@ const KEYBINDING_NAME_MIGRATIONS = {
selectCancel: "tui.select.cancel",
interrupt: "app.interrupt",
clear: "app.clear",
exit: "app.exit",
suspend: "app.suspend",
cycleThinkingLevel: "app.thinking.cycle",
expandTools: "app.tools.expand",
Expand Down
1 change: 0 additions & 1 deletion packages/coding-agent/src/core/slash-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,4 @@ export const BUILTIN_SLASH_COMMANDS: ReadonlyArray<BuiltinSlashCommand> = [
{ name: "resume", description: "Resume a different session" },
{ name: "reload", description: "Reload keybindings, extensions, skills, prompts, and themes" },
{ name: "logout", description: `Log out and quit ${APP_NAME}` },
{ name: "quit", description: `Quit ${APP_NAME}` },
];
18 changes: 16 additions & 2 deletions packages/coding-agent/src/core/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ export {
type LsToolInput,
type LsToolOptions,
} from "./ls.ts";
export {
createQuitTool,
createQuitToolDefinition,
type QuitToolInput,
} from "./quit.ts";
export {
createReadTool,
createReadToolDefinition,
Expand Down Expand Up @@ -75,13 +80,14 @@ import { createEditTool, createEditToolDefinition, type EditToolOptions } from "
import { createFindTool, createFindToolDefinition, type FindToolOptions } from "./find.ts";
import { createGrepTool, createGrepToolDefinition, type GrepToolOptions } from "./grep.ts";
import { createLsTool, createLsToolDefinition, type LsToolOptions } from "./ls.ts";
import { createQuitTool, createQuitToolDefinition } from "./quit.ts";
import { createReadTool, createReadToolDefinition, type ReadToolOptions } from "./read.ts";
import { createWriteTool, createWriteToolDefinition, type WriteToolOptions } from "./write.ts";

export type Tool = AgentTool<any>;
export type ToolDef = ToolDefinition<any, any>;
export type ToolName = "read" | "bash" | "edit" | "write" | "grep" | "find" | "ls";
export const allToolNames: Set<ToolName> = new Set(["read", "bash", "edit", "write", "grep", "find", "ls"]);
export type ToolName = "read" | "bash" | "edit" | "write" | "grep" | "find" | "ls" | "quit";
export const allToolNames: Set<ToolName> = new Set(["read", "bash", "edit", "write", "grep", "find", "ls", "quit"]);

export interface ToolsOptions {
read?: ReadToolOptions;
Expand Down Expand Up @@ -109,6 +115,8 @@ export function createToolDefinition(toolName: ToolName, cwd: string, options?:
return createFindToolDefinition(cwd, options?.find);
case "ls":
return createLsToolDefinition(cwd, options?.ls);
case "quit":
return createQuitToolDefinition();
default:
throw new Error(`Unknown tool name: ${toolName}`);
}
Expand All @@ -130,6 +138,8 @@ export function createTool(toolName: ToolName, cwd: string, options?: ToolsOptio
return createFindTool(cwd, options?.find);
case "ls":
return createLsTool(cwd, options?.ls);
case "quit":
return createQuitTool();
default:
throw new Error(`Unknown tool name: ${toolName}`);
}
Expand All @@ -141,6 +151,7 @@ export function createCodingToolDefinitions(cwd: string, options?: ToolsOptions)
createBashToolDefinition(cwd, options?.bash),
createEditToolDefinition(cwd, options?.edit),
createWriteToolDefinition(cwd, options?.write),
createQuitToolDefinition(),
];
}

Expand All @@ -162,6 +173,7 @@ export function createAllToolDefinitions(cwd: string, options?: ToolsOptions): R
grep: createGrepToolDefinition(cwd, options?.grep),
find: createFindToolDefinition(cwd, options?.find),
ls: createLsToolDefinition(cwd, options?.ls),
quit: createQuitToolDefinition(),
};
}

Expand All @@ -171,6 +183,7 @@ export function createCodingTools(cwd: string, options?: ToolsOptions): Tool[] {
createBashTool(cwd, options?.bash),
createEditTool(cwd, options?.edit),
createWriteTool(cwd, options?.write),
createQuitTool(),
];
}

Expand All @@ -192,5 +205,6 @@ export function createAllTools(cwd: string, options?: ToolsOptions): Record<Tool
grep: createGrepTool(cwd, options?.grep),
find: createFindTool(cwd, options?.find),
ls: createLsTool(cwd, options?.ls),
quit: createQuitTool(),
};
}
30 changes: 30 additions & 0 deletions packages/coding-agent/src/core/tools/quit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { AgentTool } from "@southbag/code-core";
import { type Static, Type } from "typebox";
import { APP_NAME } from "../../config.ts";
import type { ToolDefinition } from "../extensions/types.ts";
import { wrapToolDefinition } from "./tool-definition-wrapper.ts";

const quitSchema = Type.Object({});

export type QuitToolInput = Static<typeof quitSchema>;

export function createQuitToolDefinition(): ToolDefinition<typeof quitSchema, undefined> {
return {
name: "quit",
label: "quit",
description: `Quit ${APP_NAME}. Gracefully shuts down the application and terminates the process.`,
promptSnippet: `Quit ${APP_NAME}`,
parameters: quitSchema,
async execute(_toolCallId, _params, _signal, _onUpdate, _ctx) {
process.kill(process.pid, "SIGTERM");
return {
content: [{ type: "text", text: `Quitting ${APP_NAME}...` }],
details: undefined,
};
},
};
}

export function createQuitTool(): AgentTool<typeof quitSchema> {
return wrapToolDefinition(createQuitToolDefinition());
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ export class CustomEditor extends Editor {

// Special handlers that can be dynamically replaced
public onEscape?: () => void;
public onCtrlD?: () => void;
public onPasteImage?: () => void;
/** Handler for extension-registered shortcuts. Returns true if handled. */
public onExtensionShortcut?: (data: string) => boolean;
Expand Down Expand Up @@ -56,19 +55,9 @@ export class CustomEditor extends Editor {
return;
}

// Exit (Ctrl+D) - only when editor is empty
if (this.keybindings.matches(data, "app.exit")) {
if (this.getText().length === 0) {
const handler = this.onCtrlD ?? this.actionHandlers.get("app.exit");
if (handler) handler();
return;
}
// Fall through to editor handling for delete-char-forward when not empty
}

// Check all other app actions
for (const [action, handler] of this.actionHandlers) {
if (action !== "app.interrupt" && action !== "app.exit" && this.keybindings.matches(data, action)) {
if (action !== "app.interrupt" && this.keybindings.matches(data, action)) {
handler();
return;
}
Expand Down
Loading
Loading