diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 417a8824..279ac926 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,20 @@ ## [Unreleased] +### Breaking Changes + +- **Renamed `/clear` to `/new`**: The command to start a fresh session is now `/new`. Hook event reasons `before_clear`/`clear` are now `before_new`/`new`. Merry Christmas [@mitsuhiko](https://github.com/mitsuhiko)! ([#305](https://github.com/badlogic/pi-mono/pull/305)) + +### Added + +- **Auto-space before pasted file paths**: When pasting a file path (starting with `/`, `~`, or `.`) after a word character, a space is automatically prepended. ([#307](https://github.com/badlogic/pi-mono/pull/307) by [@mitsuhiko](https://github.com/mitsuhiko)) +- **Word navigation in input fields**: Added Ctrl+Left/Right and Alt+Left/Right for word-by-word cursor movement. ([#306](https://github.com/badlogic/pi-mono/pull/306) by [@kim0](https://github.com/kim0)) +- **Full Unicode input**: Input fields now accept Unicode characters beyond ASCII. ([#306](https://github.com/badlogic/pi-mono/pull/306) by [@kim0](https://github.com/kim0)) + +### Fixed + +- **Readline-style Ctrl+W**: Now skips trailing whitespace before deleting the preceding word, matching standard readline behavior. ([#306](https://github.com/badlogic/pi-mono/pull/306) by [@kim0](https://github.com/kim0)) + ## [0.28.0] - 2025-12-25 ### Changed diff --git a/packages/coding-agent/README.md b/packages/coding-agent/README.md index 415e21cf..2b404b14 100644 --- a/packages/coding-agent/README.md +++ b/packages/coding-agent/README.md @@ -197,7 +197,7 @@ The agent reads, writes, and edits files, and executes commands via bash. | `/resume` | Switch to a different session (interactive selector) | | `/login` | OAuth login for subscription-based models | | `/logout` | Clear OAuth tokens | -| `/clear` | Clear context and start fresh session | +| `/new` | Start a new session | | `/copy` | Copy last agent message to clipboard | | `/compact [instructions]` | Manually compact conversation context | | `/autocompact` | Toggle automatic context compaction | diff --git a/packages/coding-agent/docs/custom-tools.md b/packages/coding-agent/docs/custom-tools.md index d63d404a..854a9619 100644 --- a/packages/coding-agent/docs/custom-tools.md +++ b/packages/coding-agent/docs/custom-tools.md @@ -186,7 +186,7 @@ interface ToolSessionEvent { entries: SessionEntry[]; // All session entries sessionFile: string | null; // Current session file previousSessionFile: string | null; // Previous session file - reason: "start" | "switch" | "branch" | "clear"; + reason: "start" | "switch" | "branch" | "new"; } ``` @@ -194,7 +194,7 @@ interface ToolSessionEvent { - `start`: Initial session load on startup - `switch`: User switched to a different session (`/resume`) - `branch`: User branched from a previous message (`/branch`) -- `clear`: User cleared the session (`/clear`) +- `new`: User started a new session (`/new`) ### State Management Pattern @@ -250,7 +250,7 @@ const factory: CustomToolFactory = (pi) => { This pattern ensures: - When user branches, state is correct for that point in history - When user switches sessions, state matches that session -- When user clears, state resets +- When user starts a new session, state resets ## Custom Rendering diff --git a/packages/coding-agent/docs/hooks.md b/packages/coding-agent/docs/hooks.md index 1a044bdb..2a07999c 100644 --- a/packages/coding-agent/docs/hooks.md +++ b/packages/coding-agent/docs/hooks.md @@ -125,10 +125,10 @@ user switches session (/resume) ├─► session (reason: "before_switch", can cancel) └─► session (reason: "switch", AFTER switch) -user clears session (/clear) +user starts new session (/new) │ - ├─► session (reason: "before_clear", can cancel) - └─► session (reason: "clear", AFTER clear) + ├─► session (reason: "before_new", can cancel) + └─► session (reason: "new", AFTER new session starts) context compaction (auto or /compact) │ @@ -151,12 +151,12 @@ pi.on("session", async (event, ctx) => { // event.entries: SessionEntry[] - all session entries // event.sessionFile: string | null - current session file (null with --no-session) // event.previousSessionFile: string | null - previous session file - // event.reason: "start" | "before_switch" | "switch" | "before_clear" | "clear" | + // event.reason: "start" | "before_switch" | "switch" | "before_new" | "new" | // "before_branch" | "branch" | "before_compact" | "compact" | "shutdown" // event.targetTurnIndex: number - only for "before_branch" and "branch" // Cancel a before_* action: - if (event.reason === "before_clear") { + if (event.reason === "before_new") { return { cancel: true }; } @@ -171,7 +171,7 @@ pi.on("session", async (event, ctx) => { **Reasons:** - `start`: Initial session load on startup - `before_switch` / `switch`: User switched sessions (`/resume`) -- `before_clear` / `clear`: User cleared the session (`/clear`) +- `before_new` / `new`: User started a new session (`/new`) - `before_branch` / `branch`: User branched the session (`/branch`) - `before_compact` / `compact`: Context compaction (auto or `/compact`) - `shutdown`: Process is exiting (double Ctrl+C, Ctrl+D, or SIGTERM) @@ -848,9 +848,9 @@ Session switch: Clear: -> AgentSession.reset() - -> hookRunner.emit({ type: "session", reason: "before_clear", ... }) # can cancel - -> [if not cancelled: clear happens] - -> hookRunner.emit({ type: "session", reason: "clear", ... }) + -> hookRunner.emit({ type: "session", reason: "before_new", ... }) # can cancel + -> [if not cancelled: new session starts] + -> hookRunner.emit({ type: "session", reason: "new", ... }) Shutdown (interactive mode): -> handleCtrlC() or handleCtrlD() diff --git a/packages/coding-agent/examples/hooks/confirm-destructive.ts b/packages/coding-agent/examples/hooks/confirm-destructive.ts index 7f80de23..61293868 100644 --- a/packages/coding-agent/examples/hooks/confirm-destructive.ts +++ b/packages/coding-agent/examples/hooks/confirm-destructive.ts @@ -10,7 +10,7 @@ import type { HookAPI } from "@mariozechner/pi-coding-agent/hooks"; export default function (pi: HookAPI) { pi.on("session", async (event, ctx) => { // Only handle before_* events (the ones that can be cancelled) - if (event.reason === "before_clear") { + if (event.reason === "before_new") { if (!ctx.hasUI) return; const confirmed = await ctx.ui.confirm( diff --git a/packages/coding-agent/examples/hooks/dirty-repo-guard.ts b/packages/coding-agent/examples/hooks/dirty-repo-guard.ts index ab5105c4..8e6e5d66 100644 --- a/packages/coding-agent/examples/hooks/dirty-repo-guard.ts +++ b/packages/coding-agent/examples/hooks/dirty-repo-guard.ts @@ -10,7 +10,7 @@ import type { HookAPI } from "@mariozechner/pi-coding-agent/hooks"; export default function (pi: HookAPI) { pi.on("session", async (event, ctx) => { // Only guard destructive actions - if (event.reason !== "before_clear" && event.reason !== "before_switch" && event.reason !== "before_branch") { + if (event.reason !== "before_new" && event.reason !== "before_switch" && event.reason !== "before_branch") { return; } @@ -36,11 +36,7 @@ export default function (pi: HookAPI) { const changedFiles = stdout.trim().split("\n").filter(Boolean).length; const action = - event.reason === "before_clear" - ? "clear session" - : event.reason === "before_switch" - ? "switch session" - : "branch"; + event.reason === "before_new" ? "new session" : event.reason === "before_switch" ? "switch session" : "branch"; const choice = await ctx.ui.select(`You have ${changedFiles} uncommitted file(s). ${action} anyway?`, [ "Yes, proceed anyway", diff --git a/packages/coding-agent/src/core/agent-session.ts b/packages/coding-agent/src/core/agent-session.ts index 4c5d4810..ea9a513c 100644 --- a/packages/coding-agent/src/core/agent-session.ts +++ b/packages/coding-agent/src/core/agent-session.ts @@ -517,14 +517,14 @@ export class AgentSession { const previousSessionFile = this.sessionFile; const entries = this.sessionManager.getEntries(); - // Emit before_clear event (can be cancelled) + // Emit before_new event (can be cancelled) if (this._hookRunner?.hasHandlers("session")) { const result = (await this._hookRunner.emit({ type: "session", entries, sessionFile: this.sessionFile, previousSessionFile: null, - reason: "before_clear", + reason: "before_new", })) as SessionEventResult | undefined; if (result?.cancel) { @@ -539,7 +539,7 @@ export class AgentSession { this._queuedMessages = []; this._reconnectToAgent(); - // Emit session event with reason "clear" to hooks + // Emit session event with reason "new" to hooks if (this._hookRunner) { this._hookRunner.setSessionFile(this.sessionFile); await this._hookRunner.emit({ @@ -547,12 +547,12 @@ export class AgentSession { entries: [], sessionFile: this.sessionFile, previousSessionFile, - reason: "clear", + reason: "new", }); } // Emit session event to custom tools - await this._emitToolSessionEvent("clear", previousSessionFile); + await this._emitToolSessionEvent("new", previousSessionFile); return true; } diff --git a/packages/coding-agent/src/core/custom-tools/types.ts b/packages/coding-agent/src/core/custom-tools/types.ts index bc4d1d74..b5ccc591 100644 --- a/packages/coding-agent/src/core/custom-tools/types.ts +++ b/packages/coding-agent/src/core/custom-tools/types.ts @@ -51,10 +51,10 @@ export interface SessionEvent { entries: SessionEntry[]; /** Current session file path, or null in --no-session mode */ sessionFile: string | null; - /** Previous session file path, or null for "start" and "clear" */ + /** Previous session file path, or null for "start" and "new" */ previousSessionFile: string | null; /** Reason for the session event */ - reason: "start" | "switch" | "branch" | "clear"; + reason: "start" | "switch" | "branch" | "new"; } /** Rendering options passed to renderResult */ diff --git a/packages/coding-agent/src/core/hooks/types.ts b/packages/coding-agent/src/core/hooks/types.ts index f88f6736..a60d009b 100644 --- a/packages/coding-agent/src/core/hooks/types.ts +++ b/packages/coding-agent/src/core/hooks/types.ts @@ -99,7 +99,7 @@ interface SessionEventBase { entries: SessionEntry[]; /** Current session file path, or null in --no-session mode */ sessionFile: string | null; - /** Previous session file path, or null for "start" and "clear" */ + /** Previous session file path, or null for "start" and "new" */ previousSessionFile: string | null; } @@ -110,7 +110,7 @@ interface SessionEventBase { * Lifecycle: * - start: Initial session load * - before_switch / switch: Session switch (e.g., /resume command) - * - before_clear / clear: Session clear (e.g., /clear command) + * - before_new / new: New session (e.g., /new command) * - before_branch / branch: Session branch (e.g., /branch command) * - before_compact / compact: Before/after context compaction * - shutdown: Process exit (SIGINT/SIGTERM) @@ -120,7 +120,7 @@ interface SessionEventBase { */ export type SessionEvent = | (SessionEventBase & { - reason: "start" | "switch" | "clear" | "before_switch" | "before_clear" | "shutdown"; + reason: "start" | "switch" | "new" | "before_switch" | "before_new" | "shutdown"; }) | (SessionEventBase & { reason: "branch" | "before_branch"; diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index 33e9fcee..228148d5 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -169,7 +169,7 @@ export class InteractiveMode { { name: "logout", description: "Logout from OAuth provider" }, { name: "queue", description: "Select message queue mode (opens selector UI)" }, { name: "theme", description: "Select color theme (opens selector UI)" }, - { name: "clear", description: "Clear context and start a fresh session" }, + { name: "new", description: "Start a new session" }, { name: "compact", description: "Manually compact the session context" }, { name: "autocompact", description: "Toggle automatic context compaction" }, { name: "resume", description: "Resume a different session" }, @@ -672,7 +672,7 @@ export class InteractiveMode { this.editor.setText(""); return; } - if (text === "/clear") { + if (text === "/new") { this.editor.setText(""); await this.handleClearCommand(); return; @@ -1843,9 +1843,7 @@ export class InteractiveMode { this.isFirstUserMessage = true; this.chatContainer.addChild(new Spacer(1)); - this.chatContainer.addChild( - new Text(`${theme.fg("accent", "✓ Context cleared")}\n${theme.fg("muted", "Started fresh session")}`, 1, 1), - ); + this.chatContainer.addChild(new Text(`${theme.fg("accent", "✓ New session started")}`, 1, 1)); this.ui.requestRender(); } diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md new file mode 100644 index 00000000..764aaf9a --- /dev/null +++ b/packages/tui/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +## [Unreleased] + +### Added + +- **Auto-space before pasted file paths**: When pasting a file path (starting with `/`, `~`, or `.`) and the cursor is after a word character, a space is automatically prepended for better readability. Useful when dragging screenshots from macOS. ([#307](https://github.com/badlogic/pi-mono/pull/307) by [@mitsuhiko](https://github.com/mitsuhiko)) +- **Word navigation for Input component**: Added Ctrl+Left/Right and Alt+Left/Right support for word-by-word cursor movement. ([#306](https://github.com/badlogic/pi-mono/pull/306) by [@kim0](https://github.com/kim0)) +- **Full Unicode input**: Input component now accepts Unicode characters beyond ASCII. ([#306](https://github.com/badlogic/pi-mono/pull/306) by [@kim0](https://github.com/kim0)) + +### Fixed + +- **Readline-style Ctrl+W**: Now skips trailing whitespace before deleting the preceding word, matching standard readline behavior. ([#306](https://github.com/badlogic/pi-mono/pull/306) by [@kim0](https://github.com/kim0))