From 5d97c7dabb6ae277946ffb1c05e770837f50843d Mon Sep 17 00:00:00 2001
From: ZouR-Ma <2605315944@qq.com>
Date: Mon, 19 Oct 2026 17:20:46 +0800
Subject: [PATCH 1/5] docs: remove upstream Discord and pi branding from
contributor docs
Drop the remaining Discord invite links from CONTRIBUTING and replace them
with in-issue guidance, since the tracker is the only supported channel.
Rebrand the coding-agent README in StepCode terms: drop the Discord badge,
rename the Pi Packages section to Packages, and document the shipped `step`
entrypoint instead of the upstream `pi` binary. Commands documented as
`pi install` / `pi update` and the `~/.pi/agent/` package paths do not match
what this package ships, so they now read `step` and `~/.stepcode/agent/`.
Keep the `pi` manifest key, the `pi-package` keyword, and the `~/.pi/agent/`
paths that still apply to plain `pi` invocations, and state that lineage in a
new section so the remaining upstream names are not mistaken for leftovers.
Replace the two upstream blog links with in-repo references.
---
CONTRIBUTING.md | 4 +-
packages/coding-agent/README.md | 119 +++++++++++++------------
packages/coding-agent/docs/packages.md | 2 +-
packages/coding-agent/docs/usage.md | 2 +-
4 files changed, 65 insertions(+), 62 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c23cf7ae..eee8f442 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -48,7 +48,7 @@ If you use an agent, run it from the repository root directory so it picks up `A
## Issue Review
-Issues submitted Friday through Sunday are not guaranteed to be reviewed. If something is urgent, ask on Discord: https://discord.com/invite/3cU7Bz4UPx
+Issues submitted Friday through Sunday are not guaranteed to be reviewed. If something is urgent, say so at the top of the issue and explain the impact.
## Quality Bar For Issues
@@ -108,7 +108,7 @@ If you are adding a new provider to `packages/providers`, see `AGENTS.md` for re
### Why are weekend issues lower priority?
-We triage the tracker during working hours. That means more issues can accumulate over the weekend. Anything submitted Friday through Sunday may be missed or given lower priority in the Monday review queue. If a problem is urgent, ask on Discord and include the short version, a repro, and the relevant logs.
+We triage the tracker during working hours. That means more issues can accumulate over the weekend. Anything submitted Friday through Sunday may be missed or given lower priority in the Monday review queue. If a problem is urgent, flag it in the issue and include the short version, a repro, and the relevant logs.
### Why do some issues get no reply?
diff --git a/packages/coding-agent/README.md b/packages/coding-agent/README.md
index ef73fed0..ddbc42d5 100644
--- a/packages/coding-agent/README.md
+++ b/packages/coding-agent/README.md
@@ -1,15 +1,18 @@
-
---
-Pi is a minimal terminal coding harness. Adapt pi to your workflows, not the other way around, without having to fork and modify pi internals. Extend it with TypeScript [Extensions](#extensions), [Skills](#skills), [Prompt Templates](#prompt-templates), and [Themes](#themes). Put your extensions, skills, prompt templates, and themes in [Pi Packages](#pi-packages) and share them with others via npm or git.
+`@step-harness/coding-agent` is the StepCode runtime: a minimal terminal coding harness. Adapt it to your workflows, not the other way around, without forking and modifying the internals. Extend it with TypeScript [Extensions](#extensions), [Skills](#skills), [Prompt Templates](#prompt-templates), and [Themes](#themes). Put your extensions, skills, prompt templates, and themes in a [StepCode package](#packages) and share it with others via npm or git.
-Pi ships with powerful defaults but skips features like sub agents and plan mode. Instead, you can ask pi to build what you want or install a third party pi package that matches your workflow.
+StepCode ships with powerful defaults but skips opinionated features like sub agents and plan mode. Instead, ask it to build what you want, or install a third-party package that matches your workflow.
-Pi runs in four modes: interactive, print or JSON, RPC for process integration, and an SDK for embedding in your own apps.
+StepCode runs in four modes: interactive, print or JSON, RPC for process integration, and an SDK for embedding in your own apps.
+
+### Upstream lineage and naming
+
+This runtime is derived from the upstream pi coding agent. `pi` therefore still appears where it is a compatibility contract rather than product branding: the `pi` manifest key that the package loader reads from `package.json`, the `pi-package` discovery keyword on published packages, and the `~/.pi/agent/` / `.pi/` paths below, which apply when you invoke plain `pi`. The shipped product entrypoint is `step`, and under it the config and package root is `~/.stepcode/`.
## Table of Contents
@@ -30,7 +33,7 @@ Pi runs in four modes: interactive, print or JSON, RPC for process integration,
- [Skills](#skills)
- [Extensions](#extensions)
- [Themes](#themes)
- - [Pi Packages](#pi-packages)
+ - [Packages](#packages)
- [Programmatic Usage](#programmatic-usage)
- [Philosophy](#philosophy)
- [CLI Reference](#cli-reference)
@@ -43,23 +46,23 @@ Pi runs in four modes: interactive, print or JSON, RPC for process integration,
npm install -g --ignore-scripts @step-harness/coding-agent
```
-`--ignore-scripts` disables dependency lifecycle scripts during install. Pi does not require install scripts for normal npm installs.
+`--ignore-scripts` disables dependency lifecycle scripts during install. StepCode does not require install scripts for normal npm installs.
Authenticate with an API key:
```bash
export STEP_API_KEY=...
-pi
+step
```
Or use your existing subscription:
```bash
-pi
+step
/login # Then select provider
```
-Then just talk to pi. By default, pi gives the model four tools: `read`, `write`, `edit`, and `bash`. The model uses these to fulfill your requests. Add capabilities via [skills](#skills), [prompt templates](#prompt-templates), [extensions](#extensions), or [pi packages](#pi-packages).
+Then just talk to StepCode. By default, it gives the model four tools: `read`, `write`, `edit`, and `bash`. The model uses these to fulfill your requests. Add capabilities via [skills](#skills), [prompt templates](#prompt-templates), [extensions](#extensions), or [packages](#packages).
**Platform notes:** [Windows](docs/windows.md) | [Termux (Android)](docs/termux.md) | [tmux](docs/tmux.md) | [Terminal setup](docs/terminal-setup.md) | [Shell aliases](docs/shell-aliases.md)
@@ -273,7 +276,7 @@ Review this code for bugs, security issues, and performance problems.
Focus on: {{focus}}
```
-Place in `~/.pi/agent/prompts/`, `.pi/prompts/`, or a [pi package](#pi-packages) to share with others. See [docs/prompt-templates.md](docs/prompt-templates.md).
+Place in `~/.pi/agent/prompts/`, `.pi/prompts/`, or a [package](#packages) to share with others. See [docs/prompt-templates.md](docs/prompt-templates.md).
### Skills
@@ -289,7 +292,7 @@ Use this skill when the user asks about X.
2. Then that
```
-Place in `~/.pi/agent/skills/`, `~/.pi/skills/`, `~/.agents/skills/`, `~/.claude/skills/`, `.pi/skills/`, `.agents/skills/`, or `.claude/skills/` (the last two from `cwd` up through parent directories) or a [pi package](#pi-packages) to share with others. See [docs/skills.md](docs/skills.md).
+Place in `~/.pi/agent/skills/`, `~/.pi/skills/`, `~/.agents/skills/`, `~/.claude/skills/`, `.pi/skills/`, `.agents/skills/`, or `.claude/skills/` (the last two from `cwd` up through parent directories) or a [package](#packages) to share with others. See [docs/skills.md](docs/skills.md).
### Extensions
@@ -321,13 +324,13 @@ The default export can also be `async`. pi waits for async extension factories b
- Games while waiting (yes, Doom runs)
- ...anything you can dream up
-Place in `~/.pi/agent/extensions/`, `.pi/extensions/`, or a [pi package](#pi-packages) to share with others. See [docs/extensions.md](docs/extensions.md) and [examples/extensions/](examples/extensions/).
+Place in `~/.pi/agent/extensions/`, `.pi/extensions/`, or a [package](#packages) to share with others. See [docs/extensions.md](docs/extensions.md) and [examples/extensions/](examples/extensions/).
### Themes
Built-in: `dark`, `light`, `sage`, `step-blue`, `step-violet`, `step-violet-light`. Themes hot-reload: modify the active theme file and pi immediately applies changes.
-Place in `~/.pi/agent/themes/`, `.pi/themes/`, or a [pi package](#pi-packages) to share with others. See [docs/themes.md](docs/themes.md).
+Place in `~/.pi/agent/themes/`, `.pi/themes/`, or a [package](#packages) to share with others. See [docs/themes.md](docs/themes.md).
### Step entrypoint
@@ -345,39 +348,39 @@ Step-specific behavior is registered as an extension (`/init`, provider login,
and branding). The editor, input protocol, selectors, overlays, queueing, and
agent lifecycle remain pi-owned.
-### Pi Packages
+### Packages
-Bundle and share extensions, skills, prompts, and themes via npm or git. Find packages on [npmjs.com](https://www.npmjs.com/search?q=keywords%3Api-package) or [Discord](https://discord.com/channels/1456806362351669492/1457744485428629628).
+Bundle and share extensions, skills, prompts, and themes via npm or git. Find packages on [npmjs.com](https://www.npmjs.com/search?q=keywords%3Api-package).
-> **Security:** Pi packages run with full system access. Extensions execute arbitrary code, and skills can instruct the model to perform any action including running executables. Review source code before installing third-party packages.
+> **Security:** StepCode packages run with full system access. Extensions execute arbitrary code, and skills can instruct the model to perform any action including running executables. Review source code before installing third-party packages.
```bash
-pi install npm:@foo/pi-tools
-pi install npm:@foo/pi-tools@1.2.3 # pinned version
-pi install git:github.com/user/repo
-pi install git:github.com/user/repo@v1 # tag or commit
-pi install git:git@github.com:user/repo
-pi install git:git@github.com:user/repo@v1 # tag or commit
-pi install https://github.com/user/repo
-pi install https://github.com/user/repo@v1 # tag or commit
-pi install ssh://git@github.com/user/repo
-pi install ssh://git@github.com/user/repo@v1 # tag or commit
-pi remove npm:@foo/pi-tools
-pi uninstall npm:@foo/pi-tools # alias for remove
-pi list
-pi update # update pi only
-pi update --all # update pi and packages
-pi update --extensions # update packages only
-pi update --models # refresh model catalogs only
-pi update --self # update pi only
-pi update --self --force # reinstall pi even if current
-pi update npm:@foo/pi-tools # update one package
-pi config # enable/disable extensions, skills, prompts, themes
+step install npm:@foo/pi-tools
+step install npm:@foo/pi-tools@1.2.3 # pinned version
+step install git:github.com/user/repo
+step install git:github.com/user/repo@v1 # tag or commit
+step install git:git@github.com:user/repo
+step install git:git@github.com:user/repo@v1 # tag or commit
+step install https://github.com/user/repo
+step install https://github.com/user/repo@v1 # tag or commit
+step install ssh://git@github.com/user/repo
+step install ssh://git@github.com/user/repo@v1 # tag or commit
+step remove npm:@foo/pi-tools
+step uninstall npm:@foo/pi-tools # alias for remove
+step list
+step update # update StepCode only
+step update --all # update StepCode and packages
+step update --extensions # update packages only
+step update --models # refresh model catalogs only
+step update --self # update StepCode only
+step update --self --force # reinstall StepCode even if current
+step update npm:@foo/pi-tools # update one package
+step config # enable/disable extensions, skills, prompts, themes
```
-Packages install to `~/.pi/agent/git/` (git) or `~/.pi/agent/npm/` (npm). Use `-l` for project-local installs (`.pi/git/`, `.pi/npm/`). Git `@ref` values are pinned tags or commits; pinned packages are skipped by `pi update --extensions` and `pi update --all`, so use `pi install git:host/user/repo@new-ref` to move an existing package to a new ref. Git packages install dependencies with `npm install --omit=dev` by default, so runtime deps must be listed under `dependencies`; when `npmCommand` is configured, git packages use plain `install` for compatibility with wrappers. If you use a Node version manager and want package installs to reuse a stable npm context, set `npmCommand` in `settings.json`, for example `["mise", "exec", "node@20", "--", "npm"]`.
+Packages install to `~/.stepcode/agent/git/` (git) or `~/.stepcode/agent/npm/` (npm); plain `pi` invocations use `~/.pi/agent/`. Use `-l` for project-local installs (`.stepcode/git/`, `.stepcode/npm/`). Git `@ref` values are pinned tags or commits; pinned packages are skipped by `step update --extensions` and `step update --all`, so use `step install git:host/user/repo@new-ref` to move an existing package to a new ref. Git packages install dependencies with `npm install --omit=dev` by default, so runtime deps must be listed under `dependencies`; when `npmCommand` is configured, git packages use plain `install` for compatibility with wrappers. If you use a Node version manager and want package installs to reuse a stable npm context, set `npmCommand` in `settings.json`, for example `["mise", "exec", "node@20", "--", "npm"]`.
-Create a package by adding a `pi` key to `package.json`:
+Create a package by adding a `pi` manifest key to `package.json`. The key keeps its upstream name so already-published packages stay discoverable:
```json
{
@@ -392,7 +395,7 @@ Create a package by adding a `pi` key to `package.json`:
}
```
-Without a `pi` manifest, pi auto-discovers from conventional directories (`extensions/`, `skills/`, `prompts/`, `themes/`).
+Without a `pi` manifest, StepCode auto-discovers from conventional directories (`extensions/`, `skills/`, `prompts/`, `themes/`).
See [docs/packages.md](docs/packages.md).
@@ -434,11 +437,11 @@ See [docs/rpc.md](docs/rpc.md) for the protocol.
## Philosophy
-Pi is aggressively extensible so it doesn't have to dictate your workflow. Features that other tools bake in can be built with [extensions](#extensions), [skills](#skills), or installed from third-party [pi packages](#pi-packages). This keeps the core minimal while letting you shape pi to fit how you work.
+StepCode is aggressively extensible so it doesn't have to dictate your workflow. Features that other tools bake in can be built with [extensions](#extensions), [skills](#skills), or installed from third-party [packages](#packages). This keeps the core minimal while letting you shape StepCode to fit how you work.
-**No MCP.** Build CLI tools with READMEs (see [Skills](#skills)), or build an extension that adds MCP support. [Why?](https://mariozechner.at/posts/2025-11-02-what-if-you-dont-need-mcp/)
+**No MCP.** Build CLI tools with READMEs (see [Skills](#skills)), or build an extension that adds MCP support.
-**No sub-agents.** There's many ways to do this. Spawn pi instances via tmux, or build your own with [extensions](#extensions), or install a package that does it your way.
+**No sub-agents.** There's many ways to do this. Spawn StepCode instances via tmux, or build your own with [extensions](#extensions), or install a package that does it your way.
**No permission popups.** Run in a container, or build your own confirmation flow with [extensions](#extensions) inline with your environment and security requirements.
@@ -448,34 +451,34 @@ Pi is aggressively extensible so it doesn't have to dictate your workflow. Featu
**No background bash.** Use tmux. Full observability, direct interaction.
-Read the [blog post](https://mariozechner.at/posts/2025-11-30-pi-coding-agent/) for the full rationale.
+This design follows the rationale of the upstream pi coding agent.
---
## CLI Reference
```bash
-pi [options] [--] [@files...] [messages...]
+step [options] [--] [@files...] [messages...]
```
### Package Commands
```bash
-pi install [-l] # Install package, -l for project-local
-pi remove [-l] # Remove package
-pi uninstall [-l] # Alias for remove
-pi update [source|self|pi] # Update pi only, or one package source
-pi update --all # Update pi and packages
-pi update --extensions # Update packages only
-pi update --models # Refresh model catalogs only
-pi update --self # Update pi only
-pi update --self --force # Reinstall pi even if current
-pi update --extension # Update one package
-pi list # List installed packages
-pi config # Enable/disable package resources
+step install [-l] # Install package, -l for project-local
+step remove [-l] # Remove package
+step uninstall [-l] # Alias for remove
+step update [source|self|step] # Update StepCode only, or one package source
+step update --all # Update StepCode and packages
+step update --extensions # Update packages only
+step update --models # Refresh model catalogs only
+step update --self # Update StepCode only
+step update --self --force # Reinstall StepCode even if current
+step update --extension # Update one package
+step list # List installed packages
+step config # Enable/disable package resources
```
-`pi config` and project package commands accept `--approve`/`--no-approve` to trust or ignore project-local settings for one command. `pi update` never prompts for project trust.
+`step config` and project package commands accept `--approve`/`--no-approve` to trust or ignore project-local settings for one command. `step update` never prompts for project trust.
### Modes
diff --git a/packages/coding-agent/docs/packages.md b/packages/coding-agent/docs/packages.md
index 8097b124..1100fdc3 100644
--- a/packages/coding-agent/docs/packages.md
+++ b/packages/coding-agent/docs/packages.md
@@ -115,7 +115,7 @@ Local paths point to files or directories on disk and are added to settings with
## Creating a Step Package
-Add a `step` manifest to `package.json` or use conventional directories. Include the `pi-package` keyword for discoverability.
+Add a `pi` manifest to `package.json` or use conventional directories. Include the `pi-package` keyword for discoverability. Both names are kept for compatibility with already-published packages.
```json
{
diff --git a/packages/coding-agent/docs/usage.md b/packages/coding-agent/docs/usage.md
index 4fdebeae..b423bc8e 100644
--- a/packages/coding-agent/docs/usage.md
+++ b/packages/coding-agent/docs/usage.md
@@ -306,4 +306,4 @@ Step keeps the core small and pushes workflow-specific behavior into extensions,
It intentionally does not include built-in MCP, sub-agents, permission popups, plan mode, to-dos, or background bash. You can build or install those workflows as extensions or packages, or use external tools such as containers and tmux.
-For the full rationale, read the [blog post](https://mariozechner.at/posts/2025-11-30-pi-coding-agent/).
+For the full rationale, see [the philosophy section of the README](../README.md#philosophy).
From 1ec2b42b8c1aac2aec8ec96500708feb51b571e5 Mon Sep 17 00:00:00 2001
From: ZouR-Ma <2605315944@qq.com>
Date: Mon, 19 Oct 2026 18:34:49 +0800
Subject: [PATCH 2/5] docs: replace residual upstream pi branding with step
naming
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Rewrite prose, CLI examples, paths, and extension snippets across all
Markdown docs so the product surface reads as `step`. Extension sample
factories rename the `ExtensionAPI` factory parameter from `pi` to `step`;
the parameter name is caller-chosen and does not affect the contract.
Identifiers that the code actually contracts on are deliberately kept:
telemetry span and attribute names (`pi.ai.*`, `pi.harness.*`, `pi.session.*`),
the `pi` package manifest key and `pi-package` discovery keyword, the published
`@step-harness/pi-tui` package, third-party repository names, and the MIT
attribution files.
Two documentation defects found while verifying claims against source:
- `docs/tui-rendering-pipeline.md` §8 documented a table of `PI_*` environment
variables that do not exist anywhere in `packages/tui/src`. Replaced with the
real entry points: `setIncrementalRenderDisabled()`, the `showHardwareCursor`
and `terminal.clearOnShrink` settings, and `resolveEscapeTimeoutMs()`, whose
ESC threshold is derived from `SSH_CONNECTION`/`SSH_TTY` with no override.
Also corrected the crash log file name (`tui-crash.log`, not `pi-crash.log`),
the `write()` path (a plain `process.stdout.write`, with no file side channel),
and the `renderDirtyStart` claims.
- `docs/step-integration.md` Distribution claimed npm and the standalone
archives ship both a `pi` and a `step` executable. Only `step` exists in the
`bin` field and in the release output; corrected.
---
docs/plan-loop-cron-workflow-hoh.md | 4 +-
docs/step-configuration.md | 2 +-
docs/step-unified-config-and-mcp.md | 18 +-
docs/tui-rendering-pipeline.md | 36 +-
packages/agent-core/docs/harness.md | 2 +-
packages/agent-core/docs/search.md | 2 +-
packages/agent-core/docs/telemetry-schema.md | 2 +-
packages/coding-agent/README.md | 146 +++----
packages/coding-agent/docs/compaction.md | 8 +-
packages/coding-agent/docs/custom-provider.md | 42 +-
packages/coding-agent/docs/development.md | 4 +-
packages/coding-agent/docs/extensions.md | 380 +++++++++---------
packages/coding-agent/docs/mcp-catalog.md | 2 +-
packages/coding-agent/docs/models.md | 4 +-
packages/coding-agent/docs/packages.md | 4 +-
.../coding-agent/docs/prompt-templates.md | 2 +-
packages/coding-agent/docs/rpc.md | 6 +-
packages/coding-agent/docs/sdk.md | 16 +-
packages/coding-agent/docs/session-format.md | 14 +-
packages/coding-agent/docs/settings.md | 6 +-
packages/coding-agent/docs/skills.md | 4 +-
.../coding-agent/docs/step-integration.md | 104 ++---
packages/coding-agent/docs/themes.md | 2 +-
packages/coding-agent/docs/tui.md | 20 +-
packages/coding-agent/examples/README.md | 2 +-
.../examples/extensions/README.md | 22 +-
.../extensions/doom-overlay/README.md | 6 +-
.../examples/extensions/subagent/README.md | 26 +-
packages/coding-agent/examples/sdk/README.md | 4 +-
packages/providers/README.md | 16 +-
packages/telemetry/README.md | 14 +-
packages/tui/docs/editor-styling.md | 2 +-
32 files changed, 464 insertions(+), 458 deletions(-)
diff --git a/docs/plan-loop-cron-workflow-hoh.md b/docs/plan-loop-cron-workflow-hoh.md
index b0f8dfec..dfefc889 100644
--- a/docs/plan-loop-cron-workflow-hoh.md
+++ b/docs/plan-loop-cron-workflow-hoh.md
@@ -17,7 +17,7 @@ Step 目前擅长在一个 turn 内完成工作,也已经有 session、subagen
| Cron | 在指定时间执行提醒或任务 | project/session 可见 | `durable=true` 时跨 session |
| Workflow | 一次调用编排多个 agent 和阶段 | 单次 run,可 resume | run journal 与产物落盘 |
-三者都可以把通知送入 Pi 已有的 custom-message/steer 通道,但共享通道不等于
+三者都可以把通知送入运行时已有的 custom-message/steer 通道,但共享通道不等于
共享生命周期或共享实现。任一方向都必须能够在没有另外两个方向时编译、加载、测试
和关闭。
@@ -40,7 +40,7 @@ Step 目前擅长在一个 turn 内完成工作,也已经有 session、subagen
扩展通过 `ExtensionAPI.sendMessage` 发送 custom message:
```ts
-pi.sendMessage(
+step.sendMessage(
{
customType: "step-wakeup" | "step-cron" | "step-workflow",
content: [{ type: "text", text: "[wakeup] ..." }],
diff --git a/docs/step-configuration.md b/docs/step-configuration.md
index 5291256e..201bed16 100644
--- a/docs/step-configuration.md
+++ b/docs/step-configuration.md
@@ -14,7 +14,7 @@ The retired `step-settings.json` and `settings.json` files are no longer read,
written, or covered by the project trust prompt. A project that still ships one
is ignored; move any settings it holds into the project `config.toml`.
-There is no automatic import from the pre-pi `config.json` layout. `models.json`
+There is no automatic import from the legacy pre-Step `config.json` layout. `models.json`
and `auth.json` keep their own formats — they hold model definitions and
credentials, not settings — and a stale Step endpoint recorded in `models.json`
by an older release is still repaired in place at startup.
diff --git a/docs/step-unified-config-and-mcp.md b/docs/step-unified-config-and-mcp.md
index d34c99a2..c38ed27d 100644
--- a/docs/step-unified-config-and-mcp.md
+++ b/docs/step-unified-config-and-mcp.md
@@ -15,7 +15,7 @@
| 来源 | 内容 |
| --- | --- |
-| `~/.stepcode/agent/settings.json` | Pi 原生设置(主题、默认 provider/model、thinking、compaction…) |
+| `~/.stepcode/agent/settings.json` | 运行时原生设置(主题、默认 provider/model、thinking、compaction…) |
| `~/.stepcode/agent/step-settings.json` | Step 产品设置(permissionPreset、approvalMode、autoResume、feedbackEnabled…) |
| 无 | MCP 没有配置位置 |
@@ -26,12 +26,12 @@
/.stepcode/config.toml # 项目级(可选)
```
-三类内容并存于同一文档:Pi 原生设置在根表,Step 产品设置在根表,MCP 在
+三类内容并存于同一文档:运行时原生设置在根表,Step 产品设置在根表,MCP 在
`[mcp_servers.]` 子表(Codex 风格)。
关键点:
-- **单一真相**:`createStepSettingsManager` 把 Pi 的 `SettingsManager` 与 Step
+- **单一真相**:`createStepSettingsManager` 把 运行时的 `SettingsManager` 与 Step
装饰器指向同一份路径。任何在 CLI 内改的设置(`/theme`、权限预设、审批模式等)
都写回 `~/.stepcode/config.toml`,`step` 二进制**不再创建** `settings.json`。
- **项目文件可选**:缺失即"无项目级设置",不报警告、不自动创建;只有显式保存
@@ -127,12 +127,12 @@
- `readStepConfig(path)` —— `readFileSync` 放在 `try` **之外**,让 `ENOENT` 原样
抛出,调用方才能区分"文件不存在"和"TOML 非法"。
- `writeStepConfig(path, doc)` —— 写 `path..tmp` 再 `renameSync`,保证原子。
-- `readGlobalStepDefaults(env)` —— 在 Pi 的 SettingsManager 建立之前同步读取
+- `readGlobalStepDefaults(env)` —— 在 运行时的 SettingsManager 建立之前同步读取
`defaultProvider` / `defaultModel` / `telemetry.*`。这一步必须存在:静默丢弃
`telemetry.enabled = false` 等于把用户关掉的上报又打开。
- `updateGlobalMcpConfig(env, fn)` —— 读改写整体包在 `acquireSettingsLockSync` 内。
-- `StepTomlSettingsStorage` —— 实现 Pi 的 `SettingsStorage` 接口,把 TOML 文档
- (剥掉 `mcp_servers`)以 JSON 字符串形式交给 Pi,写回时再把 `mcp_servers`
+- `StepTomlSettingsStorage` —— 实现 运行时的 `SettingsStorage` 接口,把 TOML 文档
+ (剥掉 `mcp_servers`)以 JSON 字符串形式交给运行时,写回时再把 `mcp_servers`
合并回去。
### 3.2 `src/step/mcp.ts`(+294 行)
@@ -239,7 +239,7 @@ if (ctx.mode !== "tui") await startup; // 仅 TUI 分离
### 4.2 设置写入与 `mcp_servers` 互相覆盖
-Pi 的 `SettingsStorage.withLock` 只认识"设置"这一层,它拿到的字符串里没有
+运行时的 `SettingsStorage.withLock` 只认识"设置"这一层,它拿到的字符串里没有
`mcp_servers`。如果读、改、写不在同一个临界区,另一个会话在中间写入就会丢失;而且
写回时必须把**同一次读**里的 `mcp_servers` 合并回去,否则 `/theme` 一改就会把用户
所有 MCP 配置抹掉。
@@ -291,7 +291,7 @@ agent 目录本身,而不是把凭据写到宿主指定的命名空间之外。
```
~/.stepcode/
-├── config.toml # ★ 统一配置:Pi 原生设置 + Step 产品设置 + [mcp_servers]
+├── config.toml # ★ 统一配置:运行时原生设置 + Step 产品设置 + [mcp_servers]
├── auth.json # ★ Step provider 凭据(0600),由 step login 写入
├── models.json # ★ 模型目录/覆盖(从 agent/ 上提)
├── .credentials.json # ★ MCP OAuth 令牌(0600),key = "|"
@@ -309,7 +309,7 @@ agent 目录本身,而不是把凭据写到宿主指定的命名空间之外。
├── sessions/ # 顶层会话记录
├── skills/ # 用户级 skills
├── telemetry/ # 遥测缓冲(spool)
-└── agent/ # Pi agent 命名空间
+└── agent/ # agent 命名空间
├── prompts/ # 用户自定义 prompts
├── sessions/ # agent 会话记录
├── trust.json # 项目信任存储(ProjectTrustStore)
diff --git a/docs/tui-rendering-pipeline.md b/docs/tui-rendering-pipeline.md
index 9ed4a2ab..b2479fd1 100644
--- a/docs/tui-rendering-pipeline.md
+++ b/docs/tui-rendering-pipeline.md
@@ -55,7 +55,7 @@
退出时反向恢复:关进度、关 bracketed paste、`\x1b[ width` 直接把全部行写进 `pi-crash.log`,清理终端状态后抛错——指向"组件没截断"这个根因。
+10. 硬保险(`tui-main-screen.ts:651-678`):任何渲染行 `visibleWidth > width` 直接把全部行写进 `tui-crash.log`,清理终端状态后抛错——指向"组件没截断"这个根因。
11. 更新镜像:`previousLines`、`hardwareCursorRow`、`previousViewportTop`、`previousKittyImageIds`、宽高(`tui-main-screen.ts:737-749`),并按需移动硬件光标(`positionHardwareCursor`,`tui-main-screen.ts:757-788`)。
输出统一经过 `BoundedTerminalWriter`(`tui-main-screen.ts:24-80`):按 1MiB 分块刷盘,超长串按代理对边界切开,避免整帧拼成一个超过 V8 字符串上限的大串。
@@ -169,7 +169,7 @@ alt screen 进入时(`tui-alt-screen.ts:316-318`):`\x1b[?1049h` + 关 auto
## 7. 产品侧接线(apps/cli)
-渲染库只提供机制;"什么事件、什么数据、变成什么组件"由产品层决定,边界遵守 AGENTS.md 的所有权规则(组件编排属 `coding-agent`/宿主,差分渲染原语属 `pi-tui`,产品层不重写渲染循环)。
+渲染库只提供机制;"什么事件、什么数据、变成什么组件"由产品层决定,边界遵守 AGENTS.md 的所有权规则(组件编排属 `coding-agent`/宿主,差分渲染原语属 `@step-harness/pi-tui` 包,产品层不重写渲染循环)。
### 7.1 组合根
@@ -202,28 +202,28 @@ alt screen 进入时(`tui-alt-screen.ts:316-318`):`\x1b[?1049h` + 关 auto
主题文件变化(`interactive-mode.ts:1248-1253`):`ui.invalidate()` + `requestRender()`——`invalidate` 丢弃容器与组件的渲染缓存,主题色在下一帧的归一化行里生效。语法高亮全部加载完成后同样失效重绘(`interactive-mode.ts:1265-1269`)。git 分支变化只触发 `requestRender`(footer 数据提供者推送,`interactive-mode.ts:1256-1258`)。
-## 8. 环境变量与调试开关
+## 8. 渲染开关与调试
-| 变量 | 作用 |
-| --- | --- |
-| `PI_TUI_DISABLE_INCREMENTAL=1` | 关掉增量渲染,每帧全量走,用于渲染等价测试(`tui.ts:224-229`) |
-| `PI_HARDWARE_CURSOR=1` | 显示硬件光标(默认隐藏,`tui.ts:447`) |
-| `PI_CLEAR_ON_SHRINK=0` | 内容收缩时不清空多余行,减少慢终端重绘(`tui.ts:448`、`513-515`) |
-| `PI_TUI_ESC_TIMEOUT=` | 覆盖裸 ESC 判定超时(默认 10ms,SSH 100ms,`terminal.ts:104-121`) |
-| `PI_TUI_WRITE_LOG=` | 把渲染字节流落盘,回放诊断(`terminal.ts:138-151`) |
-| `PI_DEBUG_REDRAW=1` | 全量重绘原因写入 `pi-debug.log`(`tui-main-screen.ts:430-437`) |
-| `PI_TUI_DEBUG=1` | 每帧差分细节(区间、光标、前后帧)写入 `/tmp/tui`(`tui-main-screen.ts:703-730`) |
-| `PI_CODING_AGENT_DIR` | 日志目录(缺省 `~/.pi/agent`,`tui.ts:469`) |
+本节的开关都**不是环境变量**。早期版本把它们记作 `PI_TUI_*` / `PI_DEBUG_*` 环境变量,源码中并无任何对应的读取逻辑(`packages/tui/src` 内没有读取过 `PI_` 前缀的环境变量)。实际入口如下:
+
+| 开关 | 入口 | 作用 |
+| --- | --- | --- |
+| 增量渲染 | `setIncrementalRenderDisabled(true)`(`tui.ts:227-229`,进程内全局) | 每帧全量渲染,用于渲染等价测试 |
+| 硬件光标 | `showHardwareCursor` 设置项 → `ui.setShowHardwareCursor()`(`tui.ts:506-511`) | 显示终端光标,便于 IME(默认隐藏) |
+| 收缩清行 | `terminal.clearOnShrink` 设置项 → `ui.setClearOnShrink()`(`tui.ts:524-527`) | 内容收缩时是否清空多余行(默认 false,开启可能闪烁) |
+| ESC 超时 | 无开关,按传输自动判定(`terminal.ts:103-116`) | 非 SSH 10ms;检测到 `SSH_CONNECTION`/`SSH_TTY` 时 100ms |
+
+唯一的落盘诊断是崩溃现场:任何渲染行 `visibleWidth > width` 时,全部行写入 `/tui-crash.log`(`tui-main-screen.ts:628-639`)。`logDirectory` 由 `TUI` 构造参数传入,缺省是 `os.tmpdir()` 下按需创建的 `tui-*` 私有临时目录(`tui.ts:479-484`)。
## 9. 测试与验证
- `packages/tui/test` 用 `node:test`:差分行为(`container-render-cache.test.ts`、`main-screen-offscreen-change.test.ts`)、overlay 定位(`overlay-*.test.ts`)、布局(`layout.test.ts`)、输入(`stdin-buffer.test.ts`、`keys.test.ts`、`keybindings.test.ts`、`input.test.ts`)、宽度回归(`bug-regression-isimageline-startswith-bug.test.ts`、`tab-width.test.ts` 等)。
-- 渲染等价性:`PI_TUI_DISABLE_INCREMENTAL=1` 强制全量路径,与增量路径逐字节对比——增量复用一旦算错前缀就会在这里现形。
+- 渲染等价性:`setIncrementalRenderDisabled(true)` 强制全量路径,与增量路径逐字节对比——增量复用一旦算错前缀就会在这里现形。
- 产品侧:`apps/cli/test` 的 TUI 快照测试(如 `tui-acceptance-snapshot.test.ts`)锁住视图渲染输出。
## 10. 归属与约束
- 差分渲染、输入分发、布局合成只存在于 `packages/tui`;`apps/cli` 不 fork、不重写。
- `packages/tui` 保持零 AI 依赖(`docs/step-harness-architecture-redesign.md` 的 `check-tui-no-ai` 检查)。
-- 组件不得输出超过传入宽度的行——主屏渲染器会抛错并把现场写进 `pi-crash.log`;测量用 `visibleWidth()`,截断用 `truncateToWidth()`。
+- 组件不得输出超过传入宽度的行——主屏渲染器会抛错并把现场写进 `tui-crash.log`;测量用 `visibleWidth()`,截断用 `truncateToWidth()`。
- 跨进程渲染契约(远端会话)在 `packages/protocol`;本文只覆盖本地进程内的 TUI 渲染。
diff --git a/packages/agent-core/docs/harness.md b/packages/agent-core/docs/harness.md
index 425611a6..9e30c3cd 100644
--- a/packages/agent-core/docs/harness.md
+++ b/packages/agent-core/docs/harness.md
@@ -230,7 +230,7 @@ Source type provenance:
- `TelemetryContext` and typed schema helpers: `packages/telemetry`; the agent-owned schemas remain in `packages/agent/src/harness/telemetry.ts`.
- `TSchema` for durable custom-message registration: `typebox`.
-The public `QueueMode` remains `"all" | "one-at-a-time"`. Public `RetryPolicy` remains the pi-ai shape `{ enabled, maxRetries, baseDelayMs }`; operation state stores its normalized `{ maxAttempts, baseDelayMs }` equivalent. `maxRetries` and `baseDelayMs` must be finite non-negative safe integers and `maxRetries + 1` must remain safe; disabled retry normalizes to one attempt. Exponential delay and `notBefore` arithmetic saturate at `Number.MAX_SAFE_INTEGER`. Public `CompactionSettings` remains `{ enabled, reserveTokens, keepRecentTokens }`; both token counts must be finite non-negative safe integers. Constructors and setters reject invalid settings before publication. This design adds `deferred?: boolean | { window?: "15m" | "1h" | "24h" }` to `AgentHarnessStreamOptions` and its patch type; structural requests always force it to false.
+The public `QueueMode` remains `"all" | "one-at-a-time"`. Public `RetryPolicy` remains the `@step-harness/providers` shape `{ enabled, maxRetries, baseDelayMs }`; operation state stores its normalized `{ maxAttempts, baseDelayMs }` equivalent. `maxRetries` and `baseDelayMs` must be finite non-negative safe integers and `maxRetries + 1` must remain safe; disabled retry normalizes to one attempt. Exponential delay and `notBefore` arithmetic saturate at `Number.MAX_SAFE_INTEGER`. Public `CompactionSettings` remains `{ enabled, reserveTokens, keepRecentTokens }`; both token counts must be finite non-negative safe integers. Constructors and setters reject invalid settings before publication. This design adds `deferred?: boolean | { window?: "15m" | "1h" | "24h" }` to `AgentHarnessStreamOptions` and its patch type; structural requests always force it to false.
```ts
type SettledAssistantMessage = AssistantMessage & {
diff --git a/packages/agent-core/docs/search.md b/packages/agent-core/docs/search.md
index b1c3905f..09d71306 100644
--- a/packages/agent-core/docs/search.md
+++ b/packages/agent-core/docs/search.md
@@ -1,6 +1,6 @@
# Session Search
-Pi search is a small query interface over committed session entries. The shared contract returns only stable hit identity; implementations may extend hits with backend-specific display data.
+Session search is a small query interface over committed session entries. The shared contract returns only stable hit identity; implementations may extend hits with backend-specific display data.
## Core API
diff --git a/packages/agent-core/docs/telemetry-schema.md b/packages/agent-core/docs/telemetry-schema.md
index 9eb8e6e4..4fde6c07 100644
--- a/packages/agent-core/docs/telemetry-schema.md
+++ b/packages/agent-core/docs/telemetry-schema.md
@@ -1,4 +1,4 @@
-# Pi Agent Telemetry Schemas
+# Step Agent Telemetry Schemas
diff --git a/packages/coding-agent/README.md b/packages/coding-agent/README.md
index ddbc42d5..8a99a7bd 100644
--- a/packages/coding-agent/README.md
+++ b/packages/coding-agent/README.md
@@ -12,7 +12,13 @@ StepCode runs in four modes: interactive, print or JSON, RPC for process integra
### Upstream lineage and naming
-This runtime is derived from the upstream pi coding agent. `pi` therefore still appears where it is a compatibility contract rather than product branding: the `pi` manifest key that the package loader reads from `package.json`, the `pi-package` discovery keyword on published packages, and the `~/.pi/agent/` / `.pi/` paths below, which apply when you invoke plain `pi`. The shipped product entrypoint is `step`, and under it the config and package root is `~/.stepcode/`.
+The shipped entrypoint is `step`, and its config and package root is `~/.stepcode/`. This runtime is derived from an upstream project whose CLI carried a different name, which survives in three identifiers that are compatibility contracts rather than branding, and that are therefore documented as-is below:
+
+- the `pi` manifest key the package loader reads from `package.json`
+- the `pi-package` npm keyword used for package discovery
+- the published `@step-harness/pi-tui` component package
+
+Telemetry span and attribute names keep the same legacy prefix; see [telemetry-schema.md](../agent-core/docs/telemetry-schema.md).
## Table of Contents
@@ -72,11 +78,11 @@ Then just talk to StepCode. By default, it gives the model four tools: `read`, `
The shipped build authenticates against the **Step provider** by default. Log in with `/login` (or set `STEP_API_KEY`), then select a Step model via `/model` (or Ctrl+L). Press Ctrl+S in the model picker to save the highlighted model as the startup default.
-No other provider catalog is bundled. Add OpenAI- or Anthropic-compatible providers yourself via `~/.pi/agent/models.json`; configured catalogs refresh automatically, and `pi update --models` forces an immediate refresh. For custom APIs or OAuth, use extensions.
+No other provider catalog is bundled. Add OpenAI- or Anthropic-compatible providers yourself via `~/.stepcode/agent/models.json`; configured catalogs refresh automatically, and `step update --models` forces an immediate refresh. For custom APIs or OAuth, use extensions.
-Pi also supports the llama.cpp router server. Configure it with `/login llama.cpp`, manage downloads and loaded models with `/llama`, then select a loaded model with `/model`. See [docs/llama-cpp.md](docs/llama-cpp.md) for setup and usage.
+StepCode also supports the llama.cpp router server. Configure it with `/login llama.cpp`, manage downloads and loaded models with `/llama`, then select a loaded model with `/model`. See [docs/llama-cpp.md](docs/llama-cpp.md) for setup and usage.
-**Custom providers & models:** Add providers via `~/.pi/agent/models.json` if they speak a supported API (OpenAI or Anthropic). See [docs/models.md](docs/models.md) and [docs/custom-provider.md](docs/custom-provider.md), or [@step-harness/providers](../providers/README.md) for the underlying protocols.
+**Custom providers & models:** Add providers via `~/.stepcode/agent/models.json` if they speak a supported API (OpenAI or Anthropic). See [docs/models.md](docs/models.md) and [docs/custom-provider.md](docs/custom-provider.md), or [@step-harness/providers](../providers/README.md) for the underlying protocols.
---
@@ -133,11 +139,11 @@ Type `/` in the editor to trigger commands. [Extensions](#extensions) can regist
| `/share` | Upload as private GitHub gist with shareable HTML link |
| `/reload` | Reload keybindings, extensions, skills, prompts, themes, and context files |
| `/hotkeys` | Show all keyboard shortcuts |
-| `/quit` | Quit pi |
+| `/quit` | Quit step |
### Keyboard Shortcuts
-See `/hotkeys` for the full list. Customize via `~/.pi/agent/keybindings.json`. See [docs/keybindings.md](docs/keybindings.md).
+See `/hotkeys` for the full list. Customize via `~/.stepcode/agent/keybindings.json`. See [docs/keybindings.md](docs/keybindings.md).
**Commonly used:**
@@ -164,7 +170,7 @@ Submit messages while the agent is working:
Follow-up delivery remains available to extensions, RPC clients, and custom `app.message.followUp` keybindings, but has no default shortcut.
-On Windows Terminal, `Alt+Enter` is fullscreen by default. Remap it in [docs/terminal-setup.md](docs/terminal-setup.md) so pi can receive the newline shortcut.
+On Windows Terminal, `Alt+Enter` is fullscreen by default. Remap it in [docs/terminal-setup.md](docs/terminal-setup.md) so step can receive the newline shortcut.
Configure delivery in [settings](docs/settings.md): `steeringMode` and `followUpMode` can be `"one-at-a-time"` (default, waits for response) or `"all"` (delivers all queued at once). `transport` selects provider transport preference (`"sse"`, `"websocket"`, or `"auto"`) for providers that support multiple transports.
@@ -176,15 +182,15 @@ Sessions are stored as JSONL files with a tree structure. Each entry has an `id`
### Management
-Sessions auto-save to `~/.pi/agent/sessions/` organized by working directory.
+Sessions auto-save to `~/.stepcode/agent/sessions/` organized by working directory.
```bash
-pi -c # Continue most recent session
-pi -r # Browse and select from past sessions
-pi --no-session # Ephemeral mode (don't save)
-pi --name "my task" # Set session display name at startup
-pi --session # Use specific session file or ID
-pi --fork # Fork specific session file or ID into a new session
+step -c # Continue most recent session
+step -r # Browse and select from past sessions
+step --no-session # Ephemeral mode (don't save)
+step --name "my task" # Set session display name at startup
+step --session # Use specific session file or ID
+step --fork # Fork specific session file or ID into a new session
```
Use `/session` in interactive mode to see the current session ID before reusing it with `--session ` or `--fork `.
@@ -224,35 +230,35 @@ Use `/settings` to modify common options, or edit JSON files directly:
| Location | Scope |
|----------|-------|
-| `~/.pi/agent/settings.json` | Global (all projects) |
-| `.pi/settings.json` | Project (overrides global) |
+| `~/.stepcode/agent/settings.json` | Global (all projects) |
+| `.stepcode/settings.json` | Project (overrides global) |
See [docs/settings.md](docs/settings.md) for all options.
### Project Trust
-On interactive startup, pi asks before trusting a project folder that contains project-local settings, resources, or project `.agents/skills` or `.claude/skills` and has no saved decision for the folder or a parent folder in `~/.pi/agent/trust.json`. Trusting a project allows pi to load `.pi/settings.json` and `.pi` resources, install missing project packages, and execute project extensions.
+On interactive startup, step asks before trusting a project folder that contains project-local settings, resources, or project `.agents/skills` or `.claude/skills` and has no saved decision for the folder or a parent folder in `~/.stepcode/agent/trust.json`. Trusting a project allows step to load `.stepcode/settings.json` and `.stepcode` resources, install missing project packages, and execute project extensions.
-Before the trust decision, pi loads only context files, user/global extensions, and CLI `-e` extensions so they can handle the `project_trust` event. Project-local extensions, project package-managed extensions, and project settings are loaded only after the project is trusted. This split also applies when switching to a session from a different cwd whose trust has not been resolved in the current process.
+Before the trust decision, step loads only context files, user/global extensions, and CLI `-e` extensions so they can handle the `project_trust` event. Project-local extensions, project package-managed extensions, and project settings are loaded only after the project is trusted. This split also applies when switching to a session from a different cwd whose trust has not been resolved in the current process.
Non-interactive modes (`-p`, `--mode json`, and `--mode rpc`) do not show a trust prompt. Without an applicable saved trust decision, they use `defaultProjectTrust` from global settings: `ask` (default) and `never` ignore those project resources, while `always` trusts them. Pass `--approve`/`-a` or `--no-approve`/`-na` to override project trust for one run.
-If no extension or saved decision applies, `defaultProjectTrust` controls the fallback behavior. Set it to `"ask"`, `"always"`, or `"never"` in `~/.pi/agent/settings.json`, or change it with `/settings`.
+If no extension or saved decision applies, `defaultProjectTrust` controls the fallback behavior. Set it to `"ask"`, `"always"`, or `"never"` in `~/.stepcode/agent/settings.json`, or change it with `/settings`.
-`pi config` and package commands use the same project trust flow, except `pi update` never prompts. Pass `--approve` to trust project-local settings for one command or `--no-approve` to ignore them.
+`step config` and package commands use the same project trust flow, except `step update` never prompts. Pass `--approve` to trust project-local settings for one command or `--no-approve` to ignore them.
-Use `/trust` in interactive mode to save a project trust decision for future sessions, including trust for the immediate parent folder. It writes `~/.pi/agent/trust.json` only; the current session is not reloaded, so restart pi for changes to take effect.
+Use `/trust` in interactive mode to save a project trust decision for future sessions, including trust for the immediate parent folder. It writes `~/.stepcode/agent/trust.json` only; the current session is not reloaded, so restart step for changes to take effect.
---
## Context Files
-Pi loads `AGENTS.md` (or `CLAUDE.md`) at startup from:
-- `~/.pi/agent/AGENTS.md` (global)
+StepCode loads `AGENTS.md` (or `CLAUDE.md`) at startup from:
+- `~/.stepcode/agent/AGENTS.md` (global)
- Parent directories (walking up from cwd)
- Current directory
-If a directory contains `AGENTS.override.md`, Pi loads it instead of `AGENTS.md` or `CLAUDE.md` from that directory. Context files from other directories are still concatenated.
+If a directory contains `AGENTS.override.md`, StepCode loads it instead of `AGENTS.md` or `CLAUDE.md` from that directory. Context files from other directories are still concatenated.
Use for project instructions (`AGENTS.md`/`CLAUDE.md`), conventions, common commands. All matching files are concatenated.
@@ -260,7 +266,7 @@ Disable context file loading with `--no-context-files` (or `-nc`).
### System Prompt
-Replace the default system prompt with `.pi/SYSTEM.md` (project) or `~/.pi/agent/SYSTEM.md` (global). Append without replacing via `APPEND_SYSTEM.md`.
+Replace the default system prompt with `.stepcode/SYSTEM.md` (project) or `~/.stepcode/agent/SYSTEM.md` (global). Append without replacing via `APPEND_SYSTEM.md`.
---
@@ -271,19 +277,19 @@ Replace the default system prompt with `.pi/SYSTEM.md` (project) or `~/.pi/agent
Reusable prompts as Markdown files. Type `/name` to expand.
```markdown
-
+
Review this code for bugs, security issues, and performance problems.
Focus on: {{focus}}
```
-Place in `~/.pi/agent/prompts/`, `.pi/prompts/`, or a [package](#packages) to share with others. See [docs/prompt-templates.md](docs/prompt-templates.md).
+Place in `~/.stepcode/agent/prompts/`, `.stepcode/prompts/`, or a [package](#packages) to share with others. See [docs/prompt-templates.md](docs/prompt-templates.md).
### Skills
On-demand capability packages following the [Agent Skills standard](https://agentskills.io). Invoke via `/skill:name` or let the agent load them automatically.
```markdown
-
+
# My Skill
Use this skill when the user asks about X.
@@ -292,23 +298,23 @@ Use this skill when the user asks about X.
2. Then that
```
-Place in `~/.pi/agent/skills/`, `~/.pi/skills/`, `~/.agents/skills/`, `~/.claude/skills/`, `.pi/skills/`, `.agents/skills/`, or `.claude/skills/` (the last two from `cwd` up through parent directories) or a [package](#packages) to share with others. See [docs/skills.md](docs/skills.md).
+Place in `~/.stepcode/agent/skills/`, `~/.stepcode/skills/`, `~/.agents/skills/`, `~/.claude/skills/`, `.stepcode/skills/`, `.agents/skills/`, or `.claude/skills/` (the last two from `cwd` up through parent directories) or a [package](#packages) to share with others. See [docs/skills.md](docs/skills.md).
### Extensions
-TypeScript modules that extend pi with custom tools, commands, keyboard shortcuts, event handlers, and UI components.
+TypeScript modules that extend step with custom tools, commands, keyboard shortcuts, event handlers, and UI components.
```typescript
-export default function (pi: ExtensionAPI) {
- pi.registerTool({ name: "deploy", ... });
- pi.registerCommand("stats", { ... });
- pi.on("tool_call", async (event, ctx) => { ... });
+export default function (step: ExtensionAPI) {
+ step.registerTool({ name: "deploy", ... });
+ step.registerCommand("stats", { ... });
+ step.on("tool_call", async (event, ctx) => { ... });
}
```
-The default export can also be `async`. pi waits for async extension factories before startup continues, which is useful for one-time initialization such as fetching remote model lists before calling `pi.registerProvider()`.
+The default export can also be `async`. step waits for async extension factories before startup continues, which is useful for one-time initialization such as fetching remote model lists before calling `step.registerProvider()`.
**What's possible:**
- Custom tools (or replace built-in tools entirely)
@@ -320,24 +326,23 @@ The default export can also be `async`. pi waits for async extension factories b
- Git checkpointing and auto-commit
- SSH and sandbox execution
- MCP server integration
-- Make pi look like Claude Code
+- Make step look like Claude Code
- Games while waiting (yes, Doom runs)
- ...anything you can dream up
-Place in `~/.pi/agent/extensions/`, `.pi/extensions/`, or a [package](#packages) to share with others. See [docs/extensions.md](docs/extensions.md) and [examples/extensions/](examples/extensions/).
+Place in `~/.stepcode/agent/extensions/`, `.stepcode/extensions/`, or a [package](#packages) to share with others. See [docs/extensions.md](docs/extensions.md) and [examples/extensions/](examples/extensions/).
### Themes
-Built-in: `dark`, `light`, `sage`, `step-blue`, `step-violet`, `step-violet-light`. Themes hot-reload: modify the active theme file and pi immediately applies changes.
+Built-in: `dark`, `light`, `sage`, `step-blue`, `step-violet`, `step-violet-light`. Themes hot-reload: modify the active theme file and step immediately applies changes.
-Place in `~/.pi/agent/themes/`, `.pi/themes/`, or a [package](#packages) to share with others. See [docs/themes.md](docs/themes.md).
+Place in `~/.stepcode/agent/themes/`, `.stepcode/themes/`, or a [package](#packages) to share with others. See [docs/themes.md](docs/themes.md).
### Step entrypoint
-The `step` binary is a thin product entrypoint over the same pi coding-agent
-runtime and native TUI. It selects the `step` provider and model by default,
-uses the built-in `step-blue` theme (one blue palette, no light/dark variants), and stores sessions
-under `~/.stepcode/`.
+The `step` binary selects the `step` provider and model by default, uses the
+built-in `step-blue` theme (one blue palette, no light/dark variants), and
+stores sessions under `~/.stepcode/`.
```bash
STEP_API_KEY=... step
@@ -346,7 +351,7 @@ step --help
Step-specific behavior is registered as an extension (`/init`, provider login,
and branding). The editor, input protocol, selectors, overlays, queueing, and
-agent lifecycle remain pi-owned.
+agent lifecycle are owned by the shared runtime.
### Packages
@@ -355,8 +360,8 @@ Bundle and share extensions, skills, prompts, and themes via npm or git. Find pa
> **Security:** StepCode packages run with full system access. Extensions execute arbitrary code, and skills can instruct the model to perform any action including running executables. Review source code before installing third-party packages.
```bash
-step install npm:@foo/pi-tools
-step install npm:@foo/pi-tools@1.2.3 # pinned version
+step install npm:@foo/step-tools
+step install npm:@foo/step-tools@1.2.3 # pinned version
step install git:github.com/user/repo
step install git:github.com/user/repo@v1 # tag or commit
step install git:git@github.com:user/repo
@@ -365,8 +370,8 @@ step install https://github.com/user/repo
step install https://github.com/user/repo@v1 # tag or commit
step install ssh://git@github.com/user/repo
step install ssh://git@github.com/user/repo@v1 # tag or commit
-step remove npm:@foo/pi-tools
-step uninstall npm:@foo/pi-tools # alias for remove
+step remove npm:@foo/step-tools
+step uninstall npm:@foo/step-tools # alias for remove
step list
step update # update StepCode only
step update --all # update StepCode and packages
@@ -374,17 +379,17 @@ step update --extensions # update packages only
step update --models # refresh model catalogs only
step update --self # update StepCode only
step update --self --force # reinstall StepCode even if current
-step update npm:@foo/pi-tools # update one package
+step update npm:@foo/step-tools # update one package
step config # enable/disable extensions, skills, prompts, themes
```
-Packages install to `~/.stepcode/agent/git/` (git) or `~/.stepcode/agent/npm/` (npm); plain `pi` invocations use `~/.pi/agent/`. Use `-l` for project-local installs (`.stepcode/git/`, `.stepcode/npm/`). Git `@ref` values are pinned tags or commits; pinned packages are skipped by `step update --extensions` and `step update --all`, so use `step install git:host/user/repo@new-ref` to move an existing package to a new ref. Git packages install dependencies with `npm install --omit=dev` by default, so runtime deps must be listed under `dependencies`; when `npmCommand` is configured, git packages use plain `install` for compatibility with wrappers. If you use a Node version manager and want package installs to reuse a stable npm context, set `npmCommand` in `settings.json`, for example `["mise", "exec", "node@20", "--", "npm"]`.
+Packages install to `~/.stepcode/agent/git/` (git) or `~/.stepcode/agent/npm/` (npm). Use `-l` for project-local installs (`.stepcode/git/`, `.stepcode/npm/`). Git `@ref` values are pinned tags or commits; pinned packages are skipped by `step update --extensions` and `step update --all`, so use `step install git:host/user/repo@new-ref` to move an existing package to a new ref. Git packages install dependencies with `npm install --omit=dev` by default, so runtime deps must be listed under `dependencies`; when `npmCommand` is configured, git packages use plain `install` for compatibility with wrappers. If you use a Node version manager and want package installs to reuse a stable npm context, set `npmCommand` in `settings.json`, for example `["mise", "exec", "node@20", "--", "npm"]`.
Create a package by adding a `pi` manifest key to `package.json`. The key keeps its upstream name so already-published packages stay discoverable:
```json
{
- "name": "my-pi-package",
+ "name": "my-step-package",
"keywords": ["pi-package"],
"pi": {
"extensions": ["./extensions"],
@@ -426,7 +431,7 @@ See [docs/sdk.md](docs/sdk.md) and [examples/sdk/](examples/sdk/).
For non-Node.js integrations, use RPC mode over stdin/stdout:
```bash
-pi --mode rpc
+step --mode rpc
```
RPC mode uses strict LF-delimited JSONL framing. Clients must split records on `\n` only. Do not use generic line readers like Node `readline`, which also split on Unicode separators inside JSON payloads.
@@ -451,7 +456,8 @@ StepCode is aggressively extensible so it doesn't have to dictate your workflow.
**No background bash.** Use tmux. Full observability, direct interaction.
-This design follows the rationale of the upstream pi coding agent.
+This design is deliberate: the runtime stays small, and anything opinionated is
+something you add.
---
@@ -490,10 +496,10 @@ step config # Enable/disable package resources
| `--mode rpc` | RPC mode for process integration (see [docs/rpc.md](docs/rpc.md)) |
| `--export [out]` | Export session to HTML |
-In print mode, pi also reads piped stdin and merges it into the initial prompt:
+In print mode, step also reads piped stdin and merges it into the initial prompt:
```bash
-cat README.md | pi -p "Summarize this text"
+cat README.md | step -p "Summarize this text"
```
### Model Options
@@ -566,49 +572,49 @@ Combine `--no-*` with explicit flags to load exactly what you need, ignoring set
Prefix files with `@` to include in the message:
```bash
-pi @prompt.md "Answer this"
-pi -p @screenshot.png "What's in this image?"
-pi @code.ts @test.ts "Review these files"
+step @prompt.md "Answer this"
+step -p @screenshot.png "What's in this image?"
+step @code.ts @test.ts "Review these files"
```
### Examples
```bash
# Interactive with initial prompt
-pi "List all .ts files in src/"
+step "List all .ts files in src/"
# Non-interactive
-pi -p "Summarize this codebase"
+step -p "Summarize this codebase"
# Prompt beginning with a dash
-pi -p -- "- Summarize these points"
+step -p -- "- Summarize these points"
# Non-interactive with piped stdin
-cat README.md | pi -p "Summarize this text"
+cat README.md | step -p "Summarize this text"
# Named one-shot session
-pi --name "release audit" -p "Audit this repository"
+step --name "release audit" -p "Audit this repository"
# Different model
-pi --provider openai --model gpt-4o "Help me refactor"
+step --provider openai --model gpt-4o "Help me refactor"
# Model with provider prefix (no --provider needed)
-pi --model openai/gpt-4o "Help me refactor"
+step --model openai/gpt-4o "Help me refactor"
# Model with thinking level shorthand
-pi --model sonnet:high "Solve this complex problem"
+step --model sonnet:high "Solve this complex problem"
# Limit model cycling
-pi --models "claude-*,gpt-4o"
+step --models "claude-*,gpt-4o"
# Read-only mode
-pi --tools read,grep,find,ls -p "Review the code"
+step --tools read,grep,find,ls -p "Review the code"
# Disable one extension or built-in tool while keeping the rest available
-pi --exclude-tools ask_question
+step --exclude-tools ask_question
# High thinking level
-pi --thinking high "Solve this complex problem"
+step --thinking high "Solve this complex problem"
```
### Environment Variables
diff --git a/packages/coding-agent/docs/compaction.md b/packages/coding-agent/docs/compaction.md
index 22b23f60..cc0a3241 100644
--- a/packages/coding-agent/docs/compaction.md
+++ b/packages/coding-agent/docs/compaction.md
@@ -297,7 +297,7 @@ Extensions can intercept and customize both compaction and branch summarization.
Fired before auto-compaction or `/compact`. Can cancel or provide custom summary. See `SessionBeforeCompactEvent` and `CompactionPreparation` in the types file.
```typescript
-pi.on("session_before_compact", async (event, ctx) => {
+step.on("session_before_compact", async (event, ctx) => {
const { preparation, branchEntries, customInstructions, reason, willRetry, signal } = event;
// preparation.messagesToSummarize - messages to summarize
@@ -336,7 +336,7 @@ To generate a summary with your own model, convert messages to text using `seria
```typescript
import { convertToLlm, serializeConversation } from "@step-harness/coding-agent";
-pi.on("session_before_compact", async (event, ctx) => {
+step.on("session_before_compact", async (event, ctx) => {
const { preparation } = event;
// Convert AgentMessage[] to Message[], then serialize to text
@@ -371,7 +371,7 @@ See [custom-compaction.ts](../examples/extensions/custom-compaction.ts) for a co
Fired when manual or automatic compaction fails or is aborted. This is useful for telemetry extensions that need to pair `session_before_compact` attempts with terminal outcomes.
```typescript
-pi.on("session_compact_failed", async (event, ctx) => {
+step.on("session_compact_failed", async (event, ctx) => {
const { reason, errorMessage, aborted, willRetry, fromExtension } = event;
// reason - "manual" (/compact), "threshold", or "overflow"
// errorMessage - present for non-abort failures
@@ -386,7 +386,7 @@ pi.on("session_compact_failed", async (event, ctx) => {
Fired before `/tree` navigation. Always fires regardless of whether user chose to summarize. Can cancel navigation or provide custom summary.
```typescript
-pi.on("session_before_tree", async (event, ctx) => {
+step.on("session_before_tree", async (event, ctx) => {
const { preparation, signal } = event;
// preparation.targetId - where we're navigating to
diff --git a/packages/coding-agent/docs/custom-provider.md b/packages/coding-agent/docs/custom-provider.md
index c9ab1185..9b72d5de 100644
--- a/packages/coding-agent/docs/custom-provider.md
+++ b/packages/coding-agent/docs/custom-provider.md
@@ -1,6 +1,6 @@
# Custom Providers
-Extensions can register custom model providers via `pi.registerProvider()`. This enables:
+Extensions can register custom model providers via `step.registerProvider()`. This enables:
- **Proxies** - Route requests through corporate proxies or API gateways
- **Custom endpoints** - Use self-hosted or private model deployments
@@ -29,14 +29,14 @@ adapter's license and credential handling before loading it.
## Quick Reference
-Extensions can register either a complete pi-ai `Provider` or use the legacy provider-config form. Prefer a complete provider when custom authentication, filtering, refresh, or streaming behavior is required. Step composes `models.json` overrides above registered native providers.
+Extensions can register either a complete `Provider` or use the legacy provider-config form. Prefer a complete provider when custom authentication, filtering, refresh, or streaming behavior is required. Step composes `models.json` overrides above registered native providers.
```typescript
import { createProvider, openAICompletionsApi } from "@step-harness/providers";
import type { ExtensionAPI } from "@step-harness/coding-agent";
-export default function (pi: ExtensionAPI) {
- pi.registerProvider(createProvider({
+export default function (step: ExtensionAPI) {
+ step.registerProvider(createProvider({
id: "native-local",
name: "Native Local",
baseUrl: "http://localhost:8080/v1",
@@ -62,12 +62,12 @@ export default function (pi: ExtensionAPI) {
// Legacy provider-config form:
// Override baseUrl for existing provider
- pi.registerProvider("anthropic", {
+ step.registerProvider("anthropic", {
baseUrl: "https://proxy.example.com"
});
// Register new provider with models
- pi.registerProvider("my-provider", {
+ step.registerProvider("my-provider", {
name: "My Provider",
baseUrl: "https://api.example.com",
apiKey: "$MY_API_KEY",
@@ -95,19 +95,19 @@ The simplest use case: redirect an existing provider through a proxy.
```typescript
// All Anthropic requests now go through your proxy
-pi.registerProvider("anthropic", {
+step.registerProvider("anthropic", {
baseUrl: "https://proxy.example.com"
});
// Add custom headers to OpenAI requests
-pi.registerProvider("openai", {
+step.registerProvider("openai", {
headers: {
"X-Custom-Header": "value"
}
});
// Both baseUrl and headers
-pi.registerProvider("google", {
+step.registerProvider("google", {
baseUrl: "https://ai-gateway.corp.com/google",
headers: {
"X-Corp-Auth": "$CORP_AUTH_TOKEN" // env var or literal
@@ -126,7 +126,7 @@ If the model list comes from a remote endpoint, use an async extension factory:
```typescript
import type { ExtensionAPI } from "@step-harness/coding-agent";
-export default async function (pi: ExtensionAPI) {
+export default async function (step: ExtensionAPI) {
const response = await fetch("http://localhost:1234/v1/models");
const payload = (await response.json()) as {
data: Array<{
@@ -137,7 +137,7 @@ export default async function (pi: ExtensionAPI) {
}>;
};
- pi.registerProvider("local-openai", {
+ step.registerProvider("local-openai", {
baseUrl: "http://localhost:1234/v1",
apiKey: "$LOCAL_OPENAI_API_KEY",
api: "openai-completions",
@@ -157,7 +157,7 @@ export default async function (pi: ExtensionAPI) {
This registers the fetched models before startup finishes.
```typescript
-pi.registerProvider("my-llm", {
+step.registerProvider("my-llm", {
baseUrl: "https://api.my-llm.com/v1",
apiKey: "$MY_LLM_API_KEY", // env var reference
api: "openai-completions", // which streaming API to use
@@ -186,11 +186,11 @@ When `models` is provided, it **replaces** all existing models for that provider
## Unregister Provider
-Use `pi.unregisterProvider(name)` to remove a provider that was previously registered via `pi.registerProvider(name, ...)`:
+Use `step.unregisterProvider(name)` to remove a provider that was previously registered via `step.registerProvider(name, ...)`:
```typescript
// Register
-pi.registerProvider("my-llm", {
+step.registerProvider("my-llm", {
baseUrl: "https://api.my-llm.com/v1",
apiKey: "$MY_LLM_API_KEY",
api: "openai-completions",
@@ -208,7 +208,7 @@ pi.registerProvider("my-llm", {
});
// Later, remove it
-pi.unregisterProvider("my-llm");
+step.unregisterProvider("my-llm");
```
Unregistering removes that provider's dynamic models, API key fallback, OAuth provider registration, and custom stream handler registrations. Any built-in models or provider behavior that were overridden are restored.
@@ -271,7 +271,7 @@ For Anthropic-compatible providers using `api: "anthropic-messages"`, set `compa
If your provider expects `Authorization: Bearer ` but doesn't use a standard API, set `authHeader: true`:
```typescript
-pi.registerProvider("custom-api", {
+step.registerProvider("custom-api", {
baseUrl: "https://api.example.com",
apiKey: "$MY_API_KEY",
authHeader: true, // adds Authorization: Bearer header
@@ -289,7 +289,7 @@ Add OAuth/SSO authentication that integrates with `/login`:
```typescript
import type { OAuthCredentials, OAuthLoginCallbacks } from "@step-harness/providers";
-pi.registerProvider("corporate-ai", {
+step.registerProvider("corporate-ai", {
baseUrl: "https://ai.corp.com/v1",
api: "openai-responses",
models: [...],
@@ -577,10 +577,10 @@ If your provider returns overflow errors with a message step does not recognize,
```typescript
const MY_PROVIDER_OVERFLOW_PATTERN = /your provider's overflow phrase/i;
-export default function (pi: ExtensionAPI) {
- pi.registerProvider("my-provider", { /* ... */ });
+export default function (step: ExtensionAPI) {
+ step.registerProvider("my-provider", { /* ... */ });
- pi.on("message_end", (event, ctx) => {
+ step.on("message_end", (event, ctx) => {
const message = event.message;
if (message.role !== "assistant") return;
if (message.stopReason !== "error") return;
@@ -622,7 +622,7 @@ Guard the rewrite carefully:
Register your stream function:
```typescript
-pi.registerProvider("my-provider", {
+step.registerProvider("my-provider", {
baseUrl: "https://api.example.com",
apiKey: "$MY_API_KEY",
api: "my-custom-api",
diff --git a/packages/coding-agent/docs/development.md b/packages/coding-agent/docs/development.md
index d75152d3..724daf94 100644
--- a/packages/coding-agent/docs/development.md
+++ b/packages/coding-agent/docs/development.md
@@ -39,8 +39,8 @@ Configure via `package.json`:
```json
{
"piConfig": {
- "name": "pi",
- "configDir": ".pi"
+ "name": "step",
+ "configDir": ".stepcode"
}
}
```
diff --git a/packages/coding-agent/docs/extensions.md b/packages/coding-agent/docs/extensions.md
index eb77b04c..f0f1bdf4 100644
--- a/packages/coding-agent/docs/extensions.md
+++ b/packages/coding-agent/docs/extensions.md
@@ -7,12 +7,12 @@ Extensions are TypeScript modules that extend step's behavior. They can subscrib
> **Placement for /reload:** Put extensions in `~/.stepcode/agent/extensions/` (global) or `.stepcode/extensions/` (project-local) for auto-discovery. Use `step -e ./path.ts` only for quick tests. Extensions in auto-discovered locations can be hot-reloaded with `/reload`.
**Key capabilities:**
-- **Custom tools** - Register tools the LLM can call via `pi.registerTool()`
+- **Custom tools** - Register tools the LLM can call via `step.registerTool()`
- **Event interception** - Block or modify tool calls, inject context, customize compaction
- **User interaction** - Prompt users via `ctx.ui` (select, confirm, input, notify)
- **Custom UI components** - Full TUI components with keyboard input via `ctx.ui.custom()` for complex interactions
-- **Custom commands** - Register commands like `/mycommand` via `pi.registerCommand()`
-- **Session persistence** - Store state that survives restarts via `pi.appendEntry()`
+- **Custom commands** - Register commands like `/mycommand` via `step.registerCommand()`
+- **Session persistence** - Store state that survives restarts via `step.appendEntry()`
- **Custom rendering** - Control how tool calls/results and messages appear in TUI
**Example use cases:**
@@ -61,13 +61,13 @@ Create `~/.stepcode/agent/extensions/my-extension.ts`:
import type { ExtensionAPI } from "@step-harness/coding-agent";
import { Type } from "typebox";
-export default function (pi: ExtensionAPI) {
+export default function (step: ExtensionAPI) {
// React to events
- pi.on("session_start", async (_event, ctx) => {
+ step.on("session_start", async (_event, ctx) => {
ctx.ui.notify("Extension loaded!", "info");
});
- pi.on("tool_call", async (event, ctx) => {
+ step.on("tool_call", async (event, ctx) => {
if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
const ok = await ctx.ui.confirm("Dangerous!", "Allow rm -rf?");
if (!ok) return { block: true, reason: "Blocked by user" };
@@ -75,7 +75,7 @@ export default function (pi: ExtensionAPI) {
});
// Register a custom tool
- pi.registerTool({
+ step.registerTool({
name: "greet",
label: "Greet",
description: "Greet someone by name",
@@ -91,7 +91,7 @@ export default function (pi: ExtensionAPI) {
});
// Register a command
- pi.registerCommand("hello", {
+ step.registerCommand("hello", {
description: "Say hello",
handler: async (args, ctx) => {
ctx.ui.notify(`Hello ${args || "world"}!`, "info");
@@ -158,9 +158,9 @@ An extension exports a default factory function that receives `ExtensionAPI`. Th
```typescript
import type { ExtensionAPI } from "@step-harness/coding-agent";
-export default function (pi: ExtensionAPI) {
+export default function (step: ExtensionAPI) {
// Subscribe to events
- pi.on("event_name", async (event, ctx) => {
+ step.on("event_name", async (event, ctx) => {
// ctx.ui for user interaction
const ok = await ctx.ui.confirm("Title", "Are you sure?");
ctx.ui.notify("Done!", "info");
@@ -169,16 +169,16 @@ export default function (pi: ExtensionAPI) {
});
// Register tools, commands, shortcuts, flags
- pi.registerTool({ ... });
- pi.registerCommand("name", { ... });
- pi.registerShortcut("ctrl+x", { ... });
- pi.registerFlag("my-flag", { ... });
+ step.registerTool({ ... });
+ step.registerCommand("name", { ... });
+ step.registerShortcut("ctrl+x", { ... });
+ step.registerFlag("my-flag", { ... });
}
```
Extensions are loaded via [jiti](https://github.com/unjs/jiti), so TypeScript works without compilation.
-If the factory returns a `Promise`, step awaits it before continuing startup. That means async initialization completes before `session_start`, before `resources_discover`, and before provider registrations queued via `pi.registerProvider()` are flushed.
+If the factory returns a `Promise`, step awaits it before continuing startup. That means async initialization completes before `session_start`, before `resources_discover`, and before provider registrations queued via `step.registerProvider()` are flushed.
### Async factory functions
@@ -187,7 +187,7 @@ Use an async factory for one-time startup work such as fetching remote configura
```typescript
import type { ExtensionAPI } from "@step-harness/coding-agent";
-export default async function (pi: ExtensionAPI) {
+export default async function (step: ExtensionAPI) {
const response = await fetch("http://localhost:1234/v1/models");
const payload = (await response.json()) as {
data: Array<{
@@ -198,7 +198,7 @@ export default async function (pi: ExtensionAPI) {
}>;
};
- pi.registerProvider("local-openai", {
+ step.registerProvider("local-openai", {
baseUrl: "http://localhost:1234/v1",
apiKey: "$LOCAL_OPENAI_API_KEY",
api: "openai-completions",
@@ -325,7 +325,7 @@ user sends another prompt ◄─────────────────
├─► session_start { reason: "fork", previousSessionFile }
└─► resources_discover { reason: "startup" }
-/name or pi.setSessionName()
+/name or step.setSessionName()
└─► session_info_changed
/compact or auto-compaction
@@ -341,7 +341,7 @@ user sends another prompt ◄─────────────────
├─► thinking_level_select (if model change changes/clamps thinking level)
└─► model_select
-thinking level changes (settings, keybinding, pi.setThinkingLevel())
+thinking level changes (settings, keybinding, step.setThinkingLevel())
└─► thinking_level_select
exit (Ctrl+C, Ctrl+D, SIGHUP, SIGTERM)
@@ -355,7 +355,7 @@ exit (Ctrl+C, Ctrl+D, SIGHUP, SIGTERM)
Fired before step decides whether to trust a project with dynamic configs (`.stepcode`, `.agents/skills`, or `.claude/skills`). It runs during startup and when session replacement (for example `/resume`) enters a cwd whose trust has not been resolved in the current process. Only user/global extensions and CLI `-e` extensions participate; project-local extensions are not loaded until after trust is resolved.
```typescript
-pi.on("project_trust", async (event, ctx) => {
+step.on("project_trust", async (event, ctx) => {
// event.cwd - current working directory
// ctx has a limited trust context: cwd, mode, hasUI, and select/confirm/input/notify UI helpers
if (await ctx.ui.confirm("Trust project?", event.cwd)) {
@@ -375,7 +375,7 @@ Fired after `session_start` so extensions can contribute additional skill, promp
The startup path uses `reason: "startup"`. Reload uses `reason: "reload"`.
```typescript
-pi.on("resources_discover", async (event, _ctx) => {
+step.on("resources_discover", async (event, _ctx) => {
// event.cwd - current working directory
// event.reason - "startup" | "reload"
return {
@@ -395,7 +395,7 @@ See [Session Format](session-format.md) for session storage internals and the Se
Fired when a session is started, loaded, or reloaded.
```typescript
-pi.on("session_start", async (event, ctx) => {
+step.on("session_start", async (event, ctx) => {
// event.reason - "startup" | "reload" | "new" | "resume" | "fork"
// event.previousSessionFile - present for "new", "resume", and "fork"
ctx.ui.notify(`Session: ${ctx.sessionManager.getSessionFile() ?? "ephemeral"}`, "info");
@@ -404,10 +404,10 @@ pi.on("session_start", async (event, ctx) => {
#### session_info_changed
-Fired when the current session display name is set via `/name`, RPC, or `pi.setSessionName()`.
+Fired when the current session display name is set via `/name`, RPC, or `step.setSessionName()`.
```typescript
-pi.on("session_info_changed", async (event, ctx) => {
+step.on("session_info_changed", async (event, ctx) => {
// event.name - current normalized name, or undefined if cleared
ctx.ui.notify(`Session renamed: ${event.name ?? "(none)"}`, "info");
});
@@ -418,7 +418,7 @@ pi.on("session_info_changed", async (event, ctx) => {
Fired before starting a new session (`/new`) or switching sessions (`/resume`).
```typescript
-pi.on("session_before_switch", async (event, ctx) => {
+step.on("session_before_switch", async (event, ctx) => {
// event.reason - "new" or "resume"
// event.targetSessionFile - session we're switching to (only for "resume")
@@ -437,7 +437,7 @@ Do cleanup work in `session_shutdown`, then reestablish any in-memory state in `
Fired when forking via `/fork` or cloning via `/clone`.
```typescript
-pi.on("session_before_fork", async (event, ctx) => {
+step.on("session_before_fork", async (event, ctx) => {
// event.entryId - ID of the selected entry
// event.position - "before" for /fork, "at" for /clone
return { cancel: true }; // Cancel fork/clone
@@ -454,7 +454,7 @@ Do cleanup work in `session_shutdown`, then reestablish any in-memory state in `
Fired on compaction. See [compaction.md](compaction.md) for details.
```typescript
-pi.on("session_before_compact", async (event, ctx) => {
+step.on("session_before_compact", async (event, ctx) => {
const { preparation, branchEntries, customInstructions, reason, willRetry, signal } = event;
// reason - "manual" (/compact), "threshold", or "overflow"
@@ -474,14 +474,14 @@ pi.on("session_before_compact", async (event, ctx) => {
};
});
-pi.on("session_compact", async (event, ctx) => {
+step.on("session_compact", async (event, ctx) => {
// event.compactionEntry - the saved compaction
// event.fromExtension - whether extension provided it
// event.reason - "manual" (/compact), "threshold", or "overflow"
// event.willRetry - whether the aborted turn is retried after compaction (overflow recovery)
});
-pi.on("session_compact_failed", async (event, ctx) => {
+step.on("session_compact_failed", async (event, ctx) => {
// event.reason - "manual" (/compact), "threshold", or "overflow"
// event.errorMessage - present for non-abort failures
// event.aborted - true for cancelled/aborted compactions
@@ -495,7 +495,7 @@ pi.on("session_compact_failed", async (event, ctx) => {
Fired on `/tree` navigation. See [Sessions](sessions.md) for tree navigation concepts.
```typescript
-pi.on("session_before_tree", async (event, ctx) => {
+step.on("session_before_tree", async (event, ctx) => {
const { preparation, signal } = event;
return { cancel: true };
// OR provide custom summary:
@@ -508,7 +508,7 @@ pi.on("session_before_tree", async (event, ctx) => {
};
});
-pi.on("session_tree", async (event, ctx) => {
+step.on("session_tree", async (event, ctx) => {
// event.newLeafId, oldLeafId, summaryEntry, fromExtension
});
```
@@ -518,7 +518,7 @@ pi.on("session_tree", async (event, ctx) => {
Fired before a started session runtime is torn down. Use this to clean up resources opened from `session_start` or other session-scoped hooks.
```typescript
-pi.on("session_shutdown", async (event, ctx) => {
+step.on("session_shutdown", async (event, ctx) => {
// event.reason - "quit" | "reload" | "new" | "resume" | "fork"
// event.targetSessionFile - destination session for session replacement flows
// Cleanup, save state, etc.
@@ -532,7 +532,7 @@ pi.on("session_shutdown", async (event, ctx) => {
Fired after user submits prompt, before agent loop. Can inject a message and/or modify the system prompt.
```typescript
-pi.on("before_agent_start", async (event, ctx) => {
+step.on("before_agent_start", async (event, ctx) => {
// event.prompt - user's prompt text
// event.images - attached images (if any)
// event.systemPrompt - current chained system prompt for this handler
@@ -569,13 +569,13 @@ Inside `before_agent_start`, `event.systemPrompt` and `ctx.getSystemPrompt()` bo
`agent_start` fires when a low-level agent run begins. `agent_end` fires when that run ends, but Step may still auto-retry, auto-compact and retry, or continue with queued follow-up messages. Use `agent_settled` for status integrations that need to know Step will not continue running automatically.
```typescript
-pi.on("agent_start", async (_event, ctx) => {});
+step.on("agent_start", async (_event, ctx) => {});
-pi.on("agent_end", async (event, ctx) => {
+step.on("agent_end", async (event, ctx) => {
// event.messages - messages from this low-level run
});
-pi.on("agent_settled", async (_event, ctx) => {
+step.on("agent_settled", async (_event, ctx) => {
// ctx.isIdle() is true here unless another extension started a new run.
});
```
@@ -587,13 +587,13 @@ Notification-only lifecycle events for blocking user-facing extension UI prompts
Nested or overlapping prompts are coalesced into one outer waiting span. Handlers are invoked best-effort and are not awaited before showing or closing the prompt.
```typescript
-pi.on("ui_prompt_start", async (event, ctx) => {
+step.on("ui_prompt_start", async (event, ctx) => {
// event.reason === "ui_prompt"
// event.kind: "select" | "confirm" | "input" | "editor" | "custom"
// event.title: prompt title when available
});
-pi.on("ui_prompt_end", async (event, ctx) => {
+step.on("ui_prompt_end", async (event, ctx) => {
// Step is no longer waiting on that UI prompt span.
});
```
@@ -603,11 +603,11 @@ pi.on("ui_prompt_end", async (event, ctx) => {
Fired for each turn (one LLM response + tool calls).
```typescript
-pi.on("turn_start", async (event, ctx) => {
+step.on("turn_start", async (event, ctx) => {
// event.turnIndex, event.timestamp
});
-pi.on("turn_end", async (event, ctx) => {
+step.on("turn_end", async (event, ctx) => {
// event.turnIndex, event.message, event.toolResults
});
```
@@ -621,16 +621,16 @@ Fired for message lifecycle updates.
- `message_end` handlers can return `{ message }` to replace the finalized message. The replacement must keep the same `role`.
```typescript
-pi.on("message_start", async (event, ctx) => {
+step.on("message_start", async (event, ctx) => {
// event.message
});
-pi.on("message_update", async (event, ctx) => {
+step.on("message_update", async (event, ctx) => {
// event.message
// event.assistantMessageEvent (token-by-token stream event)
});
-pi.on("message_end", async (event, ctx) => {
+step.on("message_end", async (event, ctx) => {
if (event.message.role !== "assistant") return;
return {
@@ -659,15 +659,15 @@ In parallel tool mode:
- final `toolResult` message events are still emitted later in assistant source order
```typescript
-pi.on("tool_execution_start", async (event, ctx) => {
+step.on("tool_execution_start", async (event, ctx) => {
// event.toolCallId, event.toolName, event.args
});
-pi.on("tool_execution_update", async (event, ctx) => {
+step.on("tool_execution_update", async (event, ctx) => {
// event.toolCallId, event.toolName, event.args, event.partialResult
});
-pi.on("tool_execution_end", async (event, ctx) => {
+step.on("tool_execution_end", async (event, ctx) => {
// event.toolCallId, event.toolName, event.result, event.isError
});
```
@@ -677,7 +677,7 @@ pi.on("tool_execution_end", async (event, ctx) => {
Fired before each LLM call. Modify messages non-destructively. See [Session Format](session-format.md) for message types.
```typescript
-pi.on("context", async (event, ctx) => {
+step.on("context", async (event, ctx) => {
// event.messages - deep copy, safe to modify
const filtered = event.messages.filter(m => !shouldPrune(m));
return { messages: filtered };
@@ -691,7 +691,7 @@ Fired after the outgoing HTTP headers are assembled. Use it to add, override, or
Handlers mutate `event.headers` in place. Set a key to a string to add or override it, or to `null` to delete it.
```typescript
-pi.on("before_provider_headers", (event, ctx) => {
+step.on("before_provider_headers", (event, ctx) => {
// Add or override — e.g. a session id for gateway tracing/attribution
event.headers["x-session-id"] = ctx.sessionManager.getSessionId();
@@ -709,7 +709,7 @@ Fired after the provider-specific payload is built, right before the request is
This hook can rewrite provider-level system instructions or remove them entirely. Those payload-level changes are not reflected by `ctx.getSystemPrompt()`, which reports Step's system prompt string rather than the final serialized provider payload.
```typescript
-pi.on("before_provider_request", (event, ctx) => {
+step.on("before_provider_request", (event, ctx) => {
console.log(JSON.stringify(event.payload, null, 2));
// Optional: replace payload
@@ -724,7 +724,7 @@ This is mainly useful for debugging provider serialization and cache behavior.
Fired after an HTTP response is received and before its stream body is consumed. Handlers run in extension load order.
```typescript
-pi.on("after_provider_response", (event, ctx) => {
+step.on("after_provider_response", (event, ctx) => {
// event.status - HTTP status code
// event.headers - normalized response headers
if (event.status === 429) {
@@ -742,7 +742,7 @@ Header availability depends on provider and transport. Providers that abstract H
Fired when the model changes via `/model` command, model cycling (`Ctrl+P`), or session restore.
```typescript
-pi.on("model_select", async (event, ctx) => {
+step.on("model_select", async (event, ctx) => {
// event.model - newly selected model
// event.previousModel - previous model (undefined if first selection)
// event.source - "set" | "cycle" | "restore"
@@ -763,7 +763,7 @@ Use this to update UI elements (status bars, footers) or perform model-specific
Fired when the thinking level changes. This is notification-only; handler return values are ignored.
```typescript
-pi.on("thinking_level_select", async (event, ctx) => {
+step.on("thinking_level_select", async (event, ctx) => {
// event.level - newly selected thinking level
// event.previousLevel - previous thinking level
@@ -771,7 +771,7 @@ pi.on("thinking_level_select", async (event, ctx) => {
});
```
-Use this to update extension UI when `pi.setThinkingLevel()`, model changes, or built-in thinking-level controls change the active thinking level.
+Use this to update extension UI when `step.setThinkingLevel()`, model changes, or built-in thinking-level controls change the active thinking level.
### Tool Events
@@ -795,7 +795,7 @@ Behavior guarantees:
```typescript
import { isToolCallEventType } from "@step-harness/coding-agent";
-pi.on("tool_call", async (event, ctx) => {
+step.on("tool_call", async (event, ctx) => {
// event.toolName - "bash", "read", "write", "edit", etc.
// event.toolCallId
// event.input - tool parameters (mutable)
@@ -832,7 +832,7 @@ Use `isToolCallEventType` with explicit type parameters:
import { isToolCallEventType } from "@step-harness/coding-agent";
import type { MyToolInput } from "my-extension";
-pi.on("tool_call", (event) => {
+step.on("tool_call", (event) => {
if (isToolCallEventType<"my_tool", MyToolInput>("my_tool", event)) {
event.input.action; // typed
}
@@ -855,7 +855,7 @@ Use `ctx.signal` for nested async work inside the handler. This lets Esc cancel
```typescript
import { isBashToolResult } from "@step-harness/coding-agent";
-pi.on("tool_result", async (event, ctx) => {
+step.on("tool_result", async (event, ctx) => {
// event.toolName, event.toolCallId, event.input
// event.content, event.details, event.isError, event.usage
@@ -883,7 +883,7 @@ Fired when user executes `!` or `!!` commands. **Can intercept.**
```typescript
import { createLocalBashOperations } from "@step-harness/coding-agent";
-pi.on("user_bash", (event, ctx) => {
+step.on("user_bash", (event, ctx) => {
// event.command - the bash command
// event.excludeFromContext - true if !! prefix
// event.cwd - working directory
@@ -920,7 +920,7 @@ Fired when user input is received, after extension commands are checked but befo
5. Agent processing begins (`before_agent_start`, etc.)
```typescript
-pi.on("input", async (event, ctx) => {
+step.on("input", async (event, ctx) => {
// event.text - raw input (before skill/template expansion)
// event.images - attached images, if any
// event.source - "interactive" (typed), "rpc" (API), or "extension" (via sendUserMessage)
@@ -983,8 +983,8 @@ Use `CONFIG_DIR_NAME` instead of hardcoding `.stepcode` when constructing projec
import { CONFIG_DIR_NAME, type ExtensionAPI } from "@step-harness/coding-agent";
import { join } from "node:path";
-export default function (pi: ExtensionAPI) {
- pi.on("session_start", (_event, ctx) => {
+export default function (step: ExtensionAPI) {
+ step.on("session_start", (_event, ctx) => {
const projectConfigPath = join(ctx.cwd, CONFIG_DIR_NAME, "my-extension.json");
// ...
});
@@ -1012,7 +1012,7 @@ ctx.sessionManager.getLeafId() // Current leaf entry ID
### ctx.modelRegistry / ctx.model / ctx.thinkingLevel / ctx.scopedModels
-Access to models, providers, and resolved authentication. `ctx.modelRegistry.getProvider(id)` returns the effective pi-ai provider, while `getProviderAuth(id)` resolves its current API key, headers, base URL, and provider-scoped environment without requiring a loaded model. `ctx.model` is the active model, and `ctx.thinkingLevel` is its current effective thinking level.
+Access to models, providers, and resolved authentication. `ctx.modelRegistry.getProvider(id)` returns the effective provider, while `getProviderAuth(id)` resolves its current API key, headers, base URL, and provider-scoped environment without requiring a loaded model. `ctx.model` is the active model, and `ctx.thinkingLevel` is its current effective thinking level.
`ctx.scopedModels` is the read-only list of models scoped to the current session — the same set the `/scoped-models` command shows. It is resolved at session start from the `--models` CLI flag and the `enabledModels` setting (matched against the available catalogue with minimatch on `provider/modelId` or a bare `modelId`). It is empty when no scoping is configured, meaning every available model is usable. Each entry is `{ model, thinkingLevel? }`, where `thinkingLevel` is set only when a pattern pinned it (e.g. `anthropic/*:high`). Use it to populate a model picker that mirrors the built-in one instead of enumerating the whole catalogue via `ctx.modelRegistry.getAvailable()`.
@@ -1029,7 +1029,7 @@ Use this for abort-aware nested work started by extension handlers, for example:
It is usually `undefined` in idle or non-turn contexts such as session events, extension commands, and shortcuts fired while step is idle.
```typescript
-pi.on("tool_result", async (event, ctx) => {
+step.on("tool_result", async (event, ctx) => {
const response = await fetch("https://example.com/api", {
method: "POST",
body: JSON.stringify(event),
@@ -1056,7 +1056,7 @@ Request a graceful shutdown of step.
Emits `session_shutdown` event to all extensions before exiting. Available in all contexts (event handlers, tools, commands, shortcuts).
```typescript
-pi.on("tool_call", (event, ctx) => {
+step.on("tool_call", (event, ctx) => {
if (isFatal(event.input)) {
ctx.shutdown();
}
@@ -1100,7 +1100,7 @@ Returns Step's current system prompt string.
- If later-loaded extensions run after yours, they can still change what is ultimately sent.
```typescript
-pi.on("before_agent_start", (event, ctx) => {
+step.on("before_agent_start", (event, ctx) => {
const prompt = ctx.getSystemPrompt();
console.log(`System prompt length: ${prompt.length}`);
});
@@ -1128,7 +1128,7 @@ This reports the current base prompt inputs. It does not include per-turn `befor
Wait for the agent to fully settle, including automatic retries, auto-compaction retries, and queued continuations:
```typescript
-pi.registerCommand("my-cmd", {
+step.registerCommand("my-cmd", {
handler: async (args, ctx) => {
await ctx.waitForIdle();
// Agent is now idle, safe to modify session
@@ -1237,7 +1237,7 @@ To discover available sessions, use the static `SessionManager.list()` or `Sessi
```typescript
import { SessionManager } from "@step-harness/coding-agent";
-pi.registerCommand("switch", {
+step.registerCommand("switch", {
description: "Switch to another session",
handler: async (args, ctx) => {
const sessions = await SessionManager.list(ctx.cwd);
@@ -1271,7 +1271,7 @@ Lifecycle and footguns:
Safe pattern:
```typescript
-pi.registerCommand("handoff", {
+step.registerCommand("handoff", {
handler: async (_args, ctx) => {
const kickoff = "Continue from the replacement session";
await ctx.newSession({
@@ -1286,14 +1286,14 @@ pi.registerCommand("handoff", {
Unsafe pattern:
```typescript
-pi.registerCommand("handoff", {
+step.registerCommand("handoff", {
handler: async (_args, ctx) => {
const oldSessionManager = ctx.sessionManager;
await ctx.newSession({
withSession: async (_ctx) => {
// stale old objects: do not do this
oldSessionManager.getSessionFile();
- pi.sendUserMessage("wrong");
+ step.sendUserMessage("wrong");
},
});
},
@@ -1305,7 +1305,7 @@ pi.registerCommand("handoff", {
Run the same reload flow as `/reload`.
```typescript
-pi.registerCommand("reload-runtime", {
+step.registerCommand("reload-runtime", {
description: "Reload extensions, skills, prompts, themes, and context files",
handler: async (_args, ctx) => {
await ctx.reload();
@@ -1332,8 +1332,8 @@ Example tool the LLM can call to trigger reload:
import type { ExtensionAPI } from "@step-harness/coding-agent";
import { Type } from "typebox";
-export default function (pi: ExtensionAPI) {
- pi.registerCommand("reload-runtime", {
+export default function (step: ExtensionAPI) {
+ step.registerCommand("reload-runtime", {
description: "Reload extensions, skills, prompts, themes, and context files",
handler: async (_args, ctx) => {
await ctx.reload();
@@ -1341,13 +1341,13 @@ export default function (pi: ExtensionAPI) {
},
});
- pi.registerTool({
+ step.registerTool({
name: "reload_runtime",
label: "Reload Runtime",
description: "Reload extensions, skills, prompts, themes, and context files",
parameters: Type.Object({}),
async execute() {
- pi.sendUserMessage("/reload-runtime", { deliverAs: "followUp" });
+ step.sendUserMessage("/reload-runtime", { deliverAs: "followUp" });
return {
content: [{ type: "text", text: "Queued /reload-runtime as a follow-up command." }],
};
@@ -1358,17 +1358,17 @@ export default function (pi: ExtensionAPI) {
## ExtensionAPI Methods
-### pi.on(event, handler)
+### step.on(event, handler)
Subscribe to events. See [Events](#events) for event types and return values.
-### pi.registerTool(definition)
+### step.registerTool(definition)
Register a custom tool callable by the LLM. See [Custom Tools](#custom-tools) for full details.
-`pi.registerTool()` works both during extension load and after startup. You can call it inside `session_start`, command handlers, or other event handlers. New tools are refreshed immediately in the same session, so they appear in `pi.getAllTools()` and are callable by the LLM without `/reload`.
+`step.registerTool()` works both during extension load and after startup. You can call it inside `session_start`, command handlers, or other event handlers. New tools are refreshed immediately in the same session, so they appear in `step.getAllTools()` and are callable by the LLM without `/reload`.
-Use `pi.setActiveTools()` to enable or disable tools (including dynamically added tools) at runtime.
+Use `step.setActiveTools()` to enable or disable tools (including dynamically added tools) at runtime.
Use `promptSnippet` to opt a custom tool into a one-line entry in `Available tools`, and `promptGuidelines` to append tool-specific bullets to the default `Guidelines` section when the tool is active.
@@ -1380,7 +1380,7 @@ See [dynamic-tools.ts](../examples/extensions/dynamic-tools.ts) for a full examp
import { Type } from "typebox";
import { StringEnum } from "@step-harness/providers";
-pi.registerTool({
+step.registerTool({
name: "my_tool",
label: "My Tool",
description: "What this tool does",
@@ -1413,12 +1413,12 @@ pi.registerTool({
});
```
-### pi.sendMessage(message, options?)
+### step.sendMessage(message, options?)
-Inject a custom message into the session. Custom messages participate in LLM context. For durable TUI-only content that should not be sent to the LLM, use [`pi.appendEntry()`](#piappendentrycustomtype-data) with [`pi.registerEntryRenderer()`](#piregisterentryrenderercustomtype-renderer).
+Inject a custom message into the session. Custom messages participate in LLM context. For durable TUI-only content that should not be sent to the LLM, use [`step.appendEntry()`](#stepappendentrycustomtype-data) with [`step.registerEntryRenderer()`](#stepregisterentryrenderercustomtype-renderer).
```typescript
-pi.sendMessage({
+step.sendMessage({
customType: "my-extension",
content: "Message text",
display: true,
@@ -1436,26 +1436,26 @@ pi.sendMessage({
- `"nextTurn"` - Queued for next user prompt. Does not interrupt or trigger anything.
- `triggerTurn: true` - If agent is idle, trigger an LLM response immediately. Only applies to `"steer"` and `"followUp"` modes (ignored for `"nextTurn"`).
-### pi.sendUserMessage(content, options?)
+### step.sendUserMessage(content, options?)
Send a user message to the agent. Unlike `sendMessage()` which sends custom messages, this sends an actual user message that appears as if typed by the user. Always triggers a turn.
```typescript
// Simple text message
-pi.sendUserMessage("What is 2+2?");
+step.sendUserMessage("What is 2+2?");
// With content array (text + images)
-pi.sendUserMessage([
+step.sendUserMessage([
{ type: "text", text: "Describe this image:" },
{ type: "image", source: { type: "base64", mediaType: "image/png", data: "..." } },
]);
// During streaming - must specify delivery mode
-pi.sendUserMessage("Focus on error handling", { deliverAs: "steer" });
-pi.sendUserMessage("And then summarize", { deliverAs: "followUp" });
+step.sendUserMessage("Focus on error handling", { deliverAs: "steer" });
+step.sendUserMessage("And then summarize", { deliverAs: "followUp" });
// Opt in to extension command dispatch and skill/prompt template expansion
-pi.sendUserMessage("/review src/index.ts", { expandPromptTemplates: true });
+step.sendUserMessage("/review src/index.ts", { expandPromptTemplates: true });
```
**Options:**
@@ -1468,16 +1468,16 @@ When not streaming, the message is sent immediately and triggers a new turn. Whe
See [send-user-message.ts](../examples/extensions/send-user-message.ts) for a complete example.
-### pi.appendEntry(customType, data?)
+### step.appendEntry(customType, data?)
-Persist extension data. Custom entries do NOT participate in LLM context. In interactive mode, they can also render inside the chat transcript when paired with `pi.registerEntryRenderer()`.
+Persist extension data. Custom entries do NOT participate in LLM context. In interactive mode, they can also render inside the chat transcript when paired with `step.registerEntryRenderer()`.
```typescript
-pi.appendEntry("my-state", { count: 42 });
-pi.appendEntry("status-card", { title: "Indexed files", count: 17 });
+step.appendEntry("my-state", { count: 42 });
+step.appendEntry("status-card", { title: "Indexed files", count: 17 });
// Restore on reload
-pi.on("session_start", async (_event, ctx) => {
+step.on("session_start", async (_event, ctx) => {
for (const entry of ctx.sessionManager.getEntries()) {
if (entry.type === "custom" && entry.customType === "my-state") {
// Reconstruct from entry.data
@@ -1486,35 +1486,35 @@ pi.on("session_start", async (_event, ctx) => {
});
```
-### pi.setSessionName(name)
+### step.setSessionName(name)
Set the session display name (shown in session selector instead of first message).
```typescript
-pi.setSessionName("Refactor auth module");
+step.setSessionName("Refactor auth module");
```
-### pi.getSessionName()
+### step.getSessionName()
Get the current session name, if set.
```typescript
-const name = pi.getSessionName();
+const name = step.getSessionName();
if (name) {
console.log(`Session: ${name}`);
}
```
-### pi.setLabel(entryId, label)
+### step.setLabel(entryId, label)
Set or clear a label on an entry. Labels are user-defined markers for bookmarking and navigation (shown in `/tree` selector).
```typescript
// Set a label
-pi.setLabel(entryId, "checkpoint-before-refactor");
+step.setLabel(entryId, "checkpoint-before-refactor");
// Clear a label
-pi.setLabel(entryId, undefined);
+step.setLabel(entryId, undefined);
// Read labels via sessionManager
const label = ctx.sessionManager.getLabel(entryId);
@@ -1522,14 +1522,14 @@ const label = ctx.sessionManager.getLabel(entryId);
Labels persist in the session and survive restarts. Use them to mark important points (turns, checkpoints) in the conversation tree.
-### pi.registerCommand(name, options)
+### step.registerCommand(name, options)
Register a command.
If multiple extensions register the same command name, step keeps them all and assigns numeric invocation suffixes in load order, for example `/review:1` and `/review:2`.
```typescript
-pi.registerCommand("stats", {
+step.registerCommand("stats", {
description: "Show session statistics",
handler: async (args, ctx) => {
const count = ctx.sessionManager.getEntries().length;
@@ -1543,7 +1543,7 @@ Optional: add argument auto-completion for `/command ...`:
```typescript
import type { AutocompleteItem } from "@step-harness/pi-tui";
-pi.registerCommand("deploy", {
+step.registerCommand("deploy", {
description: "Deploy to an environment",
getArgumentCompletions: (prefix: string): AutocompleteItem[] | null => {
const envs = ["dev", "staging", "prod"];
@@ -1557,18 +1557,18 @@ pi.registerCommand("deploy", {
});
```
-### pi.setUnavailableTools(names)
+### step.setUnavailableTools(names)
Declare tool names unavailable for this extension's current runtime. This works during extension loading and after startup, and also applies to matching tools registered later.
```typescript
-export default function (pi: ExtensionAPI) {
- pi.setUnavailableTools(["remote_lookup"]);
+export default function (step: ExtensionAPI) {
+ step.setUnavailableTools(["remote_lookup"]);
- pi.registerCommand("enable-remote-lookup", {
+ step.registerCommand("enable-remote-lookup", {
handler: async () => {
// Clear this extension's declaration after its prerequisite is satisfied.
- pi.setUnavailableTools([]);
+ step.setUnavailableTools([]);
},
});
}
@@ -1580,13 +1580,13 @@ The requested active selection is retained while availability changes. Releasing
Use this for tools whose prerequisites or session policy make them unavailable. Tools that require conditional confirmation can stay available. Availability controls discovery and selection; keep execution-time permission checks in the tool or `tool_call` hook. A failed tool call does not automatically change availability.
-### pi.getCommands()
+### step.getCommands()
Get the slash commands available for invocation via `prompt` in the current session. Includes extension commands, prompt templates, and skill commands.
The list matches the RPC `get_commands` ordering: extensions first, then templates, then skills.
```typescript
-const commands = pi.getCommands();
+const commands = step.getCommands();
const bySource = commands.filter((command) => command.source === "extension");
const userScoped = commands.filter((command) => command.sourceInfo.scope === "user");
```
@@ -1613,11 +1613,11 @@ Use `sourceInfo` as the canonical provenance field. Do not infer ownership from
Built-in interactive commands (like `/model` and `/settings`) are not included here. They are handled only in interactive
mode and would not execute if sent via `prompt`.
-### pi.registerMessageRenderer(customType, renderer)
+### step.registerMessageRenderer(customType, renderer)
-Register a custom TUI renderer for custom messages with your `customType`. Custom messages are created with `pi.sendMessage()` and participate in LLM context. See [Custom UI](#custom-ui).
+Register a custom TUI renderer for custom messages with your `customType`. Custom messages are created with `step.sendMessage()` and participate in LLM context. See [Custom UI](#custom-ui).
-### pi.registerMarkdownTransformer(transformer)
+### step.registerMarkdownTransformer(transformer)
Register a transformer for the Markdown in normal user text, assistant text, and thinking blocks. Transformers run in extension load order, and each transformer receives the Markdown returned by the previous transformer. After the chain finishes, Step renders the transformed content with its built-in renderer.
@@ -1630,7 +1630,7 @@ The transformer receives the Markdown string and a context with:
Return the transformed Markdown:
```typescript
-pi.registerMarkdownTransformer((markdown, { messageType, isStreaming }) => {
+step.registerMarkdownTransformer((markdown, { messageType, isStreaming }) => {
if (isStreaming || messageType === "assistant-thinking") return markdown;
return markdown.replaceAll("-->", "→");
});
@@ -1638,14 +1638,14 @@ pi.registerMarkdownTransformer((markdown, { messageType, isStreaming }) => {
If a transformer throws, Step keeps the Markdown produced so far and continues with the next transformer. The hook is display-only: the original message remains unchanged in the session and model context. It runs for new user messages, assistant streaming updates, restored session messages, and terminal width changes, so transformers should remain synchronous and inexpensive.
-### pi.registerEntryRenderer(customType, renderer)
+### step.registerEntryRenderer(customType, renderer)
-Register a custom TUI renderer for custom entries with your `customType`. Custom entries are created with `pi.appendEntry()` and do not participate in LLM context.
+Register a custom TUI renderer for custom entries with your `customType`. Custom entries are created with `step.appendEntry()` and do not participate in LLM context.
```typescript
import { Box, Text } from "@step-harness/pi-tui";
-pi.registerEntryRenderer("status-card", (entry, { expanded }, theme) => {
+step.registerEntryRenderer("status-card", (entry, { expanded }, theme) => {
const data = entry.data as { title: string; count: number };
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
box.addChild(new Text(`${theme.bold(data.title)}: ${data.count}`));
@@ -1655,15 +1655,15 @@ pi.registerEntryRenderer("status-card", (entry, { expanded }, theme) => {
return box;
});
-pi.appendEntry("status-card", { title: "Indexed files", count: 17 });
+step.appendEntry("status-card", { title: "Indexed files", count: 17 });
```
-### pi.registerShortcut(shortcut, options)
+### step.registerShortcut(shortcut, options)
Register a keyboard shortcut. See [keybindings.md](keybindings.md) for the shortcut format and built-in keybindings.
```typescript
-pi.registerShortcut("ctrl+shift+p", {
+step.registerShortcut("ctrl+shift+p", {
description: "Toggle plan mode",
handler: async (ctx) => {
ctx.ui.notify("Toggled!");
@@ -1671,39 +1671,39 @@ pi.registerShortcut("ctrl+shift+p", {
});
```
-### pi.registerFlag(name, options)
+### step.registerFlag(name, options)
Register a CLI flag.
```typescript
-pi.registerFlag("plan", {
+step.registerFlag("plan", {
description: "Start in plan mode",
type: "boolean",
default: false,
});
// Check value
-if (pi.getFlag("plan")) {
+if (step.getFlag("plan")) {
// Plan mode enabled
}
```
-### pi.exec(command, args, options?)
+### step.exec(command, args, options?)
Execute a shell command.
```typescript
-const result = await pi.exec("git", ["status"], { signal, timeout: 5000 });
+const result = await step.exec("git", ["status"], { signal, timeout: 5000 });
// result.stdout, result.stderr, result.code, result.killed
```
-### pi.getActiveTools() / pi.getAllTools() / pi.setActiveTools(names)
+### step.getActiveTools() / step.getAllTools() / step.setActiveTools(names)
-Manage active tools. This works for both built-in tools and dynamically registered tools. `pi.getActiveTools()` returns the active tool names as `string[]`; `pi.getAllTools()` returns metadata for configured tools that remain available after CLI selectors and extension availability declarations.
+Manage active tools. This works for both built-in tools and dynamically registered tools. `step.getActiveTools()` returns the active tool names as `string[]`; `step.getAllTools()` returns metadata for configured tools that remain available after CLI selectors and extension availability declarations.
```typescript
-const active = pi.getActiveTools(); // ["read", "bash", ...]
-const all = pi.getAllTools();
+const active = step.getActiveTools(); // ["read", "bash", ...]
+const all = step.getAllTools();
// all = [{
// name: "read",
// description: "Read file contents...",
@@ -1713,50 +1713,50 @@ const all = pi.getAllTools();
// }, ...]
const builtinTools = all.filter((t) => t.sourceInfo.source === "builtin");
const extensionTools = all.filter((t) => t.sourceInfo.source !== "builtin" && t.sourceInfo.source !== "sdk");
-pi.setActiveTools([...new Set([...active, "my_custom_tool"])]); // Keep current tools and enable my_custom_tool
-pi.setActiveTools(["read", "bash"]); // Switch to read-only
+step.setActiveTools([...new Set([...active, "my_custom_tool"])]); // Keep current tools and enable my_custom_tool
+step.setActiveTools(["read", "bash"]); // Switch to read-only
```
-`pi.getAllTools()` returns `name`, `description`, `parameters`, `promptGuidelines`, and `sourceInfo`.
+`step.getAllTools()` returns `name`, `description`, `parameters`, `promptGuidelines`, and `sourceInfo`.
Typical `sourceInfo.source` values:
- `builtin` for built-in tools
- `sdk` for tools passed via `createAgentSession({ customTools })`
- extension source metadata for tools registered by extensions
-### pi.setModel(model)
+### step.setModel(model)
Set the current model. Returns `false` if no API key is available for the model. See [models.md](models.md) for configuring custom models.
```typescript
const model = ctx.modelRegistry.find("anthropic", "claude-sonnet-4-5");
if (model) {
- const success = await pi.setModel(model);
+ const success = await step.setModel(model);
if (!success) {
ctx.ui.notify("No API key for this model", "error");
}
}
```
-### pi.getThinkingLevel() / pi.setThinkingLevel(level)
+### step.getThinkingLevel() / step.setThinkingLevel(level)
Get or set the thinking level. Level is clamped to model capabilities (non-reasoning models always use "off"). Changes emit `thinking_level_select`.
```typescript
-const current = pi.getThinkingLevel(); // "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"
-pi.setThinkingLevel("high");
+const current = step.getThinkingLevel(); // "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"
+step.setThinkingLevel("high");
```
-### pi.events
+### step.events
Shared event bus for communication between extensions:
```typescript
-pi.events.on("my:event", (data) => { ... });
-pi.events.emit("my:event", { ... });
+step.events.on("my:event", (data) => { ... });
+step.events.emit("my:event", { ... });
```
-### pi.registerProvider(name, config)
+### step.registerProvider(name, config)
Register or override a model provider dynamically. Useful for proxies, custom endpoints, or team-wide model configurations.
@@ -1795,10 +1795,10 @@ const provider = createProvider({
api: openAICompletionsApi(),
});
-pi.registerProvider(provider);
+step.registerProvider(provider);
// Register a new provider with custom models
-pi.registerProvider("my-proxy", {
+step.registerProvider("my-proxy", {
name: "My Proxy",
baseUrl: "https://proxy.example.com",
apiKey: "$PROXY_API_KEY", // env var reference
@@ -1817,7 +1817,7 @@ pi.registerProvider("my-proxy", {
});
// Register a live llama.cpp catalog without persisting discovered models
-pi.registerProvider("llama.cpp", {
+step.registerProvider("llama.cpp", {
baseUrl: "http://localhost:8080/v1",
apiKey: "local",
api: "openai-completions",
@@ -1837,12 +1837,12 @@ pi.registerProvider("llama.cpp", {
});
// Override baseUrl for an existing provider (keeps all models)
-pi.registerProvider("anthropic", {
+step.registerProvider("anthropic", {
baseUrl: "https://proxy.example.com"
});
// Register provider with OAuth support for /login
-pi.registerProvider("corporate-ai", {
+step.registerProvider("corporate-ai", {
baseUrl: "https://ai.corp.com",
api: "openai-responses",
models: [...],
@@ -1866,7 +1866,7 @@ pi.registerProvider("corporate-ai", {
});
```
-The object form accepts a complete pi-ai `Provider`, including native `auth`, `getModels`, `refreshModels`, `filterModels`, `stream`, and `streamSimple` behavior.
+The object form accepts a complete `Provider`, including native `auth`, `getModels`, `refreshModels`, `filterModels`, `stream`, and `streamSimple` behavior.
**Legacy config options:**
- `name` - Display name for the provider in UI such as `/login`.
@@ -1882,17 +1882,17 @@ The object form accepts a complete pi-ai `Provider`, including native `auth`, `g
See [custom-provider.md](custom-provider.md) for advanced topics: custom streaming APIs, OAuth details, model definition reference.
-### pi.unregisterProvider(name)
+### step.unregisterProvider(name)
Remove a previously registered provider and its models. Built-in models that were overridden by the provider are restored. Has no effect if the provider was not registered.
Like `registerProvider`, this takes effect immediately when called after the initial load phase, so a `/reload` is not required.
```typescript
-pi.registerCommand("my-setup-teardown", {
+step.registerCommand("my-setup-teardown", {
description: "Remove the custom proxy provider",
handler: async (_args, _ctx) => {
- pi.unregisterProvider("my-proxy");
+ step.unregisterProvider("my-proxy");
},
});
```
@@ -1902,11 +1902,11 @@ pi.registerCommand("my-setup-teardown", {
Extensions with state should store it in tool result `details` for proper branching support:
```typescript
-export default function (pi: ExtensionAPI) {
+export default function (step: ExtensionAPI) {
let items: string[] = [];
// Reconstruct state from session
- pi.on("session_start", async (_event, ctx) => {
+ step.on("session_start", async (_event, ctx) => {
items = [];
for (const entry of ctx.sessionManager.getBranch()) {
if (entry.type === "message" && entry.message.role === "toolResult") {
@@ -1917,7 +1917,7 @@ export default function (pi: ExtensionAPI) {
}
});
- pi.registerTool({
+ step.registerTool({
name: "my_tool",
// ...
async execute(toolCallId, params, signal, onUpdate, ctx) {
@@ -1933,11 +1933,11 @@ export default function (pi: ExtensionAPI) {
## Custom Tools
-Register tools the LLM can call via `pi.registerTool()`. Tools appear in the system prompt and can have custom rendering.
+Register tools the LLM can call via `step.registerTool()`. Tools appear in the system prompt and can have custom rendering.
Use `promptSnippet` for a short one-line entry in the `Available tools` section in the default system prompt. If omitted, custom tools are left out of that section.
-Use `promptGuidelines` to add tool-specific bullets to the default system prompt `Guidelines` section. These bullets are included only while the tool is active (for example, after `pi.setActiveTools([...])`).
+Use `promptGuidelines` to add tool-specific bullets to the default system prompt `Guidelines` section. These bullets are included only while the tool is active (for example, after `step.setActiveTools([...])`).
**Important:** `promptGuidelines` bullets are appended flat to the `Guidelines` section with no tool name prefix or grouping. Each guideline must name the tool it refers to — avoid "Use this tool when..." because the LLM cannot tell which tool "this" means. Write "Use my_tool when..." instead.
@@ -1980,7 +1980,7 @@ import { Type } from "typebox";
import { StringEnum } from "@step-harness/providers";
import { Text } from "@step-harness/pi-tui";
-pi.registerTool({
+step.registerTool({
name: "my_tool",
label: "My Tool",
description: "What this tool does (shown to LLM)",
@@ -2013,8 +2013,8 @@ pi.registerTool({
details: { progress: 50 },
});
- // Run commands via pi.exec (captured from extension closure)
- const result = await pi.exec("some-command", [], { signal });
+ // Run commands via step.exec (captured from extension closure)
+ const result = await step.exec("some-command", [], { signal });
// Return result
return {
@@ -2056,7 +2056,7 @@ async execute(toolCallId, params) {
Example: an older session may contain an `edit` tool call with top-level `oldText` and `newText`, while the current schema only accepts `edits: [{ oldText, newText }]`.
```typescript
-pi.registerTool({
+step.registerTool({
name: "edit",
label: "Edit",
description: "Edit a single file using exact text replacement",
@@ -2147,7 +2147,7 @@ const remoteRead = createReadTool(cwd, {
});
// Register, checking flag at execution time
-pi.registerTool({
+step.registerTool({
...remoteRead,
async execute(id, params, signal, onUpdate, _ctx) {
const ssh = getSshConfig();
@@ -2239,14 +2239,14 @@ See [examples/extensions/truncated-tool.ts](../examples/extensions/truncated-too
One extension can register multiple tools with shared state:
```typescript
-export default function (pi: ExtensionAPI) {
+export default function (step: ExtensionAPI) {
let connection = null;
- pi.registerTool({ name: "db_connect", ... });
- pi.registerTool({ name: "db_query", ... });
- pi.registerTool({ name: "db_close", ... });
+ step.registerTool({ name: "db_connect", ... });
+ step.registerTool({ name: "db_query", ... });
+ step.registerTool({ name: "db_close", ... });
- pi.on("session_shutdown", async () => {
+ step.on("session_shutdown", async () => {
connection?.close();
});
}
@@ -2261,7 +2261,7 @@ By default, tool output is wrapped in a `Box` that handles padding and backgroun
Set `renderShell: "self"` when the tool should render its own shell instead of using the default `Box`. This is useful for tools that need complete control over framing or background behavior, for example large previews that must stay visually stable after the tool settles.
```typescript
-pi.registerTool({
+step.registerTool({
name: "my_tool",
label: "My Tool",
description: "Custom shell example",
@@ -2379,19 +2379,19 @@ If a slot renderer is not defined or throws:
### Dynamic Tool Loading
-Extensions can register many tools while keeping only a small initial set active. A tool can then add more tools with `pi.setActiveTools()` during execution. Step detects purely additive changes, records the newly available tool names on that tool result, and applies the updated active set before the next model request.
+Extensions can register many tools while keeping only a small initial set active. A tool can then add more tools with `step.setActiveTools()` during execution. Step detects purely additive changes, records the newly available tool names on that tool result, and applies the updated active set before the next model request.
This works with every model. Models with native deferred-loading support preserve the stable prompt prefix and load the new definitions at the tool-result position. Other models use the fallback described below.
The lifecycle is:
-1. Register every tool with `pi.registerTool()` so it appears in `pi.getAllTools()`.
+1. Register every tool with `step.registerTool()` so it appears in `step.getAllTools()`.
2. Keep loader tools, such as `search_tools`, active and leave searchable tools inactive.
-3. During loader execution, call `pi.setActiveTools([...currentTools, ...matchingTools])`. The change must be additive: do not remove currently active tools in the same call.
+3. During loader execution, call `step.setActiveTools([...currentTools, ...matchingTools])`. The change must be additive: do not remove currently active tools in the same call.
4. Step records which tools were added on the loader's tool result.
5. Before the next model response, Step exposes the added definitions using native deferred loading when supported, or the normal active tool list otherwise.
-You do not need to return provider-specific tool references or mark the loader as a special search tool. The active-tool change is the signal. Names passed to `pi.setActiveTools()` must already be registered; unknown names are ignored.
+You do not need to return provider-specific tool references or mark the loader as a special search tool. The active-tool change is the signal. Names passed to `step.setActiveTools()` must already be registered; unknown names are ignored.
#### Models with native deferred loading
@@ -2422,8 +2422,8 @@ import { Type } from "typebox";
const SEARCHABLE_TOOL_NAMES = new Set(["lookup_weather", "search_issues"]);
-export default function (pi: ExtensionAPI) {
- pi.registerTool({
+export default function (step: ExtensionAPI) {
+ step.registerTool({
name: "lookup_weather",
label: "Lookup Weather",
description: "Look up the current weather for a city",
@@ -2436,7 +2436,7 @@ export default function (pi: ExtensionAPI) {
},
});
- pi.registerTool({
+ step.registerTool({
name: "search_issues",
label: "Search Issues",
description: "Search project issues by keyword",
@@ -2449,7 +2449,7 @@ export default function (pi: ExtensionAPI) {
},
});
- pi.registerTool({
+ step.registerTool({
name: "search_tools",
label: "Search Tools",
description: "Search for and enable tools relevant to a task",
@@ -2463,7 +2463,7 @@ export default function (pi: ExtensionAPI) {
}),
async execute(_toolCallId, params) {
const terms = params.query.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
- const matches = pi.getAllTools()
+ const matches = step.getAllTools()
.filter((tool) => SEARCHABLE_TOOL_NAMES.has(tool.name))
.map((tool) => ({
tool,
@@ -2485,9 +2485,9 @@ export default function (pi: ExtensionAPI) {
};
}
- const active = pi.getActiveTools();
+ const active = step.getActiveTools();
const added = matches.filter((name) => !active.includes(name));
- pi.setActiveTools([...new Set([...active, ...added])]);
+ step.setActiveTools([...new Set([...active, ...added])]);
return {
content: [{
@@ -2501,13 +2501,13 @@ export default function (pi: ExtensionAPI) {
},
});
- pi.on("session_start", () => {
+ step.on("session_start", () => {
// Keep searchable tools registered but initially inactive. Preserve built-ins
// and tools owned by other extensions, and keep the loader itself active.
- const initialTools = pi.getActiveTools().filter(
+ const initialTools = step.getActiveTools().filter(
(name) => !SEARCHABLE_TOOL_NAMES.has(name),
);
- pi.setActiveTools([...new Set([...initialTools, "search_tools"])]);
+ step.setActiveTools([...new Set([...initialTools, "search_tools"])]);
});
}
```
@@ -2645,7 +2645,7 @@ ctx.ui.setFooter((tui, theme) => ({
ctx.ui.setFooter(undefined); // Restore built-in footer
// Terminal title
-ctx.ui.setTitle("pi - my-project");
+ctx.ui.setTitle("step - my-project");
// Editor text
ctx.ui.setEditorText("Prefill text");
@@ -2715,7 +2715,7 @@ Typical pattern:
- delegate `applyCompletion(...)` unless you need custom insertion behavior
```typescript
-pi.on("session_start", (_event, ctx) => {
+step.on("session_start", (_event, ctx) => {
ctx.ui.addAutocompleteProvider((current) => ({
triggerCharacters: ["#"],
async getSuggestions(lines, cursorLine, cursorCol, options) {
@@ -2837,8 +2837,8 @@ class VimEditor extends CustomEditor {
}
}
-export default function (pi: ExtensionAPI) {
- pi.on("session_start", (_event, ctx) => {
+export default function (step: ExtensionAPI) {
+ step.on("session_start", (_event, ctx) => {
ctx.ui.setEditorComponent((tui, theme, keybindings) =>
new VimEditor(tui, theme, keybindings)
);
@@ -2871,7 +2871,7 @@ Register a custom renderer for messages with your `customType`. Use message rend
```typescript
import { Text } from "@step-harness/pi-tui";
-pi.registerMessageRenderer("my-extension", (message, options, theme) => {
+step.registerMessageRenderer("my-extension", (message, options, theme) => {
const { expanded, outputPad } = options;
let text = theme.fg("accent", `[${message.customType}] `);
text += message.content;
@@ -2884,10 +2884,10 @@ pi.registerMessageRenderer("my-extension", (message, options, theme) => {
});
```
-Messages are sent via `pi.sendMessage()`:
+Messages are sent via `step.sendMessage()`:
```typescript
-pi.sendMessage({
+step.sendMessage({
customType: "my-extension", // Matches registerMessageRenderer
content: "Status update",
display: true, // Show in TUI
@@ -2898,11 +2898,11 @@ pi.sendMessage({
For TUI-only content that should not be sent to the LLM, render custom entries instead:
```typescript
-pi.registerEntryRenderer("my-card", (entry, options, theme) => {
+step.registerEntryRenderer("my-card", (entry, options, theme) => {
return new Text(theme.fg("accent", JSON.stringify(entry.data)));
});
-pi.appendEntry("my-card", { status: "done" });
+step.appendEntry("my-card", { status: "done" });
```
### Theme Colors
@@ -3030,7 +3030,7 @@ All examples in [examples/extensions/](../examples/extensions/).
| **Messages & Communication** |||
| `message-renderer.ts` | Custom message rendering | `registerMessageRenderer`, `sendMessage` |
| `entry-renderer.ts` | TUI-only custom entry rendering | `registerEntryRenderer`, `appendEntry` |
-| `event-bus.ts` | Inter-extension events | `pi.events` |
+| `event-bus.ts` | Inter-extension events | `step.events` |
| **Session Metadata** |||
| `session-name.ts` | Name sessions for selector | `setSessionName`, `getSessionName` |
| `bookmark.ts` | Bookmark entries for /tree | `setLabel` |
diff --git a/packages/coding-agent/docs/mcp-catalog.md b/packages/coding-agent/docs/mcp-catalog.md
index fd45bcee..c73c9550 100644
--- a/packages/coding-agent/docs/mcp-catalog.md
+++ b/packages/coding-agent/docs/mcp-catalog.md
@@ -15,7 +15,7 @@ A closed connection removes its tools and changes its status to failed. Session
Extensions can replace part of their own catalog with the existing batch API:
```ts
-pi.registerTools(nextDefinitions, { remove: previousNames });
+step.registerTools(nextDefinitions, { remove: previousNames });
```
Removal affects only the calling extension's registrations. Names supplied in both lists receive the new definition. The registry refreshes once after the batch, so callers see the finished replacement. Removing a winning extension definition can reveal another extension or builtin definition under the existing precedence rules. Empty batches and removal of names the caller does not own do not refresh the registry. Stale extension APIs reject the operation.
diff --git a/packages/coding-agent/docs/models.md b/packages/coding-agent/docs/models.md
index a25447f3..42d42eee 100644
--- a/packages/coding-agent/docs/models.md
+++ b/packages/coding-agent/docs/models.md
@@ -485,8 +485,8 @@ For providers with partial OpenAI compatibility, use the `compat` field.
| `requiresThinkingAsText` | Convert thinking blocks to plain text |
| `requiresReasoningContentOnAssistantMessages` | Include empty `reasoning_content` on all replayed assistant messages when reasoning is enabled |
| `thinkingFormat` | Use `reasoning_effort`, `openrouter`, `deepseek`, `together`, `baseten`, `zai`, `qwen`, `chat-template`, or `qwen-chat-template` thinking parameters |
-| `chatTemplateKwargs` | `chat_template_kwargs` values for `thinkingFormat: "chat-template"`; use `{ "$var": "thinking.enabled" }`, `{ "$var": "thinking.effort" }`, or `{ "$var": "thinking.budget" }` for pi-controlled thinking values |
-| `chatTemplateArgs` | `chat_template_args` values for `thinkingFormat: "baseten"`; use `{ "$var": "thinking.enabled" }`, `{ "$var": "thinking.effort" }`, or `{ "$var": "thinking.budget" }` for pi-controlled thinking values |
+| `chatTemplateKwargs` | `chat_template_kwargs` values for `thinkingFormat: "chat-template"`; use `{ "$var": "thinking.enabled" }`, `{ "$var": "thinking.effort" }`, or `{ "$var": "thinking.budget" }` for agent-controlled thinking values |
+| `chatTemplateArgs` | `chat_template_args` values for `thinkingFormat: "baseten"`; use `{ "$var": "thinking.enabled" }`, `{ "$var": "thinking.effort" }`, or `{ "$var": "thinking.budget" }` for agent-controlled thinking values |
| `thinkingTokenBudgetField` | Top-level request field used to cap reasoning tokens from `thinkingBudgets`, clamped so at least 1024 tokens remain for the answer. `"thinking_token_budget"` (vLLM), `"thinking_budget"` (Qwen/DashScope/SGLang), `"thinking_budget_tokens"` (llama.cpp). Off by default; not set on the generated catalog. |
| `supportsThinkingTokenBudget` | Alias for `thinkingTokenBudgetField: "thinking_token_budget"` (vLLM). Prefer `thinkingTokenBudgetField`. Default: `false`. |
| `cacheControlFormat` | Use Anthropic-style `cache_control` markers on the system prompt, last tool definition, and last user, assistant, or tool-result text content. Currently only `anthropic` is supported. |
diff --git a/packages/coding-agent/docs/packages.md b/packages/coding-agent/docs/packages.md
index 1100fdc3..8e6f5487 100644
--- a/packages/coding-agent/docs/packages.md
+++ b/packages/coding-agent/docs/packages.md
@@ -8,7 +8,7 @@ Step packages bundle extensions, skills, prompt templates, and themes so you can
- [Install and Manage](#install-and-manage)
- [Package Sources](#package-sources)
-- [Creating a Step Package](#creating-a-pi-package)
+- [Creating a Step Package](#creating-a-step-package)
- [Package Structure](#package-structure)
- [Dependencies](#dependencies)
- [Package Filtering](#package-filtering)
@@ -115,7 +115,7 @@ Local paths point to files or directories on disk and are added to settings with
## Creating a Step Package
-Add a `pi` manifest to `package.json` or use conventional directories. Include the `pi-package` keyword for discoverability. Both names are kept for compatibility with already-published packages.
+Add the manifest to `package.json` or use conventional directories. The key itself is `pi` and the discovery keyword is `pi-package`; both keep their legacy spelling so packages published before the rename stay discoverable, and both are read as-is by the loader.
```json
{
diff --git a/packages/coding-agent/docs/prompt-templates.md b/packages/coding-agent/docs/prompt-templates.md
index ba2280d1..280295a9 100644
--- a/packages/coding-agent/docs/prompt-templates.md
+++ b/packages/coding-agent/docs/prompt-templates.md
@@ -10,7 +10,7 @@ Step loads prompt templates from:
- Global: `~/.stepcode/agent/prompts/*.md`
- Project: `.stepcode/prompts/*.md` (only after the project is trusted)
-- Packages: `prompts/` directories or `pi.prompts` entries in `package.json`
+- Packages: `prompts/` directories, or `pi.prompts` entries in `package.json` (the manifest key keeps its legacy name; see [Packages](packages.md))
- Settings: `prompts` array with files or directories
- CLI: `--prompt-template ` (repeatable)
diff --git a/packages/coding-agent/docs/rpc.md b/packages/coding-agent/docs/rpc.md
index d65376b0..a1e69f56 100644
--- a/packages/coding-agent/docs/rpc.md
+++ b/packages/coding-agent/docs/rpc.md
@@ -64,7 +64,7 @@ With images:
If the agent is streaming and no `streamingBehavior` is specified, the command returns an error.
-**Extension commands**: If the message is an extension command (e.g., `/mycommand`), it executes immediately even during streaming. Extension commands manage their own LLM interaction via `pi.sendMessage()`.
+**Extension commands**: If the message is an extension command (e.g., `/mycommand`), it executes immediately even during streaming. Extension commands manage their own LLM interaction via `step.sendMessage()`.
**Input expansion**: Skill commands (`/skill:name`) and prompt templates (`/template`) are expanded before sending/queueing.
@@ -233,7 +233,7 @@ Response:
}
```
-Messages are `AgentMessage` objects (see [Message Types](#message-types)).
+Messages are `AgentMessage` objects.
### Model
@@ -841,7 +841,7 @@ Each command has:
- `name`: Command name (invoke with `/name`)
- `description`: Human-readable description (optional for extension commands)
- `source`: What kind of command:
- - `"extension"`: Registered via `pi.registerCommand()` in an extension
+ - `"extension"`: Registered via `step.registerCommand()` in an extension
- `"prompt"`: Loaded from a prompt template `.md` file
- `"skill"`: Loaded from a skill directory (name is prefixed with `skill:`)
- `location`: Where it was loaded from (optional, not present for extensions):
diff --git a/packages/coding-agent/docs/sdk.md b/packages/coding-agent/docs/sdk.md
index 402e3be3..ea3e509e 100644
--- a/packages/coding-agent/docs/sdk.md
+++ b/packages/coding-agent/docs/sdk.md
@@ -215,7 +215,7 @@ await session.prompt("After you're done, also check X", { streamingBehavior: "fo
```
**Behavior:**
-- **Extension commands** (e.g., `/mycommand`): Execute immediately, even during streaming. They manage their own LLM interaction via `pi.sendMessage()`.
+- **Extension commands** (e.g., `/mycommand`): Execute immediately, even during streaming. They manage their own LLM interaction via `step.sendMessage()`.
- **File-based prompt templates** (from `.md` files): Expanded to their content before sending or queueing.
- **During streaming without `streamingBehavior`**: Throws an error. Use `steer()` or `followUp()` directly, or specify the option.
- **`preflightResult(true)`**: Means the prompt was accepted, queued, or handled immediately.
@@ -471,7 +471,7 @@ const customRuntime = await ModelRuntime.create({
modelsPath: "/my/app/models.json",
});
-// Or inject any pi-ai CredentialStore
+// Or inject any `@step-harness/providers` CredentialStore
const credentials = new InMemoryCredentialStore();
const inMemoryRuntime = await ModelRuntime.create({ credentials });
@@ -604,9 +604,9 @@ const { session } = await createAgentSession({
});
```
-Use `defineTool()` for standalone definitions and arrays like `customTools: [myTool]`. Inline `pi.registerTool({ ... })` already infers parameter types correctly.
+Use `defineTool()` for standalone definitions and arrays like `customTools: [myTool]`. Inline `step.registerTool({ ... })` already infers parameter types correctly.
-Custom tools passed via `customTools` are combined with extension-registered tools. Extensions loaded by the ResourceLoader can also register tools via `pi.registerTool()`.
+Custom tools passed via `customTools` are combined with extension-registered tools. Extensions loaded by the ResourceLoader can also register tools via `step.registerTool()`.
If you pass `tools`, include each custom or extension tool name you want enabled, for example `tools: ["read", "bash", "my_tool"]`.
@@ -623,7 +623,7 @@ const loader = new DefaultResourceLoader({
additionalExtensionPaths: ["/path/to/my-extension.ts"],
extensionFactories: [
(step) => {
- pi.on("agent_start", () => {
+ step.on("agent_start", () => {
console.log("[Inline Extension] Agent starting");
});
},
@@ -644,7 +644,7 @@ import type { InlineExtension } from "@step-harness/coding-agent";
const myProvider: InlineExtension = {
name: "my-provider",
factory: (step) => {
- pi.on("agent_start", () => {
+ step.on("agent_start", () => {
console.log("[my-provider] Agent starting");
});
},
@@ -657,7 +657,7 @@ const loader = new DefaultResourceLoader({
This displays as `` instead of ``. Bare factory functions are still accepted for backward compatibility.
-**Event Bus:** Extensions can communicate via `pi.events`. Pass a shared `eventBus` to `DefaultResourceLoader` if you need to emit or listen from outside:
+**Event Bus:** Extensions can communicate via `step.events`. Pass a shared `eventBus` to `DefaultResourceLoader` if you need to emit or listen from outside:
```typescript
import { createEventBus, DefaultResourceLoader } from "@step-harness/coding-agent";
@@ -1176,7 +1176,7 @@ createAgentSessionRuntime
AgentSessionRuntime
// Auth and Models
-ModelRuntime // implements pi-ai Models and owns credential storage
+ModelRuntime // implements the providers-package Models and owns credential storage
ModelRegistry // synchronous extension compatibility facade
CredentialSynchronizationError
resolveCliModel
diff --git a/packages/coding-agent/docs/session-format.md b/packages/coding-agent/docs/session-format.md
index 8a97a1d9..e2ac2563 100644
--- a/packages/coding-agent/docs/session-format.md
+++ b/packages/coding-agent/docs/session-format.md
@@ -69,7 +69,7 @@ interface ToolCall {
}
```
-### Base Message Types (from pi-ai)
+### Base Message Types (from `@step-harness/providers`)
```typescript
interface UserMessage {
@@ -117,11 +117,11 @@ interface Usage {
}
```
-The exported pi-ai `StopReason` type also includes `"pending"`, but that value is reserved for partial messages in streaming events. Terminal `done`/`error` messages replace it with a completion reason before step persists the assistant message, so `"pending"` should never appear in session JSONL.
+The exported `StopReason` type also includes `"pending"`, but that value is reserved for partial messages in streaming events. Terminal `done`/`error` messages replace it with a completion reason before step persists the assistant message, so `"pending"` should never appear in session JSONL.
Session totals and the cost breakdown use the shared [`usage-totals.ts`](../src/core/usage-totals.ts) ledger. It includes assistant, tool, compaction, branch-summary and incremental `step-auto-clm-usage` records; cumulative maintenance summaries are excluded. Token increments must contain nonnegative safe integers and must not overflow the accumulated total. A missing/invalid cost does not hide valid tokens. `SessionStats.tokens.input` excludes caches, which are reported separately; the [Account summary](account-usage.md) combines them into complete input.
-### Extended Message Types (from pi-coding-agent)
+### Extended Message Types (from the coding agent)
```typescript
interface BashExecutionMessage {
@@ -246,7 +246,7 @@ Optional fields:
- `usage`: LLM usage from generating the summary; included in session token and cost totals
- `retainedTail`: Materialized `AgentMessage[]` kept after compaction. This is optional only for backward compatibility with older sessions. Newer harness-generated compactions include it so we can rebuild context from this checkpoint without walking older entries before the compaction entry.
- `details`: Implementation-specific data (e.g., `{ readFiles: string[], modifiedFiles: string[] }` for default, or custom data for extensions)
-- `fromHook`: `true` if generated by an extension, `false`/`undefined` if pi-generated (legacy field name)
+- `fromHook`: `true` if generated by an extension, `false`/`undefined` if generated by the agent itself (legacy field name)
- `firstKeptEntryId`: for compatibility with old entry format.
### BranchSummaryEntry
@@ -260,7 +260,7 @@ Created when switching branches via `/tree` with an LLM generated summary of the
Optional fields:
- `usage`: LLM usage from generating the summary; included in session token and cost totals
- `details`: File tracking data (`{ readFiles: string[], modifiedFiles: string[] }`) for default, or custom data for extensions
-- `fromHook`: `true` if generated by an extension, `false`/`undefined` if pi-generated (legacy field name)
+- `fromHook`: `true` if generated by an extension, `false`/`undefined` if generated by the agent itself (legacy field name)
### CustomEntry
@@ -270,7 +270,7 @@ Extension state persistence. Does NOT participate in LLM context.
{"type":"custom","id":"h8i9j0k1","parentId":"g7h8i9j0","timestamp":"2024-12-03T14:20:00.000Z","customType":"my-extension","data":{"count":42}}
```
-Use `customType` to identify your extension's entries on reload. Interactive mode can render custom entries via `pi.registerEntryRenderer(customType, renderer)`, but they still do not participate in LLM context.
+Use `customType` to identify your extension's entries on reload. Interactive mode can render custom entries via `step.registerEntryRenderer(customType, renderer)`, but they still do not participate in LLM context.
### CustomMessageEntry
@@ -297,7 +297,7 @@ Set `label` to `undefined` to clear a label.
### SessionInfoEntry
-Session metadata (e.g., user-defined display name). Set via `/name`, `--name` / `-n`, or `pi.setSessionName()` in extensions.
+Session metadata (e.g., user-defined display name). Set via `/name`, `--name` / `-n`, or `step.setSessionName()` in extensions.
```json
{"type":"session_info","id":"k1l2m3n4","parentId":"j0k1l2m3","timestamp":"2024-12-03T14:35:00.000Z","name":"Refactor auth module"}
diff --git a/packages/coding-agent/docs/settings.md b/packages/coding-agent/docs/settings.md
index 33c40c74..985e1668 100644
--- a/packages/coding-agent/docs/settings.md
+++ b/packages/coding-agent/docs/settings.md
@@ -313,7 +313,7 @@ String form loads all resources from a package:
```json
{
- "packages": ["pi-skills", "@org/my-extension"]
+ "packages": ["step-skills", "@org/my-extension"]
}
```
@@ -323,7 +323,7 @@ Object form filters which resources to load:
{
"packages": [
{
- "source": "pi-skills",
+ "source": "step-skills",
"skills": ["brave-search", "transcribe"],
"extensions": []
}
@@ -357,7 +357,7 @@ See [packages.md](packages.md) for package management details.
"warnings": {
"anthropicExtraUsage": true
},
- "packages": ["pi-skills"]
+ "packages": ["step-skills"]
}
```
diff --git a/packages/coding-agent/docs/skills.md b/packages/coding-agent/docs/skills.md
index 4188ad30..acb62f65 100644
--- a/packages/coding-agent/docs/skills.md
+++ b/packages/coding-agent/docs/skills.md
@@ -31,7 +31,7 @@ Step loads skills from:
- Project (only after the project is trusted):
- `.stepcode/skills/`
- `.agents/skills/` and `.claude/skills/` in `cwd` and ancestor directories (up to git repo root, or filesystem root when not in a repo)
-- Packages: `skills/` directories or `pi.skills` entries in `package.json`
+- Packages: `skills/` directories, or `pi.skills` entries in `package.json` (the manifest key keeps its legacy name; see [Packages](packages.md))
- Settings: `skills` array with files or directories
- CLI: `--skill ` (repeatable, additive even with `--no-skills`)
@@ -236,4 +236,4 @@ cd /path/to/brave-search && npm install
## Skill Repositories
- [Anthropic Skills](https://github.com/anthropics/skills) - Document processing (docx, pdf, pptx, xlsx), web development
-- [Step Skills](https://github.com/badlogic/pi-skills) - Web search, browser automation, Google APIs, transcription
+- [Skills collection](https://github.com/badlogic/pi-skills) - Web search, browser automation, Google APIs, transcription
diff --git a/packages/coding-agent/docs/step-integration.md b/packages/coding-agent/docs/step-integration.md
index 00271dbd..527b8702 100644
--- a/packages/coding-agent/docs/step-integration.md
+++ b/packages/coding-agent/docs/step-integration.md
@@ -1,48 +1,48 @@
# Step Integration
-The Step entrypoint is intentionally an adapter around pi's coding-agent
-runtime. The Step entrypoint sets product defaults and calls `main()` with the
-Step extension; it does not create a second event loop or a second TUI.
+The Step entrypoint is intentionally a thin adapter around the shared
+coding-agent runtime. It sets product defaults and calls `main()` with the Step
+extension; it does not create a second event loop or a second TUI.
## Ownership
- `InteractiveMode`, selectors, overlays, rendering, and input decoding stay in
- pi. Step selects a presentation-only `StepEditor` subclass that delegates
- `handleInput()` to pi's `CustomEditor`, plus Step welcome/footer components;
- these wrappers only format rendered lines and never own input or session
- state.
+ the shared runtime. Step selects a presentation-only `StepEditor` subclass
+ that delegates `handleInput()` to the native `CustomEditor`, plus Step
+ welcome/footer components; these wrappers only format rendered lines and
+ never own input or session state.
- `AgentSessionRuntime` remains the session and agent-loop authority.
- `extensions/step-provider` supplies the Step model catalog and browser OAuth
- flow through pi's provider API.
+ flow through the shared provider API.
- `stepcode.ts` is a forwarding facade for hosts that need a Step-named
boundary; it delegates prompt, queue, model, event, and disposal operations
- directly to pi. Event subscriptions follow the active session when pi handles
- `/new`, `/resume`, or `/fork`.
+ directly to the runtime. Event subscriptions follow the active session when
+ the runtime handles `/new`, `/resume`, or `/fork`.
- `step/stdio.ts` contains the length-prefixed frame codec and the small
transport-neutral event bridge.
- `step/feedback/` owns Step's user-initiated feedback contract: input
validation and redaction, collector endpoint selection, optional diagnostics
- and session-bundle construction, ordered delivery, and pending retries. Pi
- supplies the active `SessionManager` and extension UI; feedback does not own
- session state or participate in the agent loop.
+ and session-bundle construction, ordered delivery, and pending retries. The
+ runtime supplies the active `SessionManager` and extension UI; feedback does
+ not own session state or participate in the agent loop.
- `step/stdio-host.ts` is the optional `step --sdk-stdio` adapter. It owns only
framing, request validation, reverse UI requests, and wire projections. Query
declared SDK tools and lifecycle hooks are installed as temporary wrappers
- around Pi's public `Agent` callbacks; their actual execution, validation,
- transcript updates, and abort semantics still run in Pi's agent loop. The
- host rebinds after session replacement and serializes writes so stdout stays
- a valid byte stream. Unsupported query options are reported in the init
+ around the runtime's public `Agent` callbacks; their actual execution,
+ validation, transcript updates, and abort semantics still run in the shared
+ agent loop. The host rebinds after session replacement and serializes writes
+ so stdout stays a valid byte stream. Unsupported query options are reported in the init
message instead of being silently ignored.
The stdio handshake advertises `streaming-input`, `sdk-tools`,
`permission-callback`, `hooks`, and `sessions`. Partial text events are
emitted only when `includePartialMessages` is enabled; final assistant and
- tool-result events always come from the Pi session event stream.
+ tool-result events always come from the runtime session event stream.
## Product defaults
The `step` entrypoint uses `STEP_CODING_AGENT_DIR` and
-`STEP_CODING_AGENT_SESSION_DIR` when present. Without a session override, Pi's
+`STEP_CODING_AGENT_SESSION_DIR` when present. Without a session override, the
native layout is `~/.stepcode/agent/sessions/`. Project-local
resources use `.stepcode/`. When no explicit theme has been saved, the
built-in `step-blue` palette is used for either terminal appearance, without
@@ -51,18 +51,18 @@ automatic violet light/dark switching. Non-interactive
`step --export` uses the same precedence as startup: explicit `--use-theme`,
saved settings, then the Step product default.
Set `STEP_PROVIDER`, `STEP_MODEL`, `STEP_API_KEY`, or the provider endpoint
-variables to override the defaults. The Step launcher also disables pi release
-and install checks so the product does not contact upstream services.
+variables to override the defaults. The Step launcher also disables upstream
+release and install checks so the product does not contact those services.
The Step `Working...` row measures elapsed time from `agent_start` across model
and tool turns. While a response streams, it estimates output tokens from the
normalized thinking, text, and tool-call deltas at four characters per token;
positive final `usage.output` replaces that response's estimate. Anthropic and
OpenAI-compatible providers share this normalized event path. The `thinking`
-label follows thinking events and clears when text or tool output begins. Pi's
+label follows thinking events and clears when text or tool output begins. The
native presentation does not run this tracker or display these metrics.
-The built-in Step provider uses pi's `anthropic-messages` adapter. Pi appends
+The built-in Step provider uses the `anthropic-messages` adapter, which appends
`/v1/messages` to the configured base URL, so the canonical Step base is
`https://api.stepfun.com/step_plan`. For compatibility, `STEP_BASE_URL` also
accepts the older `.../v1` and `.../v1/messages` spellings; the provider
@@ -121,32 +121,32 @@ reusing the value would authenticate a gateway key against
`api.stepfun.com` and fail.
Session storage is also selected through the Step wrapper. The wrapper keeps
-Pi's `SessionManager` class, JSONL format, and tree operations unchanged, but
-binds its `create`, `open`, `continueRecent`, `forkFrom`, `list`, and `listAll`
+the native `SessionManager` class, JSONL format, and tree operations unchanged,
+but binds its `create`, `open`, `continueRecent`, `forkFrom`, `list`, and `listAll`
operations to the active Step agent root. Runtime replacement flows (`/new`,
`/resume`, `/fork`, and `/import`) use that same bound factory, so a later
-operation cannot fall back to Pi's default `~/.stepcode/agent/sessions` directory.
+operation cannot fall back to the default `~/.stepcode/agent/sessions`
+directory.
Managed `fd` and `rg` binaries follow the same explicit `agentDir` boundary and
are installed under `/bin`.
-These presentation/runtime switches are passed to pi's composition root as
+These presentation/runtime switches are passed to the composition root as
explicit options: `defaultTheme` selects the single blue Step palette and
`disableBackgroundServices` suppresses optional catalog, update, and install
telemetry work. It is deliberately distinct from interactive `offline` mode,
so disabling those background services does not prevent the first-run OAuth
flow. `tuiStyle: "step"` selects the Step presentation variant for the native
interactive mode. The shared `main.ts`, InteractiveMode, and theme controller
-do not inspect Step-specific environment variables; the ordinary `pi` entrypoint
-does not pass these options and keeps its upstream defaults.
+do not inspect Step-specific environment variables; callers that pass no such
+options keep the default behavior.
Step also injects a settings decorator through `settingsManagerFactory`. The
-decorator delegates Pi's complete `SettingsManager` API, including global and
+decorator delegates the complete `SettingsManager` API, including global and
project merging, trust, reload, and flush. Product-only permission policy is
kept in the unified `config.toml` files at `~/.stepcode/config.toml` and
`/.stepcode/config.toml`, with the same global-then-project precedence and
file locking. This keeps fields such as `permissionPreset` and `autoResume` out
-of Pi's native `settings.json` schema while leaving ordinary Pi startup
-unchanged.
+of the native `settings.json` schema while leaving default startup unchanged.
Top-level `step feedback` and interactive `/feedback` use a Step-owned gate,
configured by `feedbackEnabled` in the Step config and the legacy feedback
@@ -174,14 +174,14 @@ cannot succeed and the report or archive must be changed before resubmission.
Embedded Step hosts can use `createStepAgentSession()` or
`createStepAgentSessionServices()` from the Step surface. These are thin
-wrappers around Pi's corresponding factories: they resolve the Step global
-agent directory and decorate an existing Pi manager when one is supplied.
-They do not alter Pi's session file format or agent loop. Callers that supply a
-custom or in-memory Pi manager should pass explicit `stepSettingsPaths` when
+wrappers around the corresponding factories: they resolve the Step global
+agent directory and decorate an existing manager when one is supplied.
+They do not alter the session file format or agent loop. Callers that supply a
+custom or in-memory manager should pass explicit `stepSettingsPaths` when
they need to control where the Step sidecar is stored.
-`/init` submits a Codex-style repository-instructions prompt through pi's
-normal user-message path. The model inspects the project and uses pi's native
+`/init` submits a Codex-style repository-instructions prompt through the
+normal user-message path. The model inspects the project and uses the native
write and approval flow; the prompt explicitly preserves an existing
`AGENTS.md` instead of replacing it.
@@ -200,7 +200,7 @@ over the 120-second default. For a server, watcher, or longer job, use
`run_in_background: true`, inspect the returned log path, and stop the process
with the returned command when it is no longer needed. Background execution
keeps its session-bound lifecycle and ignores the foreground timeout fields.
-Pi's separate native `bash` tool retains its optional timeout contract.
+The separate native `bash` tool retains its optional timeout contract.
On Linux, timeout and abort cleanup snapshots the command's current descendants
before killing its process group. Descendants in separate process groups are
@@ -428,22 +428,22 @@ separation, not model-specific restrictions on which task tools are available.
## Compatibility matrix
The following is the current audit against the previous StepCode. “Native”
-means the behavior is supplied by Pi and is intentionally not duplicated in
+means the behavior is supplied by the shared runtime and is intentionally not duplicated in
the Step layer; “adapter” means a small wrapper in `src/step/` or the Step
extension. Entries marked “separate product” require a gateway or external
service and are not silently faked by this fork.
| Previous Step surface | Current ownership | State |
| --- | --- | --- |
-| Model/provider selection, compaction, resume, fork, new session | Pi native commands and `SessionManager` | aligned |
+| Model/provider selection, compaction, resume, fork, new session | Native commands and `SessionManager` | aligned |
| StepCode OAuth login/logout and legacy credential normalization | `features/step-provider`, `step/auth.ts`, `step/models-endpoint-repair.ts` | aligned; endpoint repair is tested |
-| Global/project settings and session paths | Pi managers wrapped by `step/settings-manager.ts` and `step/session.ts` | aligned; writes use `.stepcode` |
-| Step permissions, `/permissions`, `/init` | Step extension/facades over Pi selectors and hooks | aligned; `/effort` replaces Pi's native `/thinking`, while `/permission` and `/mode` remain absent |
+| Global/project settings and session paths | Native managers wrapped by `step/settings-manager.ts` and `step/session.ts` | aligned; writes use `.stepcode` |
+| Step permissions, `/permissions`, `/init` | Step extension/facades over native selectors and hooks | aligned; `/effort` replaces the native `/thinking`, while `/permission` and `/mode` remain absent |
| Built-in `search_web` | Step tool profile backed by `stepsearch.web_search` over Streamable HTTP MCP | aligned; uses search-specific environment overrides and the Step login credential fallback |
-| Top-level `step feedback` and TUI `/feedback` | `step/feedback/`, `stepcode.ts`, and the Step extension over Pi's current session/UI | aligned; the body and optional session bundle use separate Step collector requests |
+| Top-level `step feedback` and TUI `/feedback` | `step/feedback/`, `stepcode.ts`, and the Step extension over the current session/UI | aligned; the body and optional session bundle use separate Step collector requests |
| `/status`, self-memory/skills governance, `/refresh`, `/rewind`, `/copy` full-transcript semantics | Previous gateway/TUI product layer | not yet ported; no misleading alias is registered |
| `/multi-agent`, `/connect`, `/trace`, gateway/cron | Previous gateway and external channel services | separate product; requires an explicit Step service implementation |
-| Legacy top-level `models`, `serve`, `goal`, and related gateway commands | Previous command dispatcher | separate product; Pi package commands remain unchanged |
+| Legacy top-level `models`, `serve`, `goal`, and related gateway commands | Previous command dispatcher | separate product; the package commands remain unchanged |
This distinction is deliberate: a missing external service is reported as a
gap instead of being represented by a command that appears to work but changes
@@ -451,18 +451,18 @@ session or approval semantics.
## Distribution
-The npm package exposes both `pi` and `step` from bundled entrypoints. The
-standalone archives likewise contain both executables (`pi`/`step`, or their
-Windows `.exe` variants); the Step executable uses the same Bun runtime setup as
-Pi before loading the Step product adapter.
+The npm package exposes a single `step` bin from the bundled entrypoint. The
+standalone archives contain the matching `step` executable (`step.exe` on
+Windows); it uses the shared Bun runtime setup before loading the Step product
+adapter.
## Non-goals
This layer does not reimplement editor key handling, UTF-8 buffering, render
throttling, selectors, or agent scheduling. Step's permission extension only
-defines the product tiers and dangerous-command policy; it invokes Pi's
+defines the product tiers and dangerous-command policy; it invokes the
native `tool_call` confirmation and `ui.select` flows. Auto-resume likewise
-toggles Pi's native retry setting and adds only a bounded continuation after a
+toggles the native retry setting and adds only a bounded continuation after a
settled retryable failure. Changes to the underlying interaction or scheduling
-behavior belong in Pi's shared packages and should be consumed here through
+behavior belong in the shared packages and should be consumed here through
their public APIs.
diff --git a/packages/coding-agent/docs/themes.md b/packages/coding-agent/docs/themes.md
index 8aca0601..5416bae7 100644
--- a/packages/coding-agent/docs/themes.md
+++ b/packages/coding-agent/docs/themes.md
@@ -21,7 +21,7 @@ Step loads themes from:
- Built-in: `dark`, `light`, `sage`, `step-blue`, `step-violet`, `step-violet-light`
- Global: `~/.stepcode/agent/themes/*.json`
- Project: `.stepcode/themes/*.json` (only after the project is trusted)
-- Packages: `themes/` directories or `pi.themes` entries in `package.json`
+- Packages: `themes/` directories, or `pi.themes` entries in `package.json` (the manifest key keeps its legacy name; see [Packages](packages.md))
- Settings: `themes` array with files or directories
- CLI: `--theme ` (repeatable)
diff --git a/packages/coding-agent/docs/tui.md b/packages/coding-agent/docs/tui.md
index d0d85e8e..6a75b158 100644
--- a/packages/coding-agent/docs/tui.md
+++ b/packages/coding-agent/docs/tui.md
@@ -1,4 +1,4 @@
-> pi can create TUI components. Ask it to build one for your use case.
+> step can create TUI components. Ask it to build one for your use case.
# TUI Components
@@ -89,7 +89,7 @@ Without this propagation, typing with an IME (Chinese, Japanese, Korean, etc.) w
**In extensions** via `ctx.ui.custom()`:
```typescript
-pi.on("session_start", async (_event, ctx) => {
+step.on("session_start", async (_event, ctx) => {
const result = await ctx.ui.custom((tui, theme, keybindings, done) =>
new MyComponent({
theme,
@@ -385,7 +385,7 @@ class MySelector {
Usage in an extension:
```typescript
-pi.registerCommand("pick", {
+step.registerCommand("pick", {
description: "Pick an item",
handler: async (_args, ctx) => {
const items = ["Option A", "Option B", "Option C"];
@@ -614,7 +614,7 @@ import type { ExtensionAPI } from "@step-harness/coding-agent";
import { DynamicBorder } from "@step-harness/coding-agent";
import { Container, type SelectItem, SelectList, Text } from "@step-harness/pi-tui";
-pi.registerCommand("pick", {
+step.registerCommand("pick", {
handler: async (_args, ctx) => {
const items: SelectItem[] = [
{ value: "opt1", label: "Option 1", description: "First option" },
@@ -672,7 +672,7 @@ For operations that take time and should be cancellable. `BorderedLoader` shows
```typescript
import { BorderedLoader } from "@step-harness/coding-agent";
-pi.registerCommand("fetch", {
+step.registerCommand("fetch", {
handler: async (_args, ctx) => {
const result = await ctx.ui.custom((tui, theme, _kb, done) => {
const loader = new BorderedLoader(tui, theme, "Fetching data...");
@@ -705,7 +705,7 @@ For toggling multiple settings. Use `SettingsList` from `@step-harness/pi-tui` w
import { getSettingsListTheme } from "@step-harness/coding-agent";
import { Container, type SettingItem, SettingsList, Text } from "@step-harness/pi-tui";
-pi.registerCommand("settings", {
+step.registerCommand("settings", {
handler: async (_args, ctx) => {
const items: SettingItem[] = [
{ id: "verbose", label: "Verbose mode", currentValue: "off", values: ["on", "off"] },
@@ -757,7 +757,7 @@ ctx.ui.setStatus("my-ext", undefined);
### Pattern 4b: Working Indicator Customization
-Customize the inline working indicator shown while pi is streaming a response.
+Customize the inline working indicator shown while step is streaming a response.
```typescript
// Static indicator
@@ -777,7 +777,7 @@ ctx.ui.setWorkingIndicator({
// Hide the indicator entirely
ctx.ui.setWorkingIndicator({ frames: [] });
-// Restore pi's default spinner
+// Restore the default spinner
ctx.ui.setWorkingIndicator();
```
@@ -894,8 +894,8 @@ class VimEditor extends CustomEditor {
}
}
-export default function (pi: ExtensionAPI) {
- pi.on("session_start", (_event, ctx) => {
+export default function (step: ExtensionAPI) {
+ step.on("session_start", (_event, ctx) => {
// Factory receives the TUI, theme, and keybindings from the app
ctx.ui.setEditorComponent((tui, theme, keybindings) =>
new VimEditor(tui, theme, keybindings)
diff --git a/packages/coding-agent/examples/README.md b/packages/coding-agent/examples/README.md
index 87505184..c3b7b3bf 100644
--- a/packages/coding-agent/examples/README.md
+++ b/packages/coding-agent/examples/README.md
@@ -1,6 +1,6 @@
# Examples
-Example code for pi-coding-agent SDK and extensions.
+Example code for the coding agent SDK and extensions.
## Directories
diff --git a/packages/coding-agent/examples/extensions/README.md b/packages/coding-agent/examples/extensions/README.md
index 42f952a9..51b20a52 100644
--- a/packages/coding-agent/examples/extensions/README.md
+++ b/packages/coding-agent/examples/extensions/README.md
@@ -1,15 +1,15 @@
# Extension Examples
-Example extensions for pi-coding-agent.
+Example extensions for the step coding agent.
## Usage
```bash
# Load an extension with --extension flag
-pi --extension examples/extensions/permission-gate.ts
+step --extension examples/extensions/permission-gate.ts
# Or copy to extensions directory for auto-discovery
-cp permission-gate.ts ~/.pi/agent/extensions/
+cp permission-gate.ts ~/.stepcode/agent/extensions/
```
## Examples
@@ -61,7 +61,7 @@ cp permission-gate.ts ~/.pi/agent/extensions/
| `model-status.ts` | Shows model changes in status bar via `model_select` hook |
| `snake.ts` | Snake game with custom UI, keyboard handling, and session persistence |
| `tic-tac-toe.ts` | Tic-tac-toe vs the agent with `executionMode: "sequential"` tools to prevent race conditions on shared cursor state |
-| `send-user-message.ts` | Demonstrates `pi.sendUserMessage()` for sending user messages from extensions |
+| `send-user-message.ts` | Demonstrates `step.sendUserMessage()` for sending user messages from extensions |
| `timed-confirm.ts` | Demonstrates AbortSignal for auto-dismissing `ctx.ui.confirm()` and `ctx.ui.select()` dialogs |
| `rpc-demo.ts` | Exercises all RPC-supported extension UI methods; pair with [`examples/rpc-extension-ui.ts`](../rpc-extension-ui.ts) |
| `modal-editor.ts` | Custom vim-like modal editor via `ctx.ui.setEditorComponent()` |
@@ -99,7 +99,7 @@ cp permission-gate.ts ~/.pi/agent/extensions/
| Extension | Description |
|-----------|-------------|
-| `mac-system-theme.ts` | Syncs pi theme with macOS dark/light mode |
+| `mac-system-theme.ts` | Syncs step theme with macOS dark/light mode |
### Resources
@@ -113,7 +113,7 @@ cp permission-gate.ts ~/.pi/agent/extensions/
|-----------|-------------|
| `message-renderer.ts` | Custom message rendering with colors and expandable details via `registerMessageRenderer` |
| `entry-renderer.ts` | TUI-only session entry rendering via `appendEntry` and `registerEntryRenderer` |
-| `event-bus.ts` | Inter-extension communication via `pi.events` |
+| `event-bus.ts` | Inter-extension communication via `step.events` |
### Session Metadata
@@ -137,9 +137,9 @@ See [docs/extensions.md](../../docs/extensions.md) for full documentation.
import type { ExtensionAPI } from "@step-harness/coding-agent";
import { Type } from "typebox";
-export default function (pi: ExtensionAPI) {
+export default function (step: ExtensionAPI) {
// Subscribe to lifecycle events
- pi.on("tool_call", async (event, ctx) => {
+ step.on("tool_call", async (event, ctx) => {
if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
const ok = await ctx.ui.confirm("Dangerous!", "Allow rm -rf?");
if (!ok) return { block: true, reason: "Blocked by user" };
@@ -147,7 +147,7 @@ export default function (pi: ExtensionAPI) {
});
// Register custom tools
- pi.registerTool({
+ step.registerTool({
name: "greet",
label: "Greeting",
description: "Generate a greeting",
@@ -163,7 +163,7 @@ export default function (pi: ExtensionAPI) {
});
// Register commands
- pi.registerCommand("hello", {
+ step.registerCommand("hello", {
description: "Say hello",
handler: async (args, ctx) => {
ctx.ui.notify("Hello!", "info");
@@ -194,7 +194,7 @@ return {
};
// Reconstruct on session events
-pi.on("session_start", async (_event, ctx) => {
+step.on("session_start", async (_event, ctx) => {
for (const entry of ctx.sessionManager.getBranch()) {
if (entry.type === "message" && entry.message.toolName === "my_tool") {
const details = entry.message.details;
diff --git a/packages/coding-agent/examples/extensions/doom-overlay/README.md b/packages/coding-agent/examples/extensions/doom-overlay/README.md
index 420bd80b..c8d21bbd 100644
--- a/packages/coding-agent/examples/extensions/doom-overlay/README.md
+++ b/packages/coding-agent/examples/extensions/doom-overlay/README.md
@@ -1,11 +1,11 @@
# DOOM Overlay Demo
-Play DOOM as an overlay in pi. Demonstrates that the overlay system can handle real-time game rendering at 35 FPS.
+Play DOOM as an overlay in step. Demonstrates that the overlay system can handle real-time game rendering at 35 FPS.
## Usage
```bash
-pi --extension ./examples/extensions/doom-overlay
+step --extension ./examples/extensions/doom-overlay
```
Then run:
@@ -43,4 +43,4 @@ Height is calculated from width to maintain DOOM's 3.2:1 aspect ratio (accountin
- [id Software](https://github.com/id-Software/DOOM) for the original DOOM
- [doomgeneric](https://github.com/ozkl/doomgeneric) for the portable DOOM implementation
-- [pi-doom](https://github.com/badlogic/pi-doom) for the original pi integration
+- [pi-doom](https://github.com/badlogic/pi-doom) for the original upstream integration
diff --git a/packages/coding-agent/examples/extensions/subagent/README.md b/packages/coding-agent/examples/extensions/subagent/README.md
index 351e78d2..1ab028ed 100644
--- a/packages/coding-agent/examples/extensions/subagent/README.md
+++ b/packages/coding-agent/examples/extensions/subagent/README.md
@@ -4,7 +4,7 @@ Delegate tasks to specialized subagents with isolated context windows.
## Features
-- **Isolated context**: Each subagent runs in a separate `pi` process
+- **Isolated context**: Each subagent runs in a separate `step` process
- **Streaming output**: See tool calls and progress as they happen
- **Parallel streaming**: All parallel tasks stream updates simultaneously
- **Markdown rendering**: Final output rendered with proper formatting (expanded view)
@@ -35,30 +35,30 @@ From the repository root, symlink the files:
```bash
# Symlink the extension (must be in a subdirectory with index.ts)
-mkdir -p ~/.pi/agent/extensions/subagent
-ln -sf "$(pwd)/packages/coding-agent/examples/extensions/subagent/index.ts" ~/.pi/agent/extensions/subagent/index.ts
-ln -sf "$(pwd)/packages/coding-agent/examples/extensions/subagent/agents.ts" ~/.pi/agent/extensions/subagent/agents.ts
+mkdir -p ~/.stepcode/agent/extensions/subagent
+ln -sf "$(pwd)/packages/coding-agent/examples/extensions/subagent/index.ts" ~/.stepcode/agent/extensions/subagent/index.ts
+ln -sf "$(pwd)/packages/coding-agent/examples/extensions/subagent/agents.ts" ~/.stepcode/agent/extensions/subagent/agents.ts
# Symlink agents
-mkdir -p ~/.pi/agent/agents
+mkdir -p ~/.stepcode/agent/agents
for f in packages/coding-agent/examples/extensions/subagent/agents/*.md; do
- ln -sf "$(pwd)/$f" ~/.pi/agent/agents/$(basename "$f")
+ ln -sf "$(pwd)/$f" ~/.stepcode/agent/agents/$(basename "$f")
done
# Symlink workflow prompts
-mkdir -p ~/.pi/agent/prompts
+mkdir -p ~/.stepcode/agent/prompts
for f in packages/coding-agent/examples/extensions/subagent/prompts/*.md; do
- ln -sf "$(pwd)/$f" ~/.pi/agent/prompts/$(basename "$f")
+ ln -sf "$(pwd)/$f" ~/.stepcode/agent/prompts/$(basename "$f")
done
```
## Security Model
-This tool executes a separate `pi` subprocess with a delegated system prompt and tool/model configuration.
+This tool executes a separate `step` subprocess with a delegated system prompt and tool/model configuration.
-**Project-local agents** (`.pi/agents/*.md`) are repo-controlled prompts that can instruct the model to read files, run bash commands, etc.
+**Project-local agents** (`.stepcode/agents/*.md`) are repo-controlled prompts that can instruct the model to read files, run bash commands, etc.
-**Default behavior:** Only loads **user-level agents** from `~/.pi/agent/agents`.
+**Default behavior:** Only loads **user-level agents** from `~/.stepcode/agent/agents`.
To enable project-local agents, pass `agentScope: "both"` (or `"project"`). Only do this for repositories you trust.
@@ -140,8 +140,8 @@ System prompt for the agent goes here.
When `model` is omitted, the subagent inherits the dispatching session's active model and thinking level.
**Locations:**
-- `~/.pi/agent/agents/*.md` - User-level (always loaded)
-- `.pi/agents/*.md` - Project-level (only with `agentScope: "project"` or `"both"`)
+- `~/.stepcode/agent/agents/*.md` - User-level (always loaded)
+- `.stepcode/agents/*.md` - Project-level (only with `agentScope: "project"` or `"both"`)
Project agents override user agents with the same name when `agentScope: "both"`.
diff --git a/packages/coding-agent/examples/sdk/README.md b/packages/coding-agent/examples/sdk/README.md
index 9899b49f..efaa8a74 100644
--- a/packages/coding-agent/examples/sdk/README.md
+++ b/packages/coding-agent/examples/sdk/README.md
@@ -1,6 +1,6 @@
# SDK Examples
-Programmatic usage of pi-coding-agent via `createAgentSession()` and `createAgentSessionRuntime()`.
+Programmatic usage of the coding agent via `createAgentSession()` and `createAgentSessionRuntime()`.
The runtime example shows how to build a recreate function that closes over process-global fixed inputs and recreates cwd-bound services and sessions as the active session cwd changes.
@@ -107,7 +107,7 @@ await session.prompt("Hello");
|--------|---------|-------------|
| `modelRuntime` | Runtime using `agentDir/auth.json` and `models.json` | Canonical model and authentication runtime |
| `cwd` | `process.cwd()` | Working directory |
-| `agentDir` | `~/.pi/agent` | Config directory |
+| `agentDir` | `~/.stepcode/agent` | Config directory |
| `model` | From settings/first available | Model to use |
| `thinkingLevel` | From settings/"off" | off, low, medium, high |
| `tools` | `["read", "bash", "edit", "write"]` built-ins | Allowlist tool names across built-in, extension, and custom tools |
diff --git a/packages/providers/README.md b/packages/providers/README.md
index b2a3d5e6..7c6cc0bc 100644
--- a/packages/providers/README.md
+++ b/packages/providers/README.md
@@ -69,7 +69,7 @@ DeepSeek, ZAI, Qwen, Kimi, OpenRouter, Ant Ling, and OpenCode.
Configure providers and models at runtime against any OpenAI- or
Anthropic-compatible endpoint on those protocols:
-- Declare custom providers in `~/.pi/agent/models.json`.
+- Declare custom providers in `~/.stepcode/agent/models.json`.
- Fetch a live catalog from a provider's `/v1/models` endpoint (for example the
Step provider registered by the product).
- Build a provider from parts with `createProvider()` (see [Custom Providers](#custom-providers)).
@@ -359,7 +359,7 @@ Header names are merged case-insensitively. Explicit headers override auth/model
### Credential Store
-Stored credentials (API keys entered interactively, OAuth tokens) live in a `CredentialStore` — one type-tagged credential per provider. pi-ai ships an in-memory default; apps inject persistent storage:
+Stored credentials (API keys entered interactively, OAuth tokens) live in a `CredentialStore` — one type-tagged credential per provider. This package ships an in-memory default; apps inject persistent storage:
```typescript
import { createModels, type CredentialStore } from '@step-harness/providers';
@@ -369,7 +369,7 @@ const models = createModels({ credentials: myFileBackedStore });
The contract is small: `read(providerId)`, `list()` for non-secret `{ providerId, type }` metadata, `modify(providerId, fn)` (the only write path — a serialized read-modify-write), and `delete(providerId)`. Each operation accepts optional cancellation options. Enumeration must not resolve secrets or execute configured key commands. OAuth token refresh runs inside `modify`, so concurrent requests and processes cannot double-refresh a rotated token. A stored credential *owns* its provider: environment variables are only consulted when nothing is stored, and a failed refresh never silently falls back to an env key.
-API-key credentials use the same discriminator as pi's `auth.json` and can carry provider-scoped env/config values:
+API-key credentials use the same discriminator as the agent `auth.json` and can carry provider-scoped env/config values:
```typescript
const credential = {
@@ -644,7 +644,7 @@ All streaming events emitted during assistant message generation:
| `done` | Stream complete | `reason`: Stop reason ("stop", "length", "toolUse"), `message`: Final assistant message |
| `error` | Error occurred | `reason`: Error type ("error" or "aborted"), `error`: AssistantMessage with partial content |
-Streaming events for different content blocks are not guaranteed to be contiguous. Providers may emit deltas for text, thinking, and tool calls in the same upstream chunk, and pi may surface corresponding events interleaved, for example `text_start`, `text_delta`, `toolcall_start`, `text_delta`, `toolcall_delta`. Consumers must use `contentIndex` to associate each delta/end event with its block and must not assume that a block's `*_start`/`*_delta`/`*_end` sequence is uninterrupted by events for other blocks.
+Streaming events for different content blocks are not guaranteed to be contiguous. Providers may emit deltas for text, thinking, and tool calls in the same upstream chunk, and the runtime may surface corresponding events interleaved, for example `text_start`, `text_delta`, `toolcall_start`, `text_delta`, `toolcall_delta`. Consumers must use `contentIndex` to associate each delta/end event with its block and must not assume that a block's `*_start`/`*_delta`/`*_end` sequence is uninterrupted by events for other blocks.
## Image Input
@@ -992,7 +992,7 @@ Custom models can carry `headers` (e.g. proxies behind bot detection) and `compa
Some OpenAI-compatible servers do not understand the `developer` role used for reasoning-capable models. For those providers, set `compat.supportsDeveloperRole` to `false` so the system prompt is sent as a `system` message instead. If the server also does not support `reasoning_effort`, set `compat.supportsReasoningEffort` to `false` too. This commonly applies to Ollama, vLLM, SGLang, and similar OpenAI-compatible servers.
-Use model-level `thinkingLevelMap` to describe model-specific thinking controls. Keys are pi thinking levels (`off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`). Missing standard levels through `high` use provider defaults; `xhigh` and `max` are opt-in and require a non-null map entry. String values are sent to the provider, `null` marks a level unsupported, and maps may skip levels.
+Use model-level `thinkingLevelMap` to describe model-specific thinking controls. Keys are agent thinking levels (`off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`). Missing standard levels through `high` use provider defaults; `xhigh` and `max` are opt-in and require a non-null map entry. String values are sent to the provider, `null` marks a level unsupported, and maps may skip levels.
```typescript
const ollamaReasoningModel: Model<'openai-completions'> = {
@@ -1064,8 +1064,8 @@ interface OpenAICompletionsCompat {
requiresThinkingAsText?: boolean; // Whether thinking blocks must be converted to text (default: false)
requiresReasoningContentOnAssistantMessages?: boolean; // Whether all replayed assistant messages must include empty reasoning_content when reasoning is enabled (default: auto-detected for DeepSeek)
thinkingFormat?: 'openai' | 'openrouter' | 'deepseek' | 'together' | 'baseten' | 'zai' | 'qwen' | 'chat-template' | 'qwen-chat-template' | 'string-thinking' | 'ant-ling'; // Format for reasoning param: 'openai' uses reasoning_effort, 'openrouter' uses reasoning: { effort }, 'deepseek' uses thinking: { type } plus reasoning_effort when supported, 'together' uses reasoning: { enabled } plus reasoning_effort when supported, 'baseten' uses configurable chat_template_args plus reasoning_effort when supported, 'zai' uses thinking: { type }, 'qwen' uses enable_thinking, 'chat-template' uses configurable chat_template_kwargs, 'qwen-chat-template' uses chat_template_kwargs.enable_thinking and preserve_thinking, 'string-thinking' uses top-level thinking, 'ant-ling' uses reasoning: { effort } only for mapped efforts (default: openai)
- chatTemplateKwargs?: Record; // chat_template_kwargs values; use $var for pi-controlled thinking values
- chatTemplateArgs?: Record; // chat_template_args values for thinkingFormat: 'baseten'; use $var for pi-controlled thinking values
+ chatTemplateKwargs?: Record; // chat_template_kwargs values; use $var for agent-controlled thinking values
+ chatTemplateArgs?: Record; // chat_template_args values for thinkingFormat: 'baseten'; use $var for agent-controlled thinking values
thinkingTokenBudgetField?: 'thinking_token_budget' | 'thinking_budget' | 'thinking_budget_tokens'; // Top-level field that caps reasoning tokens from thinkingBudgets (vLLM / Qwen / llama.cpp). Off by default.
supportsThinkingTokenBudget?: boolean; // Alias for thinkingTokenBudgetField: 'thinking_token_budget' (vLLM). Prefer thinkingTokenBudgetField. Default: false.
cacheControlFormat?: 'anthropic'; // Anthropic-style cache_control on system prompt, last tool, and last user/assistant text content
@@ -1411,7 +1411,7 @@ Compat is a strict superset of the root entrypoint, so a file can switch its imp
### Adding a New Provider
-> The Step-only build ships no built-in providers; the supported way to add one is [`createProvider()`](#createprovider) / a `~/.pi/agent/models.json` entry, no package changes required.
+> The Step-only build ships no built-in providers; the supported way to add one is [`createProvider()`](#createprovider) / a `~/.stepcode/agent/models.json` entry, no package changes required.
Adding a new LLM provider requires changes across multiple files. The layered layout: API implementations live in `src/api/`, provider factories in `src/providers/`, stable generated catalog wrappers live in `src/providers/.models.ts`, and `src/models.generated.ts` registers them. This checklist covers all necessary steps:
diff --git a/packages/telemetry/README.md b/packages/telemetry/README.md
index a4c298e1..6414749e 100644
--- a/packages/telemetry/README.md
+++ b/packages/telemetry/README.md
@@ -1,6 +1,6 @@
# @step-harness/telemetry
-Vendor-neutral telemetry contracts and typed schema utilities for pi packages.
+Vendor-neutral telemetry contracts and typed schema utilities for step packages.
This package provides:
@@ -10,7 +10,7 @@ This package provides:
- serializable schema definitions with inferred TypeScript types;
- no exporter, global current-span state, or dependency on a telemetry backend.
-Applications can use the in-memory reference or provide an adapter for OpenTelemetry, Sentry, logs, or another backend. Pi packages pass telemetry contexts explicitly and define their domain schemas separately.
+Applications can use the in-memory reference or provide an adapter for OpenTelemetry, Sentry, logs, or another backend. Step packages pass telemetry contexts explicitly and define their domain schemas separately.
## Table of Contents
@@ -24,7 +24,7 @@ Applications can use the in-memory reference or provide an adapter for OpenTelem
- [Typed Schemas](#typed-schemas)
- [Start and Completion Attributes](#start-and-completion-attributes)
- [Schema Metadata](#schema-metadata)
-- [Pi Package Integration](#pi-package-integration)
+- [Package Integration](#package-integration)
- [Security and Portability](#security-and-portability)
- [API Reference](#api-reference)
- [Development](#development)
@@ -129,7 +129,7 @@ An adapter implements `TelemetryContext` and bridges the generic API to its back
- ignore calls made after settlement;
- ignore a failed recording call atomically, suppress backend failures, and still execute the business callback exactly once.
-Adapters may activate backend-native ambient context internally for automatic instrumentation, but pi code always propagates the parent through `TelemetryContext` arguments. Exporter buffering, flushing, sampling, backend IDs, and backend-specific context objects belong to the adapter. Use the [adapter conformance suite](#adapter-conformance) to check these observable semantics.
+Adapters may activate backend-native ambient context internally for automatic instrumentation, but step code always propagates the parent through `TelemetryContext` arguments. Exporter buffering, flushing, sampling, backend IDs, and backend-specific context objects belong to the adapter. Use the [adapter conformance suite](#adapter-conformance) to check these observable semantics.
## No-op Context
@@ -362,13 +362,13 @@ Parent metadata is descriptive schema data:
Adapters do not need to understand schema objects. Instrumentation helpers and tests use them to keep emitted names and attributes consistent.
-## Pi Package Integration
+## Package Integration
Package ownership is intentionally split:
- `@step-harness/telemetry` owns the vendor-neutral contract, no-op and in-memory reference contexts, schema utilities, and adapter conformance suite;
- `@step-harness/providers` accepts and propagates `telemetryContext` in provider request options but owns no telemetry schema;
-- `@step-harness/agent-core` owns and exports the pi AI-request and harness schemas, their combined readonly schema tuple, and typed span helpers.
+- `@step-harness/agent-core` owns and exports the AI-request and harness schemas, their combined readonly schema tuple, and typed span helpers.
```typescript
import {
@@ -380,7 +380,7 @@ import {
} from '@step-harness/agent-core';
```
-The pi schemas use pi-owned `pi.ai.*`, `pi.harness.*`, and `pi.session.*` names. Adapters may translate them to backend conventions without changing the emitted pi vocabulary.
+The schemas use `pi.ai.*`, `pi.harness.*`, and `pi.session.*` span names. That prefix is part of the emitted telemetry contract, so adapters may translate it to backend conventions but the vocabulary itself does not change.
## Security and Portability
diff --git a/packages/tui/docs/editor-styling.md b/packages/tui/docs/editor-styling.md
index 26653586..64c9c9c1 100644
--- a/packages/tui/docs/editor-styling.md
+++ b/packages/tui/docs/editor-styling.md
@@ -6,4 +6,4 @@ Subclasses may override the protected `styleText(text, line, startIndex)` hook t
- The hook runs after wrapping and scrolling. A fragment can be part of a longer token or only one side of the cursor.
- Preserve the input characters and display width. Only add self-contained ANSI styles that reset before the fragment ends.
- The cursor grapheme, cursor marker, padding, borders and autocomplete list bypass this hook. The base implementation returns the text unchanged.
-- Product-specific keyword detection, theme resolution and animation lifecycle belong to the caller, not `pi-tui`.
+- Product-specific keyword detection, theme resolution and animation lifecycle belong to the caller, not `@step-harness/pi-tui`.
From 777579f74a018d54a9daa553451d66761d7757ce Mon Sep 17 00:00:00 2001
From: ZouR-Ma <2605315944@qq.com>
Date: Mon, 19 Oct 2026 18:48:12 +0800
Subject: [PATCH 3/5] docs: keep extension snippets on the real `pi` factory
parameter
The `ExtensionAPI` factory parameter name is chosen by the caller, so a
documentation sample using `step` compiles and runs. It does not match the
codebase, though: `ExtensionFactory` is declared as `(pi: ExtensionAPI)` and
all 119 extension examples in `examples/` use `pi`. A reader copying a snippet
or cross-referencing an example would see two different names for the same
object.
Reverts the parameter and its call sites in code samples to `pi`, which is what
real code uses, and re-syncs the two `extensions.md` anchors that follow the
`pi.appendEntry` / `pi.registerEntryRenderer` headings. Prose and CLI examples
keep their `step` naming, since those describe the product rather than mirror
source.
---
docs/plan-loop-cron-workflow-hoh.md | 2 +-
packages/coding-agent/README.md | 10 +-
packages/coding-agent/docs/compaction.md | 8 +-
packages/coding-agent/docs/custom-provider.md | 40 +-
packages/coding-agent/docs/extensions.md | 374 +++++++++---------
packages/coding-agent/docs/mcp-catalog.md | 2 +-
packages/coding-agent/docs/rpc.md | 4 +-
packages/coding-agent/docs/sdk.md | 12 +-
packages/coding-agent/docs/session-format.md | 4 +-
packages/coding-agent/docs/tui.md | 14 +-
.../examples/extensions/README.md | 14 +-
11 files changed, 242 insertions(+), 242 deletions(-)
diff --git a/docs/plan-loop-cron-workflow-hoh.md b/docs/plan-loop-cron-workflow-hoh.md
index dfefc889..605fa615 100644
--- a/docs/plan-loop-cron-workflow-hoh.md
+++ b/docs/plan-loop-cron-workflow-hoh.md
@@ -40,7 +40,7 @@ Step 目前擅长在一个 turn 内完成工作,也已经有 session、subagen
扩展通过 `ExtensionAPI.sendMessage` 发送 custom message:
```ts
-step.sendMessage(
+pi.sendMessage(
{
customType: "step-wakeup" | "step-cron" | "step-workflow",
content: [{ type: "text", text: "[wakeup] ..." }],
diff --git a/packages/coding-agent/README.md b/packages/coding-agent/README.md
index 8a99a7bd..e2e35dcd 100644
--- a/packages/coding-agent/README.md
+++ b/packages/coding-agent/README.md
@@ -307,14 +307,14 @@ Place in `~/.stepcode/agent/skills/`, `~/.stepcode/skills/`, `~/.agents/skills/`
TypeScript modules that extend step with custom tools, commands, keyboard shortcuts, event handlers, and UI components.
```typescript
-export default function (step: ExtensionAPI) {
- step.registerTool({ name: "deploy", ... });
- step.registerCommand("stats", { ... });
- step.on("tool_call", async (event, ctx) => { ... });
+export default function (pi: ExtensionAPI) {
+ pi.registerTool({ name: "deploy", ... });
+ pi.registerCommand("stats", { ... });
+ pi.on("tool_call", async (event, ctx) => { ... });
}
```
-The default export can also be `async`. step waits for async extension factories before startup continues, which is useful for one-time initialization such as fetching remote model lists before calling `step.registerProvider()`.
+The default export can also be `async`. step waits for async extension factories before startup continues, which is useful for one-time initialization such as fetching remote model lists before calling `pi.registerProvider()`.
**What's possible:**
- Custom tools (or replace built-in tools entirely)
diff --git a/packages/coding-agent/docs/compaction.md b/packages/coding-agent/docs/compaction.md
index cc0a3241..22b23f60 100644
--- a/packages/coding-agent/docs/compaction.md
+++ b/packages/coding-agent/docs/compaction.md
@@ -297,7 +297,7 @@ Extensions can intercept and customize both compaction and branch summarization.
Fired before auto-compaction or `/compact`. Can cancel or provide custom summary. See `SessionBeforeCompactEvent` and `CompactionPreparation` in the types file.
```typescript
-step.on("session_before_compact", async (event, ctx) => {
+pi.on("session_before_compact", async (event, ctx) => {
const { preparation, branchEntries, customInstructions, reason, willRetry, signal } = event;
// preparation.messagesToSummarize - messages to summarize
@@ -336,7 +336,7 @@ To generate a summary with your own model, convert messages to text using `seria
```typescript
import { convertToLlm, serializeConversation } from "@step-harness/coding-agent";
-step.on("session_before_compact", async (event, ctx) => {
+pi.on("session_before_compact", async (event, ctx) => {
const { preparation } = event;
// Convert AgentMessage[] to Message[], then serialize to text
@@ -371,7 +371,7 @@ See [custom-compaction.ts](../examples/extensions/custom-compaction.ts) for a co
Fired when manual or automatic compaction fails or is aborted. This is useful for telemetry extensions that need to pair `session_before_compact` attempts with terminal outcomes.
```typescript
-step.on("session_compact_failed", async (event, ctx) => {
+pi.on("session_compact_failed", async (event, ctx) => {
const { reason, errorMessage, aborted, willRetry, fromExtension } = event;
// reason - "manual" (/compact), "threshold", or "overflow"
// errorMessage - present for non-abort failures
@@ -386,7 +386,7 @@ step.on("session_compact_failed", async (event, ctx) => {
Fired before `/tree` navigation. Always fires regardless of whether user chose to summarize. Can cancel navigation or provide custom summary.
```typescript
-step.on("session_before_tree", async (event, ctx) => {
+pi.on("session_before_tree", async (event, ctx) => {
const { preparation, signal } = event;
// preparation.targetId - where we're navigating to
diff --git a/packages/coding-agent/docs/custom-provider.md b/packages/coding-agent/docs/custom-provider.md
index 9b72d5de..f92b507a 100644
--- a/packages/coding-agent/docs/custom-provider.md
+++ b/packages/coding-agent/docs/custom-provider.md
@@ -1,6 +1,6 @@
# Custom Providers
-Extensions can register custom model providers via `step.registerProvider()`. This enables:
+Extensions can register custom model providers via `pi.registerProvider()`. This enables:
- **Proxies** - Route requests through corporate proxies or API gateways
- **Custom endpoints** - Use self-hosted or private model deployments
@@ -35,8 +35,8 @@ Extensions can register either a complete `Provider` or use the legacy provider-
import { createProvider, openAICompletionsApi } from "@step-harness/providers";
import type { ExtensionAPI } from "@step-harness/coding-agent";
-export default function (step: ExtensionAPI) {
- step.registerProvider(createProvider({
+export default function (pi: ExtensionAPI) {
+ pi.registerProvider(createProvider({
id: "native-local",
name: "Native Local",
baseUrl: "http://localhost:8080/v1",
@@ -62,12 +62,12 @@ export default function (step: ExtensionAPI) {
// Legacy provider-config form:
// Override baseUrl for existing provider
- step.registerProvider("anthropic", {
+ pi.registerProvider("anthropic", {
baseUrl: "https://proxy.example.com"
});
// Register new provider with models
- step.registerProvider("my-provider", {
+ pi.registerProvider("my-provider", {
name: "My Provider",
baseUrl: "https://api.example.com",
apiKey: "$MY_API_KEY",
@@ -95,19 +95,19 @@ The simplest use case: redirect an existing provider through a proxy.
```typescript
// All Anthropic requests now go through your proxy
-step.registerProvider("anthropic", {
+pi.registerProvider("anthropic", {
baseUrl: "https://proxy.example.com"
});
// Add custom headers to OpenAI requests
-step.registerProvider("openai", {
+pi.registerProvider("openai", {
headers: {
"X-Custom-Header": "value"
}
});
// Both baseUrl and headers
-step.registerProvider("google", {
+pi.registerProvider("google", {
baseUrl: "https://ai-gateway.corp.com/google",
headers: {
"X-Corp-Auth": "$CORP_AUTH_TOKEN" // env var or literal
@@ -126,7 +126,7 @@ If the model list comes from a remote endpoint, use an async extension factory:
```typescript
import type { ExtensionAPI } from "@step-harness/coding-agent";
-export default async function (step: ExtensionAPI) {
+export default async function (pi: ExtensionAPI) {
const response = await fetch("http://localhost:1234/v1/models");
const payload = (await response.json()) as {
data: Array<{
@@ -137,7 +137,7 @@ export default async function (step: ExtensionAPI) {
}>;
};
- step.registerProvider("local-openai", {
+ pi.registerProvider("local-openai", {
baseUrl: "http://localhost:1234/v1",
apiKey: "$LOCAL_OPENAI_API_KEY",
api: "openai-completions",
@@ -157,7 +157,7 @@ export default async function (step: ExtensionAPI) {
This registers the fetched models before startup finishes.
```typescript
-step.registerProvider("my-llm", {
+pi.registerProvider("my-llm", {
baseUrl: "https://api.my-llm.com/v1",
apiKey: "$MY_LLM_API_KEY", // env var reference
api: "openai-completions", // which streaming API to use
@@ -186,11 +186,11 @@ When `models` is provided, it **replaces** all existing models for that provider
## Unregister Provider
-Use `step.unregisterProvider(name)` to remove a provider that was previously registered via `step.registerProvider(name, ...)`:
+Use `pi.unregisterProvider(name)` to remove a provider that was previously registered via `pi.registerProvider(name, ...)`:
```typescript
// Register
-step.registerProvider("my-llm", {
+pi.registerProvider("my-llm", {
baseUrl: "https://api.my-llm.com/v1",
apiKey: "$MY_LLM_API_KEY",
api: "openai-completions",
@@ -208,7 +208,7 @@ step.registerProvider("my-llm", {
});
// Later, remove it
-step.unregisterProvider("my-llm");
+pi.unregisterProvider("my-llm");
```
Unregistering removes that provider's dynamic models, API key fallback, OAuth provider registration, and custom stream handler registrations. Any built-in models or provider behavior that were overridden are restored.
@@ -271,7 +271,7 @@ For Anthropic-compatible providers using `api: "anthropic-messages"`, set `compa
If your provider expects `Authorization: Bearer ` but doesn't use a standard API, set `authHeader: true`:
```typescript
-step.registerProvider("custom-api", {
+pi.registerProvider("custom-api", {
baseUrl: "https://api.example.com",
apiKey: "$MY_API_KEY",
authHeader: true, // adds Authorization: Bearer header
@@ -289,7 +289,7 @@ Add OAuth/SSO authentication that integrates with `/login`:
```typescript
import type { OAuthCredentials, OAuthLoginCallbacks } from "@step-harness/providers";
-step.registerProvider("corporate-ai", {
+pi.registerProvider("corporate-ai", {
baseUrl: "https://ai.corp.com/v1",
api: "openai-responses",
models: [...],
@@ -577,10 +577,10 @@ If your provider returns overflow errors with a message step does not recognize,
```typescript
const MY_PROVIDER_OVERFLOW_PATTERN = /your provider's overflow phrase/i;
-export default function (step: ExtensionAPI) {
- step.registerProvider("my-provider", { /* ... */ });
+export default function (pi: ExtensionAPI) {
+ pi.registerProvider("my-provider", { /* ... */ });
- step.on("message_end", (event, ctx) => {
+ pi.on("message_end", (event, ctx) => {
const message = event.message;
if (message.role !== "assistant") return;
if (message.stopReason !== "error") return;
@@ -622,7 +622,7 @@ Guard the rewrite carefully:
Register your stream function:
```typescript
-step.registerProvider("my-provider", {
+pi.registerProvider("my-provider", {
baseUrl: "https://api.example.com",
apiKey: "$MY_API_KEY",
api: "my-custom-api",
diff --git a/packages/coding-agent/docs/extensions.md b/packages/coding-agent/docs/extensions.md
index f0f1bdf4..54aa26fb 100644
--- a/packages/coding-agent/docs/extensions.md
+++ b/packages/coding-agent/docs/extensions.md
@@ -7,12 +7,12 @@ Extensions are TypeScript modules that extend step's behavior. They can subscrib
> **Placement for /reload:** Put extensions in `~/.stepcode/agent/extensions/` (global) or `.stepcode/extensions/` (project-local) for auto-discovery. Use `step -e ./path.ts` only for quick tests. Extensions in auto-discovered locations can be hot-reloaded with `/reload`.
**Key capabilities:**
-- **Custom tools** - Register tools the LLM can call via `step.registerTool()`
+- **Custom tools** - Register tools the LLM can call via `pi.registerTool()`
- **Event interception** - Block or modify tool calls, inject context, customize compaction
- **User interaction** - Prompt users via `ctx.ui` (select, confirm, input, notify)
- **Custom UI components** - Full TUI components with keyboard input via `ctx.ui.custom()` for complex interactions
-- **Custom commands** - Register commands like `/mycommand` via `step.registerCommand()`
-- **Session persistence** - Store state that survives restarts via `step.appendEntry()`
+- **Custom commands** - Register commands like `/mycommand` via `pi.registerCommand()`
+- **Session persistence** - Store state that survives restarts via `pi.appendEntry()`
- **Custom rendering** - Control how tool calls/results and messages appear in TUI
**Example use cases:**
@@ -61,13 +61,13 @@ Create `~/.stepcode/agent/extensions/my-extension.ts`:
import type { ExtensionAPI } from "@step-harness/coding-agent";
import { Type } from "typebox";
-export default function (step: ExtensionAPI) {
+export default function (pi: ExtensionAPI) {
// React to events
- step.on("session_start", async (_event, ctx) => {
+ pi.on("session_start", async (_event, ctx) => {
ctx.ui.notify("Extension loaded!", "info");
});
- step.on("tool_call", async (event, ctx) => {
+ pi.on("tool_call", async (event, ctx) => {
if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
const ok = await ctx.ui.confirm("Dangerous!", "Allow rm -rf?");
if (!ok) return { block: true, reason: "Blocked by user" };
@@ -75,7 +75,7 @@ export default function (step: ExtensionAPI) {
});
// Register a custom tool
- step.registerTool({
+ pi.registerTool({
name: "greet",
label: "Greet",
description: "Greet someone by name",
@@ -91,7 +91,7 @@ export default function (step: ExtensionAPI) {
});
// Register a command
- step.registerCommand("hello", {
+ pi.registerCommand("hello", {
description: "Say hello",
handler: async (args, ctx) => {
ctx.ui.notify(`Hello ${args || "world"}!`, "info");
@@ -158,9 +158,9 @@ An extension exports a default factory function that receives `ExtensionAPI`. Th
```typescript
import type { ExtensionAPI } from "@step-harness/coding-agent";
-export default function (step: ExtensionAPI) {
+export default function (pi: ExtensionAPI) {
// Subscribe to events
- step.on("event_name", async (event, ctx) => {
+ pi.on("event_name", async (event, ctx) => {
// ctx.ui for user interaction
const ok = await ctx.ui.confirm("Title", "Are you sure?");
ctx.ui.notify("Done!", "info");
@@ -169,16 +169,16 @@ export default function (step: ExtensionAPI) {
});
// Register tools, commands, shortcuts, flags
- step.registerTool({ ... });
- step.registerCommand("name", { ... });
- step.registerShortcut("ctrl+x", { ... });
- step.registerFlag("my-flag", { ... });
+ pi.registerTool({ ... });
+ pi.registerCommand("name", { ... });
+ pi.registerShortcut("ctrl+x", { ... });
+ pi.registerFlag("my-flag", { ... });
}
```
Extensions are loaded via [jiti](https://github.com/unjs/jiti), so TypeScript works without compilation.
-If the factory returns a `Promise`, step awaits it before continuing startup. That means async initialization completes before `session_start`, before `resources_discover`, and before provider registrations queued via `step.registerProvider()` are flushed.
+If the factory returns a `Promise`, step awaits it before continuing startup. That means async initialization completes before `session_start`, before `resources_discover`, and before provider registrations queued via `pi.registerProvider()` are flushed.
### Async factory functions
@@ -187,7 +187,7 @@ Use an async factory for one-time startup work such as fetching remote configura
```typescript
import type { ExtensionAPI } from "@step-harness/coding-agent";
-export default async function (step: ExtensionAPI) {
+export default async function (pi: ExtensionAPI) {
const response = await fetch("http://localhost:1234/v1/models");
const payload = (await response.json()) as {
data: Array<{
@@ -198,7 +198,7 @@ export default async function (step: ExtensionAPI) {
}>;
};
- step.registerProvider("local-openai", {
+ pi.registerProvider("local-openai", {
baseUrl: "http://localhost:1234/v1",
apiKey: "$LOCAL_OPENAI_API_KEY",
api: "openai-completions",
@@ -325,7 +325,7 @@ user sends another prompt ◄─────────────────
├─► session_start { reason: "fork", previousSessionFile }
└─► resources_discover { reason: "startup" }
-/name or step.setSessionName()
+/name or pi.setSessionName()
└─► session_info_changed
/compact or auto-compaction
@@ -341,7 +341,7 @@ user sends another prompt ◄─────────────────
├─► thinking_level_select (if model change changes/clamps thinking level)
└─► model_select
-thinking level changes (settings, keybinding, step.setThinkingLevel())
+thinking level changes (settings, keybinding, pi.setThinkingLevel())
└─► thinking_level_select
exit (Ctrl+C, Ctrl+D, SIGHUP, SIGTERM)
@@ -355,7 +355,7 @@ exit (Ctrl+C, Ctrl+D, SIGHUP, SIGTERM)
Fired before step decides whether to trust a project with dynamic configs (`.stepcode`, `.agents/skills`, or `.claude/skills`). It runs during startup and when session replacement (for example `/resume`) enters a cwd whose trust has not been resolved in the current process. Only user/global extensions and CLI `-e` extensions participate; project-local extensions are not loaded until after trust is resolved.
```typescript
-step.on("project_trust", async (event, ctx) => {
+pi.on("project_trust", async (event, ctx) => {
// event.cwd - current working directory
// ctx has a limited trust context: cwd, mode, hasUI, and select/confirm/input/notify UI helpers
if (await ctx.ui.confirm("Trust project?", event.cwd)) {
@@ -375,7 +375,7 @@ Fired after `session_start` so extensions can contribute additional skill, promp
The startup path uses `reason: "startup"`. Reload uses `reason: "reload"`.
```typescript
-step.on("resources_discover", async (event, _ctx) => {
+pi.on("resources_discover", async (event, _ctx) => {
// event.cwd - current working directory
// event.reason - "startup" | "reload"
return {
@@ -395,7 +395,7 @@ See [Session Format](session-format.md) for session storage internals and the Se
Fired when a session is started, loaded, or reloaded.
```typescript
-step.on("session_start", async (event, ctx) => {
+pi.on("session_start", async (event, ctx) => {
// event.reason - "startup" | "reload" | "new" | "resume" | "fork"
// event.previousSessionFile - present for "new", "resume", and "fork"
ctx.ui.notify(`Session: ${ctx.sessionManager.getSessionFile() ?? "ephemeral"}`, "info");
@@ -404,10 +404,10 @@ step.on("session_start", async (event, ctx) => {
#### session_info_changed
-Fired when the current session display name is set via `/name`, RPC, or `step.setSessionName()`.
+Fired when the current session display name is set via `/name`, RPC, or `pi.setSessionName()`.
```typescript
-step.on("session_info_changed", async (event, ctx) => {
+pi.on("session_info_changed", async (event, ctx) => {
// event.name - current normalized name, or undefined if cleared
ctx.ui.notify(`Session renamed: ${event.name ?? "(none)"}`, "info");
});
@@ -418,7 +418,7 @@ step.on("session_info_changed", async (event, ctx) => {
Fired before starting a new session (`/new`) or switching sessions (`/resume`).
```typescript
-step.on("session_before_switch", async (event, ctx) => {
+pi.on("session_before_switch", async (event, ctx) => {
// event.reason - "new" or "resume"
// event.targetSessionFile - session we're switching to (only for "resume")
@@ -437,7 +437,7 @@ Do cleanup work in `session_shutdown`, then reestablish any in-memory state in `
Fired when forking via `/fork` or cloning via `/clone`.
```typescript
-step.on("session_before_fork", async (event, ctx) => {
+pi.on("session_before_fork", async (event, ctx) => {
// event.entryId - ID of the selected entry
// event.position - "before" for /fork, "at" for /clone
return { cancel: true }; // Cancel fork/clone
@@ -454,7 +454,7 @@ Do cleanup work in `session_shutdown`, then reestablish any in-memory state in `
Fired on compaction. See [compaction.md](compaction.md) for details.
```typescript
-step.on("session_before_compact", async (event, ctx) => {
+pi.on("session_before_compact", async (event, ctx) => {
const { preparation, branchEntries, customInstructions, reason, willRetry, signal } = event;
// reason - "manual" (/compact), "threshold", or "overflow"
@@ -474,14 +474,14 @@ step.on("session_before_compact", async (event, ctx) => {
};
});
-step.on("session_compact", async (event, ctx) => {
+pi.on("session_compact", async (event, ctx) => {
// event.compactionEntry - the saved compaction
// event.fromExtension - whether extension provided it
// event.reason - "manual" (/compact), "threshold", or "overflow"
// event.willRetry - whether the aborted turn is retried after compaction (overflow recovery)
});
-step.on("session_compact_failed", async (event, ctx) => {
+pi.on("session_compact_failed", async (event, ctx) => {
// event.reason - "manual" (/compact), "threshold", or "overflow"
// event.errorMessage - present for non-abort failures
// event.aborted - true for cancelled/aborted compactions
@@ -495,7 +495,7 @@ step.on("session_compact_failed", async (event, ctx) => {
Fired on `/tree` navigation. See [Sessions](sessions.md) for tree navigation concepts.
```typescript
-step.on("session_before_tree", async (event, ctx) => {
+pi.on("session_before_tree", async (event, ctx) => {
const { preparation, signal } = event;
return { cancel: true };
// OR provide custom summary:
@@ -508,7 +508,7 @@ step.on("session_before_tree", async (event, ctx) => {
};
});
-step.on("session_tree", async (event, ctx) => {
+pi.on("session_tree", async (event, ctx) => {
// event.newLeafId, oldLeafId, summaryEntry, fromExtension
});
```
@@ -518,7 +518,7 @@ step.on("session_tree", async (event, ctx) => {
Fired before a started session runtime is torn down. Use this to clean up resources opened from `session_start` or other session-scoped hooks.
```typescript
-step.on("session_shutdown", async (event, ctx) => {
+pi.on("session_shutdown", async (event, ctx) => {
// event.reason - "quit" | "reload" | "new" | "resume" | "fork"
// event.targetSessionFile - destination session for session replacement flows
// Cleanup, save state, etc.
@@ -532,7 +532,7 @@ step.on("session_shutdown", async (event, ctx) => {
Fired after user submits prompt, before agent loop. Can inject a message and/or modify the system prompt.
```typescript
-step.on("before_agent_start", async (event, ctx) => {
+pi.on("before_agent_start", async (event, ctx) => {
// event.prompt - user's prompt text
// event.images - attached images (if any)
// event.systemPrompt - current chained system prompt for this handler
@@ -569,13 +569,13 @@ Inside `before_agent_start`, `event.systemPrompt` and `ctx.getSystemPrompt()` bo
`agent_start` fires when a low-level agent run begins. `agent_end` fires when that run ends, but Step may still auto-retry, auto-compact and retry, or continue with queued follow-up messages. Use `agent_settled` for status integrations that need to know Step will not continue running automatically.
```typescript
-step.on("agent_start", async (_event, ctx) => {});
+pi.on("agent_start", async (_event, ctx) => {});
-step.on("agent_end", async (event, ctx) => {
+pi.on("agent_end", async (event, ctx) => {
// event.messages - messages from this low-level run
});
-step.on("agent_settled", async (_event, ctx) => {
+pi.on("agent_settled", async (_event, ctx) => {
// ctx.isIdle() is true here unless another extension started a new run.
});
```
@@ -587,13 +587,13 @@ Notification-only lifecycle events for blocking user-facing extension UI prompts
Nested or overlapping prompts are coalesced into one outer waiting span. Handlers are invoked best-effort and are not awaited before showing or closing the prompt.
```typescript
-step.on("ui_prompt_start", async (event, ctx) => {
+pi.on("ui_prompt_start", async (event, ctx) => {
// event.reason === "ui_prompt"
// event.kind: "select" | "confirm" | "input" | "editor" | "custom"
// event.title: prompt title when available
});
-step.on("ui_prompt_end", async (event, ctx) => {
+pi.on("ui_prompt_end", async (event, ctx) => {
// Step is no longer waiting on that UI prompt span.
});
```
@@ -603,11 +603,11 @@ step.on("ui_prompt_end", async (event, ctx) => {
Fired for each turn (one LLM response + tool calls).
```typescript
-step.on("turn_start", async (event, ctx) => {
+pi.on("turn_start", async (event, ctx) => {
// event.turnIndex, event.timestamp
});
-step.on("turn_end", async (event, ctx) => {
+pi.on("turn_end", async (event, ctx) => {
// event.turnIndex, event.message, event.toolResults
});
```
@@ -621,16 +621,16 @@ Fired for message lifecycle updates.
- `message_end` handlers can return `{ message }` to replace the finalized message. The replacement must keep the same `role`.
```typescript
-step.on("message_start", async (event, ctx) => {
+pi.on("message_start", async (event, ctx) => {
// event.message
});
-step.on("message_update", async (event, ctx) => {
+pi.on("message_update", async (event, ctx) => {
// event.message
// event.assistantMessageEvent (token-by-token stream event)
});
-step.on("message_end", async (event, ctx) => {
+pi.on("message_end", async (event, ctx) => {
if (event.message.role !== "assistant") return;
return {
@@ -659,15 +659,15 @@ In parallel tool mode:
- final `toolResult` message events are still emitted later in assistant source order
```typescript
-step.on("tool_execution_start", async (event, ctx) => {
+pi.on("tool_execution_start", async (event, ctx) => {
// event.toolCallId, event.toolName, event.args
});
-step.on("tool_execution_update", async (event, ctx) => {
+pi.on("tool_execution_update", async (event, ctx) => {
// event.toolCallId, event.toolName, event.args, event.partialResult
});
-step.on("tool_execution_end", async (event, ctx) => {
+pi.on("tool_execution_end", async (event, ctx) => {
// event.toolCallId, event.toolName, event.result, event.isError
});
```
@@ -677,7 +677,7 @@ step.on("tool_execution_end", async (event, ctx) => {
Fired before each LLM call. Modify messages non-destructively. See [Session Format](session-format.md) for message types.
```typescript
-step.on("context", async (event, ctx) => {
+pi.on("context", async (event, ctx) => {
// event.messages - deep copy, safe to modify
const filtered = event.messages.filter(m => !shouldPrune(m));
return { messages: filtered };
@@ -691,7 +691,7 @@ Fired after the outgoing HTTP headers are assembled. Use it to add, override, or
Handlers mutate `event.headers` in place. Set a key to a string to add or override it, or to `null` to delete it.
```typescript
-step.on("before_provider_headers", (event, ctx) => {
+pi.on("before_provider_headers", (event, ctx) => {
// Add or override — e.g. a session id for gateway tracing/attribution
event.headers["x-session-id"] = ctx.sessionManager.getSessionId();
@@ -709,7 +709,7 @@ Fired after the provider-specific payload is built, right before the request is
This hook can rewrite provider-level system instructions or remove them entirely. Those payload-level changes are not reflected by `ctx.getSystemPrompt()`, which reports Step's system prompt string rather than the final serialized provider payload.
```typescript
-step.on("before_provider_request", (event, ctx) => {
+pi.on("before_provider_request", (event, ctx) => {
console.log(JSON.stringify(event.payload, null, 2));
// Optional: replace payload
@@ -724,7 +724,7 @@ This is mainly useful for debugging provider serialization and cache behavior.
Fired after an HTTP response is received and before its stream body is consumed. Handlers run in extension load order.
```typescript
-step.on("after_provider_response", (event, ctx) => {
+pi.on("after_provider_response", (event, ctx) => {
// event.status - HTTP status code
// event.headers - normalized response headers
if (event.status === 429) {
@@ -742,7 +742,7 @@ Header availability depends on provider and transport. Providers that abstract H
Fired when the model changes via `/model` command, model cycling (`Ctrl+P`), or session restore.
```typescript
-step.on("model_select", async (event, ctx) => {
+pi.on("model_select", async (event, ctx) => {
// event.model - newly selected model
// event.previousModel - previous model (undefined if first selection)
// event.source - "set" | "cycle" | "restore"
@@ -763,7 +763,7 @@ Use this to update UI elements (status bars, footers) or perform model-specific
Fired when the thinking level changes. This is notification-only; handler return values are ignored.
```typescript
-step.on("thinking_level_select", async (event, ctx) => {
+pi.on("thinking_level_select", async (event, ctx) => {
// event.level - newly selected thinking level
// event.previousLevel - previous thinking level
@@ -771,7 +771,7 @@ step.on("thinking_level_select", async (event, ctx) => {
});
```
-Use this to update extension UI when `step.setThinkingLevel()`, model changes, or built-in thinking-level controls change the active thinking level.
+Use this to update extension UI when `pi.setThinkingLevel()`, model changes, or built-in thinking-level controls change the active thinking level.
### Tool Events
@@ -795,7 +795,7 @@ Behavior guarantees:
```typescript
import { isToolCallEventType } from "@step-harness/coding-agent";
-step.on("tool_call", async (event, ctx) => {
+pi.on("tool_call", async (event, ctx) => {
// event.toolName - "bash", "read", "write", "edit", etc.
// event.toolCallId
// event.input - tool parameters (mutable)
@@ -832,7 +832,7 @@ Use `isToolCallEventType` with explicit type parameters:
import { isToolCallEventType } from "@step-harness/coding-agent";
import type { MyToolInput } from "my-extension";
-step.on("tool_call", (event) => {
+pi.on("tool_call", (event) => {
if (isToolCallEventType<"my_tool", MyToolInput>("my_tool", event)) {
event.input.action; // typed
}
@@ -855,7 +855,7 @@ Use `ctx.signal` for nested async work inside the handler. This lets Esc cancel
```typescript
import { isBashToolResult } from "@step-harness/coding-agent";
-step.on("tool_result", async (event, ctx) => {
+pi.on("tool_result", async (event, ctx) => {
// event.toolName, event.toolCallId, event.input
// event.content, event.details, event.isError, event.usage
@@ -883,7 +883,7 @@ Fired when user executes `!` or `!!` commands. **Can intercept.**
```typescript
import { createLocalBashOperations } from "@step-harness/coding-agent";
-step.on("user_bash", (event, ctx) => {
+pi.on("user_bash", (event, ctx) => {
// event.command - the bash command
// event.excludeFromContext - true if !! prefix
// event.cwd - working directory
@@ -920,7 +920,7 @@ Fired when user input is received, after extension commands are checked but befo
5. Agent processing begins (`before_agent_start`, etc.)
```typescript
-step.on("input", async (event, ctx) => {
+pi.on("input", async (event, ctx) => {
// event.text - raw input (before skill/template expansion)
// event.images - attached images, if any
// event.source - "interactive" (typed), "rpc" (API), or "extension" (via sendUserMessage)
@@ -983,8 +983,8 @@ Use `CONFIG_DIR_NAME` instead of hardcoding `.stepcode` when constructing projec
import { CONFIG_DIR_NAME, type ExtensionAPI } from "@step-harness/coding-agent";
import { join } from "node:path";
-export default function (step: ExtensionAPI) {
- step.on("session_start", (_event, ctx) => {
+export default function (pi: ExtensionAPI) {
+ pi.on("session_start", (_event, ctx) => {
const projectConfigPath = join(ctx.cwd, CONFIG_DIR_NAME, "my-extension.json");
// ...
});
@@ -1029,7 +1029,7 @@ Use this for abort-aware nested work started by extension handlers, for example:
It is usually `undefined` in idle or non-turn contexts such as session events, extension commands, and shortcuts fired while step is idle.
```typescript
-step.on("tool_result", async (event, ctx) => {
+pi.on("tool_result", async (event, ctx) => {
const response = await fetch("https://example.com/api", {
method: "POST",
body: JSON.stringify(event),
@@ -1056,7 +1056,7 @@ Request a graceful shutdown of step.
Emits `session_shutdown` event to all extensions before exiting. Available in all contexts (event handlers, tools, commands, shortcuts).
```typescript
-step.on("tool_call", (event, ctx) => {
+pi.on("tool_call", (event, ctx) => {
if (isFatal(event.input)) {
ctx.shutdown();
}
@@ -1100,7 +1100,7 @@ Returns Step's current system prompt string.
- If later-loaded extensions run after yours, they can still change what is ultimately sent.
```typescript
-step.on("before_agent_start", (event, ctx) => {
+pi.on("before_agent_start", (event, ctx) => {
const prompt = ctx.getSystemPrompt();
console.log(`System prompt length: ${prompt.length}`);
});
@@ -1128,7 +1128,7 @@ This reports the current base prompt inputs. It does not include per-turn `befor
Wait for the agent to fully settle, including automatic retries, auto-compaction retries, and queued continuations:
```typescript
-step.registerCommand("my-cmd", {
+pi.registerCommand("my-cmd", {
handler: async (args, ctx) => {
await ctx.waitForIdle();
// Agent is now idle, safe to modify session
@@ -1237,7 +1237,7 @@ To discover available sessions, use the static `SessionManager.list()` or `Sessi
```typescript
import { SessionManager } from "@step-harness/coding-agent";
-step.registerCommand("switch", {
+pi.registerCommand("switch", {
description: "Switch to another session",
handler: async (args, ctx) => {
const sessions = await SessionManager.list(ctx.cwd);
@@ -1271,7 +1271,7 @@ Lifecycle and footguns:
Safe pattern:
```typescript
-step.registerCommand("handoff", {
+pi.registerCommand("handoff", {
handler: async (_args, ctx) => {
const kickoff = "Continue from the replacement session";
await ctx.newSession({
@@ -1286,14 +1286,14 @@ step.registerCommand("handoff", {
Unsafe pattern:
```typescript
-step.registerCommand("handoff", {
+pi.registerCommand("handoff", {
handler: async (_args, ctx) => {
const oldSessionManager = ctx.sessionManager;
await ctx.newSession({
withSession: async (_ctx) => {
// stale old objects: do not do this
oldSessionManager.getSessionFile();
- step.sendUserMessage("wrong");
+ pi.sendUserMessage("wrong");
},
});
},
@@ -1305,7 +1305,7 @@ step.registerCommand("handoff", {
Run the same reload flow as `/reload`.
```typescript
-step.registerCommand("reload-runtime", {
+pi.registerCommand("reload-runtime", {
description: "Reload extensions, skills, prompts, themes, and context files",
handler: async (_args, ctx) => {
await ctx.reload();
@@ -1332,8 +1332,8 @@ Example tool the LLM can call to trigger reload:
import type { ExtensionAPI } from "@step-harness/coding-agent";
import { Type } from "typebox";
-export default function (step: ExtensionAPI) {
- step.registerCommand("reload-runtime", {
+export default function (pi: ExtensionAPI) {
+ pi.registerCommand("reload-runtime", {
description: "Reload extensions, skills, prompts, themes, and context files",
handler: async (_args, ctx) => {
await ctx.reload();
@@ -1341,13 +1341,13 @@ export default function (step: ExtensionAPI) {
},
});
- step.registerTool({
+ pi.registerTool({
name: "reload_runtime",
label: "Reload Runtime",
description: "Reload extensions, skills, prompts, themes, and context files",
parameters: Type.Object({}),
async execute() {
- step.sendUserMessage("/reload-runtime", { deliverAs: "followUp" });
+ pi.sendUserMessage("/reload-runtime", { deliverAs: "followUp" });
return {
content: [{ type: "text", text: "Queued /reload-runtime as a follow-up command." }],
};
@@ -1358,17 +1358,17 @@ export default function (step: ExtensionAPI) {
## ExtensionAPI Methods
-### step.on(event, handler)
+### pi.on(event, handler)
Subscribe to events. See [Events](#events) for event types and return values.
-### step.registerTool(definition)
+### pi.registerTool(definition)
Register a custom tool callable by the LLM. See [Custom Tools](#custom-tools) for full details.
-`step.registerTool()` works both during extension load and after startup. You can call it inside `session_start`, command handlers, or other event handlers. New tools are refreshed immediately in the same session, so they appear in `step.getAllTools()` and are callable by the LLM without `/reload`.
+`pi.registerTool()` works both during extension load and after startup. You can call it inside `session_start`, command handlers, or other event handlers. New tools are refreshed immediately in the same session, so they appear in `pi.getAllTools()` and are callable by the LLM without `/reload`.
-Use `step.setActiveTools()` to enable or disable tools (including dynamically added tools) at runtime.
+Use `pi.setActiveTools()` to enable or disable tools (including dynamically added tools) at runtime.
Use `promptSnippet` to opt a custom tool into a one-line entry in `Available tools`, and `promptGuidelines` to append tool-specific bullets to the default `Guidelines` section when the tool is active.
@@ -1380,7 +1380,7 @@ See [dynamic-tools.ts](../examples/extensions/dynamic-tools.ts) for a full examp
import { Type } from "typebox";
import { StringEnum } from "@step-harness/providers";
-step.registerTool({
+pi.registerTool({
name: "my_tool",
label: "My Tool",
description: "What this tool does",
@@ -1413,12 +1413,12 @@ step.registerTool({
});
```
-### step.sendMessage(message, options?)
+### pi.sendMessage(message, options?)
-Inject a custom message into the session. Custom messages participate in LLM context. For durable TUI-only content that should not be sent to the LLM, use [`step.appendEntry()`](#stepappendentrycustomtype-data) with [`step.registerEntryRenderer()`](#stepregisterentryrenderercustomtype-renderer).
+Inject a custom message into the session. Custom messages participate in LLM context. For durable TUI-only content that should not be sent to the LLM, use [`pi.appendEntry()`](#piappendentrycustomtype-data) with [`pi.registerEntryRenderer()`](#piregisterentryrenderercustomtype-renderer).
```typescript
-step.sendMessage({
+pi.sendMessage({
customType: "my-extension",
content: "Message text",
display: true,
@@ -1436,26 +1436,26 @@ step.sendMessage({
- `"nextTurn"` - Queued for next user prompt. Does not interrupt or trigger anything.
- `triggerTurn: true` - If agent is idle, trigger an LLM response immediately. Only applies to `"steer"` and `"followUp"` modes (ignored for `"nextTurn"`).
-### step.sendUserMessage(content, options?)
+### pi.sendUserMessage(content, options?)
Send a user message to the agent. Unlike `sendMessage()` which sends custom messages, this sends an actual user message that appears as if typed by the user. Always triggers a turn.
```typescript
// Simple text message
-step.sendUserMessage("What is 2+2?");
+pi.sendUserMessage("What is 2+2?");
// With content array (text + images)
-step.sendUserMessage([
+pi.sendUserMessage([
{ type: "text", text: "Describe this image:" },
{ type: "image", source: { type: "base64", mediaType: "image/png", data: "..." } },
]);
// During streaming - must specify delivery mode
-step.sendUserMessage("Focus on error handling", { deliverAs: "steer" });
-step.sendUserMessage("And then summarize", { deliverAs: "followUp" });
+pi.sendUserMessage("Focus on error handling", { deliverAs: "steer" });
+pi.sendUserMessage("And then summarize", { deliverAs: "followUp" });
// Opt in to extension command dispatch and skill/prompt template expansion
-step.sendUserMessage("/review src/index.ts", { expandPromptTemplates: true });
+pi.sendUserMessage("/review src/index.ts", { expandPromptTemplates: true });
```
**Options:**
@@ -1468,16 +1468,16 @@ When not streaming, the message is sent immediately and triggers a new turn. Whe
See [send-user-message.ts](../examples/extensions/send-user-message.ts) for a complete example.
-### step.appendEntry(customType, data?)
+### pi.appendEntry(customType, data?)
-Persist extension data. Custom entries do NOT participate in LLM context. In interactive mode, they can also render inside the chat transcript when paired with `step.registerEntryRenderer()`.
+Persist extension data. Custom entries do NOT participate in LLM context. In interactive mode, they can also render inside the chat transcript when paired with `pi.registerEntryRenderer()`.
```typescript
-step.appendEntry("my-state", { count: 42 });
-step.appendEntry("status-card", { title: "Indexed files", count: 17 });
+pi.appendEntry("my-state", { count: 42 });
+pi.appendEntry("status-card", { title: "Indexed files", count: 17 });
// Restore on reload
-step.on("session_start", async (_event, ctx) => {
+pi.on("session_start", async (_event, ctx) => {
for (const entry of ctx.sessionManager.getEntries()) {
if (entry.type === "custom" && entry.customType === "my-state") {
// Reconstruct from entry.data
@@ -1486,35 +1486,35 @@ step.on("session_start", async (_event, ctx) => {
});
```
-### step.setSessionName(name)
+### pi.setSessionName(name)
Set the session display name (shown in session selector instead of first message).
```typescript
-step.setSessionName("Refactor auth module");
+pi.setSessionName("Refactor auth module");
```
-### step.getSessionName()
+### pi.getSessionName()
Get the current session name, if set.
```typescript
-const name = step.getSessionName();
+const name = pi.getSessionName();
if (name) {
console.log(`Session: ${name}`);
}
```
-### step.setLabel(entryId, label)
+### pi.setLabel(entryId, label)
Set or clear a label on an entry. Labels are user-defined markers for bookmarking and navigation (shown in `/tree` selector).
```typescript
// Set a label
-step.setLabel(entryId, "checkpoint-before-refactor");
+pi.setLabel(entryId, "checkpoint-before-refactor");
// Clear a label
-step.setLabel(entryId, undefined);
+pi.setLabel(entryId, undefined);
// Read labels via sessionManager
const label = ctx.sessionManager.getLabel(entryId);
@@ -1522,14 +1522,14 @@ const label = ctx.sessionManager.getLabel(entryId);
Labels persist in the session and survive restarts. Use them to mark important points (turns, checkpoints) in the conversation tree.
-### step.registerCommand(name, options)
+### pi.registerCommand(name, options)
Register a command.
If multiple extensions register the same command name, step keeps them all and assigns numeric invocation suffixes in load order, for example `/review:1` and `/review:2`.
```typescript
-step.registerCommand("stats", {
+pi.registerCommand("stats", {
description: "Show session statistics",
handler: async (args, ctx) => {
const count = ctx.sessionManager.getEntries().length;
@@ -1543,7 +1543,7 @@ Optional: add argument auto-completion for `/command ...`:
```typescript
import type { AutocompleteItem } from "@step-harness/pi-tui";
-step.registerCommand("deploy", {
+pi.registerCommand("deploy", {
description: "Deploy to an environment",
getArgumentCompletions: (prefix: string): AutocompleteItem[] | null => {
const envs = ["dev", "staging", "prod"];
@@ -1557,18 +1557,18 @@ step.registerCommand("deploy", {
});
```
-### step.setUnavailableTools(names)
+### pi.setUnavailableTools(names)
Declare tool names unavailable for this extension's current runtime. This works during extension loading and after startup, and also applies to matching tools registered later.
```typescript
-export default function (step: ExtensionAPI) {
- step.setUnavailableTools(["remote_lookup"]);
+export default function (pi: ExtensionAPI) {
+ pi.setUnavailableTools(["remote_lookup"]);
- step.registerCommand("enable-remote-lookup", {
+ pi.registerCommand("enable-remote-lookup", {
handler: async () => {
// Clear this extension's declaration after its prerequisite is satisfied.
- step.setUnavailableTools([]);
+ pi.setUnavailableTools([]);
},
});
}
@@ -1580,13 +1580,13 @@ The requested active selection is retained while availability changes. Releasing
Use this for tools whose prerequisites or session policy make them unavailable. Tools that require conditional confirmation can stay available. Availability controls discovery and selection; keep execution-time permission checks in the tool or `tool_call` hook. A failed tool call does not automatically change availability.
-### step.getCommands()
+### pi.getCommands()
Get the slash commands available for invocation via `prompt` in the current session. Includes extension commands, prompt templates, and skill commands.
The list matches the RPC `get_commands` ordering: extensions first, then templates, then skills.
```typescript
-const commands = step.getCommands();
+const commands = pi.getCommands();
const bySource = commands.filter((command) => command.source === "extension");
const userScoped = commands.filter((command) => command.sourceInfo.scope === "user");
```
@@ -1613,11 +1613,11 @@ Use `sourceInfo` as the canonical provenance field. Do not infer ownership from
Built-in interactive commands (like `/model` and `/settings`) are not included here. They are handled only in interactive
mode and would not execute if sent via `prompt`.
-### step.registerMessageRenderer(customType, renderer)
+### pi.registerMessageRenderer(customType, renderer)
-Register a custom TUI renderer for custom messages with your `customType`. Custom messages are created with `step.sendMessage()` and participate in LLM context. See [Custom UI](#custom-ui).
+Register a custom TUI renderer for custom messages with your `customType`. Custom messages are created with `pi.sendMessage()` and participate in LLM context. See [Custom UI](#custom-ui).
-### step.registerMarkdownTransformer(transformer)
+### pi.registerMarkdownTransformer(transformer)
Register a transformer for the Markdown in normal user text, assistant text, and thinking blocks. Transformers run in extension load order, and each transformer receives the Markdown returned by the previous transformer. After the chain finishes, Step renders the transformed content with its built-in renderer.
@@ -1630,7 +1630,7 @@ The transformer receives the Markdown string and a context with:
Return the transformed Markdown:
```typescript
-step.registerMarkdownTransformer((markdown, { messageType, isStreaming }) => {
+pi.registerMarkdownTransformer((markdown, { messageType, isStreaming }) => {
if (isStreaming || messageType === "assistant-thinking") return markdown;
return markdown.replaceAll("-->", "→");
});
@@ -1638,14 +1638,14 @@ step.registerMarkdownTransformer((markdown, { messageType, isStreaming }) => {
If a transformer throws, Step keeps the Markdown produced so far and continues with the next transformer. The hook is display-only: the original message remains unchanged in the session and model context. It runs for new user messages, assistant streaming updates, restored session messages, and terminal width changes, so transformers should remain synchronous and inexpensive.
-### step.registerEntryRenderer(customType, renderer)
+### pi.registerEntryRenderer(customType, renderer)
-Register a custom TUI renderer for custom entries with your `customType`. Custom entries are created with `step.appendEntry()` and do not participate in LLM context.
+Register a custom TUI renderer for custom entries with your `customType`. Custom entries are created with `pi.appendEntry()` and do not participate in LLM context.
```typescript
import { Box, Text } from "@step-harness/pi-tui";
-step.registerEntryRenderer("status-card", (entry, { expanded }, theme) => {
+pi.registerEntryRenderer("status-card", (entry, { expanded }, theme) => {
const data = entry.data as { title: string; count: number };
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
box.addChild(new Text(`${theme.bold(data.title)}: ${data.count}`));
@@ -1655,15 +1655,15 @@ step.registerEntryRenderer("status-card", (entry, { expanded }, theme) => {
return box;
});
-step.appendEntry("status-card", { title: "Indexed files", count: 17 });
+pi.appendEntry("status-card", { title: "Indexed files", count: 17 });
```
-### step.registerShortcut(shortcut, options)
+### pi.registerShortcut(shortcut, options)
Register a keyboard shortcut. See [keybindings.md](keybindings.md) for the shortcut format and built-in keybindings.
```typescript
-step.registerShortcut("ctrl+shift+p", {
+pi.registerShortcut("ctrl+shift+p", {
description: "Toggle plan mode",
handler: async (ctx) => {
ctx.ui.notify("Toggled!");
@@ -1671,39 +1671,39 @@ step.registerShortcut("ctrl+shift+p", {
});
```
-### step.registerFlag(name, options)
+### pi.registerFlag(name, options)
Register a CLI flag.
```typescript
-step.registerFlag("plan", {
+pi.registerFlag("plan", {
description: "Start in plan mode",
type: "boolean",
default: false,
});
// Check value
-if (step.getFlag("plan")) {
+if (pi.getFlag("plan")) {
// Plan mode enabled
}
```
-### step.exec(command, args, options?)
+### pi.exec(command, args, options?)
Execute a shell command.
```typescript
-const result = await step.exec("git", ["status"], { signal, timeout: 5000 });
+const result = await pi.exec("git", ["status"], { signal, timeout: 5000 });
// result.stdout, result.stderr, result.code, result.killed
```
-### step.getActiveTools() / step.getAllTools() / step.setActiveTools(names)
+### pi.getActiveTools() / pi.getAllTools() / pi.setActiveTools(names)
-Manage active tools. This works for both built-in tools and dynamically registered tools. `step.getActiveTools()` returns the active tool names as `string[]`; `step.getAllTools()` returns metadata for configured tools that remain available after CLI selectors and extension availability declarations.
+Manage active tools. This works for both built-in tools and dynamically registered tools. `pi.getActiveTools()` returns the active tool names as `string[]`; `pi.getAllTools()` returns metadata for configured tools that remain available after CLI selectors and extension availability declarations.
```typescript
-const active = step.getActiveTools(); // ["read", "bash", ...]
-const all = step.getAllTools();
+const active = pi.getActiveTools(); // ["read", "bash", ...]
+const all = pi.getAllTools();
// all = [{
// name: "read",
// description: "Read file contents...",
@@ -1713,50 +1713,50 @@ const all = step.getAllTools();
// }, ...]
const builtinTools = all.filter((t) => t.sourceInfo.source === "builtin");
const extensionTools = all.filter((t) => t.sourceInfo.source !== "builtin" && t.sourceInfo.source !== "sdk");
-step.setActiveTools([...new Set([...active, "my_custom_tool"])]); // Keep current tools and enable my_custom_tool
-step.setActiveTools(["read", "bash"]); // Switch to read-only
+pi.setActiveTools([...new Set([...active, "my_custom_tool"])]); // Keep current tools and enable my_custom_tool
+pi.setActiveTools(["read", "bash"]); // Switch to read-only
```
-`step.getAllTools()` returns `name`, `description`, `parameters`, `promptGuidelines`, and `sourceInfo`.
+`pi.getAllTools()` returns `name`, `description`, `parameters`, `promptGuidelines`, and `sourceInfo`.
Typical `sourceInfo.source` values:
- `builtin` for built-in tools
- `sdk` for tools passed via `createAgentSession({ customTools })`
- extension source metadata for tools registered by extensions
-### step.setModel(model)
+### pi.setModel(model)
Set the current model. Returns `false` if no API key is available for the model. See [models.md](models.md) for configuring custom models.
```typescript
const model = ctx.modelRegistry.find("anthropic", "claude-sonnet-4-5");
if (model) {
- const success = await step.setModel(model);
+ const success = await pi.setModel(model);
if (!success) {
ctx.ui.notify("No API key for this model", "error");
}
}
```
-### step.getThinkingLevel() / step.setThinkingLevel(level)
+### pi.getThinkingLevel() / pi.setThinkingLevel(level)
Get or set the thinking level. Level is clamped to model capabilities (non-reasoning models always use "off"). Changes emit `thinking_level_select`.
```typescript
-const current = step.getThinkingLevel(); // "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"
-step.setThinkingLevel("high");
+const current = pi.getThinkingLevel(); // "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"
+pi.setThinkingLevel("high");
```
-### step.events
+### pi.events
Shared event bus for communication between extensions:
```typescript
-step.events.on("my:event", (data) => { ... });
-step.events.emit("my:event", { ... });
+pi.events.on("my:event", (data) => { ... });
+pi.events.emit("my:event", { ... });
```
-### step.registerProvider(name, config)
+### pi.registerProvider(name, config)
Register or override a model provider dynamically. Useful for proxies, custom endpoints, or team-wide model configurations.
@@ -1795,10 +1795,10 @@ const provider = createProvider({
api: openAICompletionsApi(),
});
-step.registerProvider(provider);
+pi.registerProvider(provider);
// Register a new provider with custom models
-step.registerProvider("my-proxy", {
+pi.registerProvider("my-proxy", {
name: "My Proxy",
baseUrl: "https://proxy.example.com",
apiKey: "$PROXY_API_KEY", // env var reference
@@ -1817,7 +1817,7 @@ step.registerProvider("my-proxy", {
});
// Register a live llama.cpp catalog without persisting discovered models
-step.registerProvider("llama.cpp", {
+pi.registerProvider("llama.cpp", {
baseUrl: "http://localhost:8080/v1",
apiKey: "local",
api: "openai-completions",
@@ -1837,12 +1837,12 @@ step.registerProvider("llama.cpp", {
});
// Override baseUrl for an existing provider (keeps all models)
-step.registerProvider("anthropic", {
+pi.registerProvider("anthropic", {
baseUrl: "https://proxy.example.com"
});
// Register provider with OAuth support for /login
-step.registerProvider("corporate-ai", {
+pi.registerProvider("corporate-ai", {
baseUrl: "https://ai.corp.com",
api: "openai-responses",
models: [...],
@@ -1882,17 +1882,17 @@ The object form accepts a complete `Provider`, including native `auth`, `getMode
See [custom-provider.md](custom-provider.md) for advanced topics: custom streaming APIs, OAuth details, model definition reference.
-### step.unregisterProvider(name)
+### pi.unregisterProvider(name)
Remove a previously registered provider and its models. Built-in models that were overridden by the provider are restored. Has no effect if the provider was not registered.
Like `registerProvider`, this takes effect immediately when called after the initial load phase, so a `/reload` is not required.
```typescript
-step.registerCommand("my-setup-teardown", {
+pi.registerCommand("my-setup-teardown", {
description: "Remove the custom proxy provider",
handler: async (_args, _ctx) => {
- step.unregisterProvider("my-proxy");
+ pi.unregisterProvider("my-proxy");
},
});
```
@@ -1902,11 +1902,11 @@ step.registerCommand("my-setup-teardown", {
Extensions with state should store it in tool result `details` for proper branching support:
```typescript
-export default function (step: ExtensionAPI) {
+export default function (pi: ExtensionAPI) {
let items: string[] = [];
// Reconstruct state from session
- step.on("session_start", async (_event, ctx) => {
+ pi.on("session_start", async (_event, ctx) => {
items = [];
for (const entry of ctx.sessionManager.getBranch()) {
if (entry.type === "message" && entry.message.role === "toolResult") {
@@ -1917,7 +1917,7 @@ export default function (step: ExtensionAPI) {
}
});
- step.registerTool({
+ pi.registerTool({
name: "my_tool",
// ...
async execute(toolCallId, params, signal, onUpdate, ctx) {
@@ -1933,11 +1933,11 @@ export default function (step: ExtensionAPI) {
## Custom Tools
-Register tools the LLM can call via `step.registerTool()`. Tools appear in the system prompt and can have custom rendering.
+Register tools the LLM can call via `pi.registerTool()`. Tools appear in the system prompt and can have custom rendering.
Use `promptSnippet` for a short one-line entry in the `Available tools` section in the default system prompt. If omitted, custom tools are left out of that section.
-Use `promptGuidelines` to add tool-specific bullets to the default system prompt `Guidelines` section. These bullets are included only while the tool is active (for example, after `step.setActiveTools([...])`).
+Use `promptGuidelines` to add tool-specific bullets to the default system prompt `Guidelines` section. These bullets are included only while the tool is active (for example, after `pi.setActiveTools([...])`).
**Important:** `promptGuidelines` bullets are appended flat to the `Guidelines` section with no tool name prefix or grouping. Each guideline must name the tool it refers to — avoid "Use this tool when..." because the LLM cannot tell which tool "this" means. Write "Use my_tool when..." instead.
@@ -1980,7 +1980,7 @@ import { Type } from "typebox";
import { StringEnum } from "@step-harness/providers";
import { Text } from "@step-harness/pi-tui";
-step.registerTool({
+pi.registerTool({
name: "my_tool",
label: "My Tool",
description: "What this tool does (shown to LLM)",
@@ -2013,8 +2013,8 @@ step.registerTool({
details: { progress: 50 },
});
- // Run commands via step.exec (captured from extension closure)
- const result = await step.exec("some-command", [], { signal });
+ // Run commands via pi.exec (captured from extension closure)
+ const result = await pi.exec("some-command", [], { signal });
// Return result
return {
@@ -2056,7 +2056,7 @@ async execute(toolCallId, params) {
Example: an older session may contain an `edit` tool call with top-level `oldText` and `newText`, while the current schema only accepts `edits: [{ oldText, newText }]`.
```typescript
-step.registerTool({
+pi.registerTool({
name: "edit",
label: "Edit",
description: "Edit a single file using exact text replacement",
@@ -2147,7 +2147,7 @@ const remoteRead = createReadTool(cwd, {
});
// Register, checking flag at execution time
-step.registerTool({
+pi.registerTool({
...remoteRead,
async execute(id, params, signal, onUpdate, _ctx) {
const ssh = getSshConfig();
@@ -2239,14 +2239,14 @@ See [examples/extensions/truncated-tool.ts](../examples/extensions/truncated-too
One extension can register multiple tools with shared state:
```typescript
-export default function (step: ExtensionAPI) {
+export default function (pi: ExtensionAPI) {
let connection = null;
- step.registerTool({ name: "db_connect", ... });
- step.registerTool({ name: "db_query", ... });
- step.registerTool({ name: "db_close", ... });
+ pi.registerTool({ name: "db_connect", ... });
+ pi.registerTool({ name: "db_query", ... });
+ pi.registerTool({ name: "db_close", ... });
- step.on("session_shutdown", async () => {
+ pi.on("session_shutdown", async () => {
connection?.close();
});
}
@@ -2261,7 +2261,7 @@ By default, tool output is wrapped in a `Box` that handles padding and backgroun
Set `renderShell: "self"` when the tool should render its own shell instead of using the default `Box`. This is useful for tools that need complete control over framing or background behavior, for example large previews that must stay visually stable after the tool settles.
```typescript
-step.registerTool({
+pi.registerTool({
name: "my_tool",
label: "My Tool",
description: "Custom shell example",
@@ -2379,19 +2379,19 @@ If a slot renderer is not defined or throws:
### Dynamic Tool Loading
-Extensions can register many tools while keeping only a small initial set active. A tool can then add more tools with `step.setActiveTools()` during execution. Step detects purely additive changes, records the newly available tool names on that tool result, and applies the updated active set before the next model request.
+Extensions can register many tools while keeping only a small initial set active. A tool can then add more tools with `pi.setActiveTools()` during execution. Step detects purely additive changes, records the newly available tool names on that tool result, and applies the updated active set before the next model request.
This works with every model. Models with native deferred-loading support preserve the stable prompt prefix and load the new definitions at the tool-result position. Other models use the fallback described below.
The lifecycle is:
-1. Register every tool with `step.registerTool()` so it appears in `step.getAllTools()`.
+1. Register every tool with `pi.registerTool()` so it appears in `pi.getAllTools()`.
2. Keep loader tools, such as `search_tools`, active and leave searchable tools inactive.
-3. During loader execution, call `step.setActiveTools([...currentTools, ...matchingTools])`. The change must be additive: do not remove currently active tools in the same call.
+3. During loader execution, call `pi.setActiveTools([...currentTools, ...matchingTools])`. The change must be additive: do not remove currently active tools in the same call.
4. Step records which tools were added on the loader's tool result.
5. Before the next model response, Step exposes the added definitions using native deferred loading when supported, or the normal active tool list otherwise.
-You do not need to return provider-specific tool references or mark the loader as a special search tool. The active-tool change is the signal. Names passed to `step.setActiveTools()` must already be registered; unknown names are ignored.
+You do not need to return provider-specific tool references or mark the loader as a special search tool. The active-tool change is the signal. Names passed to `pi.setActiveTools()` must already be registered; unknown names are ignored.
#### Models with native deferred loading
@@ -2422,8 +2422,8 @@ import { Type } from "typebox";
const SEARCHABLE_TOOL_NAMES = new Set(["lookup_weather", "search_issues"]);
-export default function (step: ExtensionAPI) {
- step.registerTool({
+export default function (pi: ExtensionAPI) {
+ pi.registerTool({
name: "lookup_weather",
label: "Lookup Weather",
description: "Look up the current weather for a city",
@@ -2436,7 +2436,7 @@ export default function (step: ExtensionAPI) {
},
});
- step.registerTool({
+ pi.registerTool({
name: "search_issues",
label: "Search Issues",
description: "Search project issues by keyword",
@@ -2449,7 +2449,7 @@ export default function (step: ExtensionAPI) {
},
});
- step.registerTool({
+ pi.registerTool({
name: "search_tools",
label: "Search Tools",
description: "Search for and enable tools relevant to a task",
@@ -2463,7 +2463,7 @@ export default function (step: ExtensionAPI) {
}),
async execute(_toolCallId, params) {
const terms = params.query.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
- const matches = step.getAllTools()
+ const matches = pi.getAllTools()
.filter((tool) => SEARCHABLE_TOOL_NAMES.has(tool.name))
.map((tool) => ({
tool,
@@ -2485,9 +2485,9 @@ export default function (step: ExtensionAPI) {
};
}
- const active = step.getActiveTools();
+ const active = pi.getActiveTools();
const added = matches.filter((name) => !active.includes(name));
- step.setActiveTools([...new Set([...active, ...added])]);
+ pi.setActiveTools([...new Set([...active, ...added])]);
return {
content: [{
@@ -2501,13 +2501,13 @@ export default function (step: ExtensionAPI) {
},
});
- step.on("session_start", () => {
+ pi.on("session_start", () => {
// Keep searchable tools registered but initially inactive. Preserve built-ins
// and tools owned by other extensions, and keep the loader itself active.
- const initialTools = step.getActiveTools().filter(
+ const initialTools = pi.getActiveTools().filter(
(name) => !SEARCHABLE_TOOL_NAMES.has(name),
);
- step.setActiveTools([...new Set([...initialTools, "search_tools"])]);
+ pi.setActiveTools([...new Set([...initialTools, "search_tools"])]);
});
}
```
@@ -2715,7 +2715,7 @@ Typical pattern:
- delegate `applyCompletion(...)` unless you need custom insertion behavior
```typescript
-step.on("session_start", (_event, ctx) => {
+pi.on("session_start", (_event, ctx) => {
ctx.ui.addAutocompleteProvider((current) => ({
triggerCharacters: ["#"],
async getSuggestions(lines, cursorLine, cursorCol, options) {
@@ -2837,8 +2837,8 @@ class VimEditor extends CustomEditor {
}
}
-export default function (step: ExtensionAPI) {
- step.on("session_start", (_event, ctx) => {
+export default function (pi: ExtensionAPI) {
+ pi.on("session_start", (_event, ctx) => {
ctx.ui.setEditorComponent((tui, theme, keybindings) =>
new VimEditor(tui, theme, keybindings)
);
@@ -2871,7 +2871,7 @@ Register a custom renderer for messages with your `customType`. Use message rend
```typescript
import { Text } from "@step-harness/pi-tui";
-step.registerMessageRenderer("my-extension", (message, options, theme) => {
+pi.registerMessageRenderer("my-extension", (message, options, theme) => {
const { expanded, outputPad } = options;
let text = theme.fg("accent", `[${message.customType}] `);
text += message.content;
@@ -2884,10 +2884,10 @@ step.registerMessageRenderer("my-extension", (message, options, theme) => {
});
```
-Messages are sent via `step.sendMessage()`:
+Messages are sent via `pi.sendMessage()`:
```typescript
-step.sendMessage({
+pi.sendMessage({
customType: "my-extension", // Matches registerMessageRenderer
content: "Status update",
display: true, // Show in TUI
@@ -2898,11 +2898,11 @@ step.sendMessage({
For TUI-only content that should not be sent to the LLM, render custom entries instead:
```typescript
-step.registerEntryRenderer("my-card", (entry, options, theme) => {
+pi.registerEntryRenderer("my-card", (entry, options, theme) => {
return new Text(theme.fg("accent", JSON.stringify(entry.data)));
});
-step.appendEntry("my-card", { status: "done" });
+pi.appendEntry("my-card", { status: "done" });
```
### Theme Colors
@@ -3030,7 +3030,7 @@ All examples in [examples/extensions/](../examples/extensions/).
| **Messages & Communication** |||
| `message-renderer.ts` | Custom message rendering | `registerMessageRenderer`, `sendMessage` |
| `entry-renderer.ts` | TUI-only custom entry rendering | `registerEntryRenderer`, `appendEntry` |
-| `event-bus.ts` | Inter-extension events | `step.events` |
+| `event-bus.ts` | Inter-extension events | `pi.events` |
| **Session Metadata** |||
| `session-name.ts` | Name sessions for selector | `setSessionName`, `getSessionName` |
| `bookmark.ts` | Bookmark entries for /tree | `setLabel` |
diff --git a/packages/coding-agent/docs/mcp-catalog.md b/packages/coding-agent/docs/mcp-catalog.md
index c73c9550..fd45bcee 100644
--- a/packages/coding-agent/docs/mcp-catalog.md
+++ b/packages/coding-agent/docs/mcp-catalog.md
@@ -15,7 +15,7 @@ A closed connection removes its tools and changes its status to failed. Session
Extensions can replace part of their own catalog with the existing batch API:
```ts
-step.registerTools(nextDefinitions, { remove: previousNames });
+pi.registerTools(nextDefinitions, { remove: previousNames });
```
Removal affects only the calling extension's registrations. Names supplied in both lists receive the new definition. The registry refreshes once after the batch, so callers see the finished replacement. Removing a winning extension definition can reveal another extension or builtin definition under the existing precedence rules. Empty batches and removal of names the caller does not own do not refresh the registry. Stale extension APIs reject the operation.
diff --git a/packages/coding-agent/docs/rpc.md b/packages/coding-agent/docs/rpc.md
index a1e69f56..303f08aa 100644
--- a/packages/coding-agent/docs/rpc.md
+++ b/packages/coding-agent/docs/rpc.md
@@ -64,7 +64,7 @@ With images:
If the agent is streaming and no `streamingBehavior` is specified, the command returns an error.
-**Extension commands**: If the message is an extension command (e.g., `/mycommand`), it executes immediately even during streaming. Extension commands manage their own LLM interaction via `step.sendMessage()`.
+**Extension commands**: If the message is an extension command (e.g., `/mycommand`), it executes immediately even during streaming. Extension commands manage their own LLM interaction via `pi.sendMessage()`.
**Input expansion**: Skill commands (`/skill:name`) and prompt templates (`/template`) are expanded before sending/queueing.
@@ -841,7 +841,7 @@ Each command has:
- `name`: Command name (invoke with `/name`)
- `description`: Human-readable description (optional for extension commands)
- `source`: What kind of command:
- - `"extension"`: Registered via `step.registerCommand()` in an extension
+ - `"extension"`: Registered via `pi.registerCommand()` in an extension
- `"prompt"`: Loaded from a prompt template `.md` file
- `"skill"`: Loaded from a skill directory (name is prefixed with `skill:`)
- `location`: Where it was loaded from (optional, not present for extensions):
diff --git a/packages/coding-agent/docs/sdk.md b/packages/coding-agent/docs/sdk.md
index ea3e509e..25567175 100644
--- a/packages/coding-agent/docs/sdk.md
+++ b/packages/coding-agent/docs/sdk.md
@@ -215,7 +215,7 @@ await session.prompt("After you're done, also check X", { streamingBehavior: "fo
```
**Behavior:**
-- **Extension commands** (e.g., `/mycommand`): Execute immediately, even during streaming. They manage their own LLM interaction via `step.sendMessage()`.
+- **Extension commands** (e.g., `/mycommand`): Execute immediately, even during streaming. They manage their own LLM interaction via `pi.sendMessage()`.
- **File-based prompt templates** (from `.md` files): Expanded to their content before sending or queueing.
- **During streaming without `streamingBehavior`**: Throws an error. Use `steer()` or `followUp()` directly, or specify the option.
- **`preflightResult(true)`**: Means the prompt was accepted, queued, or handled immediately.
@@ -604,9 +604,9 @@ const { session } = await createAgentSession({
});
```
-Use `defineTool()` for standalone definitions and arrays like `customTools: [myTool]`. Inline `step.registerTool({ ... })` already infers parameter types correctly.
+Use `defineTool()` for standalone definitions and arrays like `customTools: [myTool]`. Inline `pi.registerTool({ ... })` already infers parameter types correctly.
-Custom tools passed via `customTools` are combined with extension-registered tools. Extensions loaded by the ResourceLoader can also register tools via `step.registerTool()`.
+Custom tools passed via `customTools` are combined with extension-registered tools. Extensions loaded by the ResourceLoader can also register tools via `pi.registerTool()`.
If you pass `tools`, include each custom or extension tool name you want enabled, for example `tools: ["read", "bash", "my_tool"]`.
@@ -623,7 +623,7 @@ const loader = new DefaultResourceLoader({
additionalExtensionPaths: ["/path/to/my-extension.ts"],
extensionFactories: [
(step) => {
- step.on("agent_start", () => {
+ pi.on("agent_start", () => {
console.log("[Inline Extension] Agent starting");
});
},
@@ -644,7 +644,7 @@ import type { InlineExtension } from "@step-harness/coding-agent";
const myProvider: InlineExtension = {
name: "my-provider",
factory: (step) => {
- step.on("agent_start", () => {
+ pi.on("agent_start", () => {
console.log("[my-provider] Agent starting");
});
},
@@ -657,7 +657,7 @@ const loader = new DefaultResourceLoader({
This displays as `` instead of ``. Bare factory functions are still accepted for backward compatibility.
-**Event Bus:** Extensions can communicate via `step.events`. Pass a shared `eventBus` to `DefaultResourceLoader` if you need to emit or listen from outside:
+**Event Bus:** Extensions can communicate via `pi.events`. Pass a shared `eventBus` to `DefaultResourceLoader` if you need to emit or listen from outside:
```typescript
import { createEventBus, DefaultResourceLoader } from "@step-harness/coding-agent";
diff --git a/packages/coding-agent/docs/session-format.md b/packages/coding-agent/docs/session-format.md
index e2ac2563..2c1db3e2 100644
--- a/packages/coding-agent/docs/session-format.md
+++ b/packages/coding-agent/docs/session-format.md
@@ -270,7 +270,7 @@ Extension state persistence. Does NOT participate in LLM context.
{"type":"custom","id":"h8i9j0k1","parentId":"g7h8i9j0","timestamp":"2024-12-03T14:20:00.000Z","customType":"my-extension","data":{"count":42}}
```
-Use `customType` to identify your extension's entries on reload. Interactive mode can render custom entries via `step.registerEntryRenderer(customType, renderer)`, but they still do not participate in LLM context.
+Use `customType` to identify your extension's entries on reload. Interactive mode can render custom entries via `pi.registerEntryRenderer(customType, renderer)`, but they still do not participate in LLM context.
### CustomMessageEntry
@@ -297,7 +297,7 @@ Set `label` to `undefined` to clear a label.
### SessionInfoEntry
-Session metadata (e.g., user-defined display name). Set via `/name`, `--name` / `-n`, or `step.setSessionName()` in extensions.
+Session metadata (e.g., user-defined display name). Set via `/name`, `--name` / `-n`, or `pi.setSessionName()` in extensions.
```json
{"type":"session_info","id":"k1l2m3n4","parentId":"j0k1l2m3","timestamp":"2024-12-03T14:35:00.000Z","name":"Refactor auth module"}
diff --git a/packages/coding-agent/docs/tui.md b/packages/coding-agent/docs/tui.md
index 6a75b158..ae99521f 100644
--- a/packages/coding-agent/docs/tui.md
+++ b/packages/coding-agent/docs/tui.md
@@ -89,7 +89,7 @@ Without this propagation, typing with an IME (Chinese, Japanese, Korean, etc.) w
**In extensions** via `ctx.ui.custom()`:
```typescript
-step.on("session_start", async (_event, ctx) => {
+pi.on("session_start", async (_event, ctx) => {
const result = await ctx.ui.custom((tui, theme, keybindings, done) =>
new MyComponent({
theme,
@@ -385,7 +385,7 @@ class MySelector {
Usage in an extension:
```typescript
-step.registerCommand("pick", {
+pi.registerCommand("pick", {
description: "Pick an item",
handler: async (_args, ctx) => {
const items = ["Option A", "Option B", "Option C"];
@@ -614,7 +614,7 @@ import type { ExtensionAPI } from "@step-harness/coding-agent";
import { DynamicBorder } from "@step-harness/coding-agent";
import { Container, type SelectItem, SelectList, Text } from "@step-harness/pi-tui";
-step.registerCommand("pick", {
+pi.registerCommand("pick", {
handler: async (_args, ctx) => {
const items: SelectItem[] = [
{ value: "opt1", label: "Option 1", description: "First option" },
@@ -672,7 +672,7 @@ For operations that take time and should be cancellable. `BorderedLoader` shows
```typescript
import { BorderedLoader } from "@step-harness/coding-agent";
-step.registerCommand("fetch", {
+pi.registerCommand("fetch", {
handler: async (_args, ctx) => {
const result = await ctx.ui.custom((tui, theme, _kb, done) => {
const loader = new BorderedLoader(tui, theme, "Fetching data...");
@@ -705,7 +705,7 @@ For toggling multiple settings. Use `SettingsList` from `@step-harness/pi-tui` w
import { getSettingsListTheme } from "@step-harness/coding-agent";
import { Container, type SettingItem, SettingsList, Text } from "@step-harness/pi-tui";
-step.registerCommand("settings", {
+pi.registerCommand("settings", {
handler: async (_args, ctx) => {
const items: SettingItem[] = [
{ id: "verbose", label: "Verbose mode", currentValue: "off", values: ["on", "off"] },
@@ -894,8 +894,8 @@ class VimEditor extends CustomEditor {
}
}
-export default function (step: ExtensionAPI) {
- step.on("session_start", (_event, ctx) => {
+export default function (pi: ExtensionAPI) {
+ pi.on("session_start", (_event, ctx) => {
// Factory receives the TUI, theme, and keybindings from the app
ctx.ui.setEditorComponent((tui, theme, keybindings) =>
new VimEditor(tui, theme, keybindings)
diff --git a/packages/coding-agent/examples/extensions/README.md b/packages/coding-agent/examples/extensions/README.md
index 51b20a52..c615f225 100644
--- a/packages/coding-agent/examples/extensions/README.md
+++ b/packages/coding-agent/examples/extensions/README.md
@@ -61,7 +61,7 @@ cp permission-gate.ts ~/.stepcode/agent/extensions/
| `model-status.ts` | Shows model changes in status bar via `model_select` hook |
| `snake.ts` | Snake game with custom UI, keyboard handling, and session persistence |
| `tic-tac-toe.ts` | Tic-tac-toe vs the agent with `executionMode: "sequential"` tools to prevent race conditions on shared cursor state |
-| `send-user-message.ts` | Demonstrates `step.sendUserMessage()` for sending user messages from extensions |
+| `send-user-message.ts` | Demonstrates `pi.sendUserMessage()` for sending user messages from extensions |
| `timed-confirm.ts` | Demonstrates AbortSignal for auto-dismissing `ctx.ui.confirm()` and `ctx.ui.select()` dialogs |
| `rpc-demo.ts` | Exercises all RPC-supported extension UI methods; pair with [`examples/rpc-extension-ui.ts`](../rpc-extension-ui.ts) |
| `modal-editor.ts` | Custom vim-like modal editor via `ctx.ui.setEditorComponent()` |
@@ -113,7 +113,7 @@ cp permission-gate.ts ~/.stepcode/agent/extensions/
|-----------|-------------|
| `message-renderer.ts` | Custom message rendering with colors and expandable details via `registerMessageRenderer` |
| `entry-renderer.ts` | TUI-only session entry rendering via `appendEntry` and `registerEntryRenderer` |
-| `event-bus.ts` | Inter-extension communication via `step.events` |
+| `event-bus.ts` | Inter-extension communication via `pi.events` |
### Session Metadata
@@ -137,9 +137,9 @@ See [docs/extensions.md](../../docs/extensions.md) for full documentation.
import type { ExtensionAPI } from "@step-harness/coding-agent";
import { Type } from "typebox";
-export default function (step: ExtensionAPI) {
+export default function (pi: ExtensionAPI) {
// Subscribe to lifecycle events
- step.on("tool_call", async (event, ctx) => {
+ pi.on("tool_call", async (event, ctx) => {
if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
const ok = await ctx.ui.confirm("Dangerous!", "Allow rm -rf?");
if (!ok) return { block: true, reason: "Blocked by user" };
@@ -147,7 +147,7 @@ export default function (step: ExtensionAPI) {
});
// Register custom tools
- step.registerTool({
+ pi.registerTool({
name: "greet",
label: "Greeting",
description: "Generate a greeting",
@@ -163,7 +163,7 @@ export default function (step: ExtensionAPI) {
});
// Register commands
- step.registerCommand("hello", {
+ pi.registerCommand("hello", {
description: "Say hello",
handler: async (args, ctx) => {
ctx.ui.notify("Hello!", "info");
@@ -194,7 +194,7 @@ return {
};
// Reconstruct on session events
-step.on("session_start", async (_event, ctx) => {
+pi.on("session_start", async (_event, ctx) => {
for (const entry of ctx.sessionManager.getBranch()) {
if (entry.type === "message" && entry.message.toolName === "my_tool") {
const details = entry.message.details;
From 14ac5ff14f1237193e3f33a6d24c168e4353150e Mon Sep 17 00:00:00 2001
From: ZouR-Ma <2605315944@qq.com>
Date: Mon, 19 Oct 2026 19:57:50 +0800
Subject: [PATCH 4/5] docs: keep generated telemetry reference consistent with
generator
---
packages/agent-core/docs/telemetry-schema.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/agent-core/docs/telemetry-schema.md b/packages/agent-core/docs/telemetry-schema.md
index 4fde6c07..9eb8e6e4 100644
--- a/packages/agent-core/docs/telemetry-schema.md
+++ b/packages/agent-core/docs/telemetry-schema.md
@@ -1,4 +1,4 @@
-# Step Agent Telemetry Schemas
+# Pi Agent Telemetry Schemas
From 76f87d965b107e8fb08c86493d9d6a00e14ef981 Mon Sep 17 00:00:00 2001
From: ZouR-Ma <2605315944@qq.com>
Date: Mon, 19 Oct 2026 20:00:38 +0800
Subject: [PATCH 5/5] docs: generate Step Agent telemetry reference and assert
its title
---
packages/agent-core/docs/telemetry-schema.md | 2 +-
packages/agent-core/scripts/generate-telemetry-docs.ts | 2 +-
packages/agent-core/test/harness/telemetry.test.ts | 4 +++-
3 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/packages/agent-core/docs/telemetry-schema.md b/packages/agent-core/docs/telemetry-schema.md
index 9eb8e6e4..4fde6c07 100644
--- a/packages/agent-core/docs/telemetry-schema.md
+++ b/packages/agent-core/docs/telemetry-schema.md
@@ -1,4 +1,4 @@
-# Pi Agent Telemetry Schemas
+# Step Agent Telemetry Schemas
diff --git a/packages/agent-core/scripts/generate-telemetry-docs.ts b/packages/agent-core/scripts/generate-telemetry-docs.ts
index 31e733ce..a4d94915 100644
--- a/packages/agent-core/scripts/generate-telemetry-docs.ts
+++ b/packages/agent-core/scripts/generate-telemetry-docs.ts
@@ -83,7 +83,7 @@ function renderSchema(schema: TelemetrySchemaDefinition, title: string): string[
export function renderAgentTelemetrySchemaMarkdown(): string {
const lines = [
- "# Pi Agent Telemetry Schemas",
+ "# Step Agent Telemetry Schemas",
"",
"",
"",
diff --git a/packages/agent-core/test/harness/telemetry.test.ts b/packages/agent-core/test/harness/telemetry.test.ts
index 92faa063..b7265242 100644
--- a/packages/agent-core/test/harness/telemetry.test.ts
+++ b/packages/agent-core/test/harness/telemetry.test.ts
@@ -33,8 +33,10 @@ describe("agent telemetry schemas", () => {
"pi.harness.event_handler",
"pi.session.write",
]);
+ const expected = renderAgentTelemetrySchemaMarkdown();
+ expect(expected).toMatch(/^# Step Agent Telemetry Schemas\n/);
const actual = readFileSync(resolve(import.meta.dirname, "../../docs/telemetry-schema.md"), "utf8");
- expect(actual).toBe(renderAgentTelemetrySchemaMarkdown());
+ expect(actual).toBe(expected);
});
it("starts AI-request and harness spans through one composed typed starter", async () => {