Notes the new xAI Grok OAuth provider in /login, the XAI_OAUTH_TOKEN
env-var fallback, the four new optional fields on OpenAIResponsesOptions,
the generate_image xai branch, and the new tts tool.
Op: extend
Adds packages/coding-agent/src/tools/tts.ts: a CustomTool that POSTs to
https://api.x.ai/v1/tts using the shared xAI credentials helper
(supports both SuperGrok OAuth and plain XAI_API_KEY).
Built-in voices: ara, eve (default), leo, rex, sal. xAI also accepts
custom voice IDs (the schema does not enum-restrict voice_id). Output
codec inferred from output_path suffix (.wav → wav, else mp3). Max
15,000 characters per request. Composes the callers abort signal with
a 60s timeout fence.
Wired into sdk.ts immediately after the image-gen tool registration,
matching the await logger.time(...) pattern.
Ported from NousResearch/hermes-agent (MIT) — tools/tts_tool.py
L167-171 (constants) and L896-959 (_generate_xai_tts).
Op: extend
Adds packages/coding-agent/src/lib/xai-http.ts: a shared credential
resolver used by image generation (this commit) and TTS (next commit).
Tries the xai-oauth SuperGrok token first via
ModelRegistry.getApiKeyForProvider (refresh cascade lives there);
falls back to XAI_API_KEY. Ported from NousResearch/hermes-agent (MIT).
Extends imageGenTool with a "xai" provider branch that POSTs to
https://api.x.ai/v1/images/generations with the Grok Imagine surface:
grok-imagine-image (default, $0.02/image) or
grok-imagine-image-quality ($0.05/image). Aspect ratios 1:1, 16:9,
9:16, 4:3, 3:4, 3:2, 2:3. Resolutions 1k/2k. Decoded via the existing
saveImagesToTemp helper — no new image-handling code paths.
Op: extend
- Added support for multi-range line selectors on URLs (e.g., `:5-10,20-30`) and combining `:raw` mode with line range selectors.
- Added support for line range selectors on directory listings with offset and limit parameters.
- Fixed `:raw` selector being ignored for JSON and feed URLs and directory listing line selectors dropping offset parameter.
- Added clear error message for line offset beyond directory listing end.
- Refactored URL parsing and directory reading to support multiple comma-separated ranges and improved line-based slicing logic.
- Added comprehensive test coverage for multi-range selectors, raw mode combinations, and directory range operations.
- Replaced `LINE↑`/`LINE↓`/`A-B:` op sigils with unified `A-B:` anchor + `|`/`↑`/`↓` payload sigils.
- Added `mode: "replacement"` tag to insert edits so the applier distinguishes replace-bucket from insert-bucket lines.
- Removed lenient fallbacks (implicit continuation, inline payload acceptance, escaped delimiter stripping).
- Updated grammar, prompt, tokenizer, parser, applier, and messages to match the new format.
Reviewer caught that the new `legacy-pi-ai-shim.ts` is only referenced
via a computed string path, so Bun's `--compile` static analyzer cannot
trace it into bunfs. The same gap existed for the pre-existing
`typebox.ts` shim — `path.resolve(import.meta.dir, "../typebox.ts")`
collapses to `/$bunfs/typebox.ts` in compiled mode (where
`import.meta.dir` is `/$bunfs/root`), which does not exist in bunfs.
Legacy plugins importing bare `@sinclair/typebox` or `@(scope)/pi-ai`
therefore hit "Cannot find module" in release builds.
- Branch `TYPEBOX_SHIM_PATH` and `LEGACY_PI_AI_SHIM_PATH` on
`isCompiledBinary()`: in compiled mode they point at the
`--root`-relative bunfs entry path with a `.js` extension
(`/$bunfs/root/packages/coding-agent/src/extensibility/<file>.js`);
in dev they keep the `path.resolve(import.meta.dir, "../<file>.ts")`
source path so tests still resolve against the workspace tree.
- List both shims as additional `--compile` entrypoints in
`scripts/build-binary.ts` so Bun actually emits them into bunfs at the
paths the runtime expects.
Verified with a focused `bun build --compile` probe: `Bun.resolveSync`
returns the expected bunfs path for both shims when they are listed as
entries, and fails when they are not. Dev-mode test suite unchanged
(17 pass / 0 fail across the four legacy-pi compat test files).
Plannotator-class legacy extensions still import `Type` from
`@(scope)/pi-ai` (e.g. `@earendil-works/pi-ai` rewritten to
`@oh-my-pi/pi-ai`). pi-ai 15.1.0 removed the root `Type` runtime
export, so extension load crashed with `Export named 'Type' not found`
even though the `@sinclair/typebox` Zod-backed shim still ships in the
coding agent.
Routed bare `@oh-my-pi/pi-ai` root specifiers — used by both the
mirrored-source rewriter and the Bun.plugin onResolve hook — through a
new sibling shim that re-exports the canonical pi-ai surface plus the
`Type` runtime from the existing TypeBox shim. Subpath imports such as
`@oh-my-pi/pi-ai/utils/oauth` continue to resolve directly against the
bundled pi-ai package.
Fixes#1437
- Removed `path` field from hashline input parameters and function signatures across diff, execute, and params modules.
- Updated HashlinePatch.parse() calls to omit the path option, relying on the ¶PATH#HASH header in input instead.
- Removed unused warning tracking for escaped payload delimiters in hashline parser.
- Added detection and stripping of extra backslashes before indented payload rows, which models often emit when JSON-escaping the delimiter.
- Added `_input` field alias support in hashlineEditParamsSchema to accept provider-emitted variants.
- Added warning message for escaped payload delimiter acceptance to guide users toward canonical syntax.
- Modified scanInlineBody to strip the payload prefix when present at the start of inline content.
- Added test cases covering backslash-delimited payloads in insert, delete, and replace operations.
- Added `parsePatchStreaming` and `Executor.endStreaming` to handle in-flight ops gracefully during incremental parsing, dropping pending ops with no payload to avoid phantom edits.
- Introduced `PatchSection.applyPartialTo` as a streaming-aware counterpart to `applyTo`, using the new streaming parser for diff preview generation.
- Removed the custom `buildHashlineNaturalOrderPreviews` function and replaced it with calls to `applyPartialTo`, centralizing streaming logic in the parser layer.
- Changed output format to group results under `# /` headers to reduce token usage for shared path prefixes.
- Clamped the `limit` parameter to 1-200 (default 200) instead of the previous 1000.
- Updated tests to assert against raw file lists instead of parsed text output.
- Updated the payload continuation syntax to use backslash (`\`) instead of plus (`+`) as the explicit prefix for multi-line payloads and blank lines.
- Modified grammar, format constants, parser comments, and all documentation and test cases to reflect the new syntax.
- This is a breaking change for existing hashline patches using the `+` prefix.
- Imported isPromise from node:util/types in four modules.
- Replaced four instanceof Promise checks with isPromise calls for more reliable promise detection.
- Removed the `!` delete sigil and all associated parsing, validation, and tokenization logic. The delete operation is no longer a supported edit kind.
- Updated grammar, format constants, and error messages to reference only insert and replace operations.
- Simplified the executor's overlap validation to handle only replace operations.
- Parser now buffers markdown-style `#` lines and skips them when they directly precede a hashline operation.
- It now preserves comment lines that are not immediately before an operation while still handling blank separators as regular raw input.
- Added focused parser tests plus prompt and changelog updates describing the new comment-skipping behavior.
- Reduced default context lines from 4 to 2 in diff generation.
- Removed leading and trailing ellipsis placeholders from generated diff strings.
- Clarified the destructive nature of the replace operator and added examples in the prompt.
- Simplified `renderSection` output assembly in hashline execution to stop injecting a separate headline.
- Removed conditional headline generation that prefixed create/update/status text before the header.
- Returned tool results containing only header, preview, and warnings content blocks.
- Preflighted write policies for all sections before any commit in multi-section batches.
- Rejected duplicate canonical targets (e.g., `a.ts` and `./a.ts`) before writes begin.
- Fixed `after_anchor` normalization mutating cached edits across repeated patch applications.
- Fixed `detectLineEnding` to use first-occurrence style instead of majority vote.
Two TS errors in CI:
1. `agent-session.ts:1317` — `error TS1345: An expression of type 'void'
cannot be tested for truthiness`. The listener type is
`(event: AgentSessionEvent) => void`, so the returned value can't be
directly tested. Same shape in `agent.ts:1079`.
2. `test/session/emit-listener-isolation.test.ts:19` — the test fixture
for `AgentEvent.tool_execution_start` was missing the required `args`
field.
Cast the return to `unknown` and check `instanceof Promise` instead of
duck-typing `.then` — type-safe and matches what async functions actually
return. Add `args: {}` to the test fixture.
Rebase on main accidentally dropped these two code paths that exist
on the current main branch:
- autoApprove field in CreateAgentSessionOptions
- settingsInstance.override('tools.approvalMode', ...) for --approval-mode
Both are now restored to match upstream main.
createAgentSession() removed the hidden `resolve` tool from the registry
whenever no active tool advertised `deferrable: true`. Plan mode dispatches
its plan-approval `resolve { action: "apply", extra: { title } }` call
through a standing handler installed by InteractiveMode (no deferrable tool
involved), so read-only plan-mode toolsets (e.g. `read`, `search`, `find`,
`web_search`) silently activated plan mode without `resolve`. The agent had
no callable tool to submit the finalized plan and got stuck on the post-turn
tool-decision reminder.
Keep `resolve` registered whenever `plan.enabled` is true so the standing
handler always has a callable tool. The hidden flag still prevents `resolve`
from appearing in the active tool set until plan mode (or a deferrable tool's
preview action) opts in.
Fixes#1428
Root cause: Bun 1.3.x (JavaScriptCore) busy-waits when the only
pending work is an unresolved Promise. A setInterval keepalive
keeps the event loop in epoll_wait instead of userspace spinning.
- EventLoopKeepalive: setInterval-based keepalive (re-arms after each
firing, addressing the bot review concern about setTimeout expiry)
- keepaliveWhile(): wrapper to await a Promise with keepalive active
- Applied to getUserInput() in main.ts
- Retains yieldIfDue() and ExponentialYield from #1396
Idle CPU drops from ~100% to ~0% (wchan=do_epoll_wait).
Both `AgentSession.#emit` (session/agent-session.ts) and `Agent.#emit`
(packages/agent/src/agent.ts) iterated listeners with no error isolation.
A synchronous throw in any subscriber aborted the for-loop, so later
subscribers (TUI rendering, ACP bridge, task executor progress,
hindsight) silently missed events. Many listeners — see
`modes/controllers/event-controller.ts:141` and
`modes/controllers/input-controller.ts:576` — are registered as
`async (event) => { await this.handleEvent(event); }`; the returned
Promise was dropped, so any rejection became an unhandled rejection.
Wrap each listener invocation in try/catch and attach a `.catch` to any
returned thenable. Errors are logged via `logger.warn` (already imported
in agent-session.ts) and `console.error` (agent.ts has no logger
dependency, keep it that way).
Test: new `test/session/emit-listener-isolation.test.ts` registers two
listeners on both classes; first listener throws (or returns a rejecting
Promise); asserts the second listener still receives the event AND no
`unhandledRejection` fires. 4 cases (sync+async × Agent+AgentSession).
All fail on current main; all pass with the fix.
`getPackageDir()` walked up from `import.meta.dir` and fell back to
`getProjectDir()` (the user's `cwd`) when no `package.json` was located.
Inside `bun --compile` binaries `import.meta.dir` resolves to
`/$bunfs/root`, so the walk hit the filesystem root and `omp` ended up
reading the host project's `CHANGELOG.md` as its own — both in startup
"What's New" display and `/changelog`. Worse, parsing succeeded on any
`## [x.y.z]` heading, so `lastChangelogVersion` was persisted into
`~/.omp/agent/config.yml` based on the host project's file.
- Made `getPackageDir()` return `string | undefined`; removed the `cwd`
fallback so package-asset lookup never bleeds into the host project.
- Extracted the walk-up into a pure `walkUpForPackageDir(startDir)` so
the resolution contract is unit-testable from arbitrary directories.
- Made `getChangelogPath()` propagate `undefined` and taught
`parseChangelog` to accept it, returning `[]`. Existing callers
(`main.ts` startup, `/changelog` TUI handler, `/changelog` slash
command) already gate on empty entries, so the compiled-binary path
now skips changelog display cleanly without mutating settings.
- Added `test/issue-1423-repro.test.ts` covering the resolver contract,
the `parseChangelog(undefined)` path, the `PI_PACKAGE_DIR` override,
and the negative assertion that a host `## [99.0.0]` heading never
surfaces as an omp entry.
Fixes#1423
- Added a dedicated @oh-my-pi/hashline package with parser, patcher, filesystem, snapshots, and release metadata.
- Migrated coding-agent hashline and stream entrypoints to @oh-my-pi/hashline and removed old hashline module exports.
- Changed multi-section hashline execution to validate section hashes and flush diagnostics only at the final commit.
- Added session fileSnapshotStore support and rewired edit/read/search/write tools to use it instead of fileReadCache.
- Added `unfoldUntilLines`/`unfoldLimitLines` options to progressively reveal nested elidable spans breadth-first instead of collapsing everything behind the outermost elision.
- Added `minTotalLines` setting to skip summarization for short files, returning verbatim content instead.
- Added `:` selector support to `search` paths for constraining matches to specific line ranges.
- Extracted `parseLineRanges`/`parseLineRangeChunk`/`isLineInRanges` from `read.ts` into shared `path-utils.ts`.
- Removed inline_body from grammar; op sigils (↑, ↓, :) now accept no trailing content.
- Executor emits INLINE_PAYLOAD_ACCEPTED_WARNING when legacy inline form is encountered but still applies the edit leniently.
- Updated prompt docs and all tests to use bare op + `+`-prefixed continuation rows.
- Changed two test expectations from exact string match (toBe) to substring match (toContain) for the '(no output)' text.
- This allows tests to pass when the output contains additional content beyond the expected string.
- Added rule that inserts add only listed rows; trailing blank payload writes a literal empty line.
- Added rule that a bare anchor op inserts one blank line and is not a no-op.
- Clarified that frozen line numbers do not shift as earlier ops in the same hunk apply.
- Expanded anti-pattern example to show trailing-blank doubling and anchor-offset mistakes.
- Measured bash wall-clock duration for direct, terminal-bridge, and interactive execution paths.
- Recorded wall time in result notices and details, then stripped the duplicated literal notice during shell rendering.
- Updated the renderer to include wall time in the status label and added tests for the new wall-time behavior.
- Clarified the payload indentation rule to state that payload indentation is literal.
- Reworded the continuation-line guidance so a leading plus is treated as part of payload formatting.
- Updated the anti-pattern example to show a plus-prefixed continuation line in the wrong-pattern sample.
- Changed unprefixed continuation lines from a hard error to accepted implicit payload with a warning.
- Demoted inner `LINE:TEXT` ops whose anchors fall inside a pending `A-B:` block to payload continuation lines instead of raising an overlap error.
- Added `IMPLICIT_CONTINUATION_WARNING` and `PAYLOAD_LINE_PREFIX_DEMOTED_WARNING` constants for both new lenient paths.
- Changed multiline payload syntax so continuation lines must start with `+`; that prefix is stripped before writing.
- Raw unprefixed lines after an op now throw an error instead of being silently accepted as payload.
- Removed `PAYLOAD_LINE_PREFIX_DEMOTED_WARNING` and the nested-replace demotion path; inner `N:` ops inside a pending `A-B:` now raise an overlap error.
- Raw blank lines between ops are ignored; use `+` alone for an empty payload line.