diff --git a/AGENTS.md b/AGENTS.md index f3c30c7bd..3fb3504ac 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -164,7 +164,7 @@ To change an entry, fix the source: - **Generator-level fixups** (premium multipliers, codex pricing fallback, fallback models, post-processing) → `packages/catalog/scripts/generate-models.ts`. - **Thinking metadata / generated policies** → `packages/catalog/src/model-thinking.ts` (`applyGeneratedModelPolicies`); model-id classification (family/version parsing) lives in `packages/catalog/src/identity/classify.ts`. -Regenerate with `bun --cwd=packages/catalog run generate-models` and commit `models.json` alongside the source change. Add a regression test against the **resolver/descriptor**, not the bundled JSON, so it survives upstream metadata shifts. +Regenerate with `bun run gen:models` and commit `models.json` alongside the source change. Add a regression test against the **resolver/descriptor**, not the bundled JSON, so it survives upstream metadata shifts. ## Logging diff --git a/Dockerfile b/Dockerfile index 2f7f8ffb3..f98f81e4c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -186,7 +186,7 @@ COPY . /pi/ # Regenerate the docs index that `--ignore-scripts` skipped above. The root # package.json's `prepare` script normally handles this on a vanilla install. -RUN bun --cwd=packages/coding-agent run generate-docs-index +RUN bun --cwd=packages/coding-agent run gen:docs ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/omp"] CMD ["--help"] diff --git a/docs/natives-build-release-debugging.md b/docs/natives-build-release-debugging.md index cf8f547f4..7aca9d3d7 100644 --- a/docs/natives-build-release-debugging.md +++ b/docs/natives-build-release-debugging.md @@ -25,7 +25,7 @@ It follows the architecture terms from `docs/natives-architecture.md`: `packages/natives/package.json` scripts: - `bun scripts/build-native.ts` (`build`) → N-API build, addon install, generated declarations install, explicit ESM export and enum runtime patch. -- `bun scripts/embed-native.ts` (`embed:native`) → generate `native/embedded-addon.js` plus `native/embedded-addons..tar.gz` from built files. +- `bun scripts/embed-native.ts` (`gen:native`) → generate `native/embedded-addon.js` plus `native/embedded-addons..tar.gz` from built files. - `bun scripts/gen-npm-packages.ts` (`gen:npm`) → generate per-platform npm leaf packages (`@oh-my-pi/pi-natives--`, installed as optional dependencies of the core package) under `npm/` from built addon files. Root scripts include `build:native` as `bun --cwd=packages/natives run build`. @@ -201,7 +201,7 @@ Generated declarations currently include exports from these Rust modules: | x64 machine loads baseline when modern expected | `PI_NATIVE_VARIANT=baseline`, no AVX2 detected, or modern file unavailable | Check env and filenames in `native/` | Build modern variant (`TARGET_VARIANT=modern ... build`) and ship it | | Cross-build produces wrong-labeled binary | Mismatch between `CROSS_TARGET` and `TARGET_PLATFORM`/`TARGET_ARCH`, or missing x64 variant | Confirm env tuple and output filename | Re-run with consistent env values and explicit x64 `TARGET_VARIANT` | | Compiled binary fails after upgrade | Stale extracted cache, embedded archive mismatch, or embedded manifest version mismatch | Inspect `/` and loader error list | Delete versioned cache for the package version; regenerate embedded archive/manifest during packaging | -| `embed:native` fails with `No native addons found` | Required platform artifact was not built before embedding | Check expected list in error text | Build at least one expected artifact for the target, then rerun `embed:native` | +| `gen:native` fails with `No native addons found` | Required platform artifact was not built before embedding | Check expected list in error text | Build at least one expected artifact for the target, then rerun `gen:native` | ## Operational commands @@ -214,11 +214,11 @@ TARGET_VARIANT=modern bun --cwd=packages/natives run build TARGET_VARIANT=baseline bun --cwd=packages/natives run build # Generate embedded addon manifest from built native files -bun --cwd=packages/natives run embed:native +bun run gen:native # Output archive: packages/natives/native/embedded-addons.-.tar.gz # Reset embedded manifest to null stub -bun --cwd=packages/natives run embed:native -- --reset +bun run gen:native:reset ``` ## Orchestrator-side content-addressed build cache (robomp) diff --git a/docs/porting-from-pi-mono.md b/docs/porting-from-pi-mono.md index 4119d043e..427d47394 100644 --- a/docs/porting-from-pi-mono.md +++ b/docs/porting-from-pi-mono.md @@ -100,7 +100,7 @@ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "myapp-")); ## 5) Prefer Bun embeds (no copying) -Do not add new runtime asset copy steps. Keep assets in repo and prefer Bun embeds/imports; preserve existing explicit generation workflows such as `packages/coding-agent/src/export/html/tool-views.generated.js` (built from collab-web sources via `bun run build-tool-views`). +Do not add new runtime asset copy steps. Keep assets in repo and prefer Bun embeds/imports; preserve existing explicit generation workflows such as `packages/coding-agent/src/export/html/tool-views.generated.js` (built from collab-web sources via `bun run gen:tool-views`). - If upstream copies assets into a dist folder, replace with Bun-friendly embeds. - Prompts are static `.md` files; use Bun text imports (`with { type: "text" }`) and Handlebars instead of inline prompt strings. diff --git a/docs/session-operations-export-share-fork-resume.md b/docs/session-operations-export-share-fork-resume.md index b8b50a057..417827b1a 100644 --- a/docs/session-operations-export-share-fork-resume.md +++ b/docs/session-operations-export-share-fork-resume.md @@ -43,7 +43,7 @@ Behavior details: - `--copy`, `clipboard`, and `copy` arguments are explicitly rejected with a warning to use `/dump`. - Export embeds session header/entries/leaf plus current `systemPrompt` and tool descriptions from agent state. - Subagent transcripts stored next to the session file (`/.jsonl`, recursively for nested spawns) are embedded as `subSessions` (`collectSubSessions` in `src/export/html/index.ts`; disable with `includeSubSessions: false` in `ExportOptions`). In the page, agent ids in task tool cards open a breadcrumbed sub-session overlay. -- Tool calls render through the `` web component — the React per-tool renderers shared with collab-web (`packages/collab-web/src/tool-render/`), prebuilt into `src/export/html/tool-views.generated.js` by `bun --cwd=packages/collab-web run build:tool-views`. +- Tool calls render through the `` web component — the React per-tool renderers shared with collab-web (`packages/collab-web/src/tool-render/`), prebuilt into `src/export/html/tool-views.generated.js` by `bun run gen:tool-views`. - No session entries are appended during export. Caveat: diff --git a/package.json b/package.json index ef90771f6..dfbc9c811 100644 --- a/package.json +++ b/package.json @@ -171,13 +171,22 @@ "lint:py": "ruff check python && ruff format --check python", "fix:py": "ruff check --fix python && ruff format python", "prepublishOnly": "bun run check", - "prepare": "bun run build-tool-views", + "prepare": "bun run gen:tool-views", "publish": "bun run prepublishOnly && npm publish -ws --access public", "publish:dry": "bun run prepublishOnly && npm publish -ws --access public --dry-run", "release": "bun scripts/release.ts", - "generate-models": "bun --cwd=packages/catalog run generate-models", - "generate-docs-index": "bun --cwd=packages/coding-agent run generate-docs-index", - "build-tool-views": "bun --cwd=packages/collab-web run build:tool-views", + "gen:models": "bun --cwd=packages/catalog run gen:models", + "gen:stats": "bun --cwd=packages/stats run gen:stats", + "gen:stats:reset": "bun --cwd=packages/stats run gen:stats:reset", + "gen:docs": "bun --cwd=packages/coding-agent run gen:docs", + "gen:docs:reset": "bun --cwd=packages/coding-agent run gen:docs:reset", + "gen:changelog": "bun scripts/rewrite-changelog.ts", + "gen:tool-views": "bun --cwd=packages/collab-web run gen:tool-views", + "gen:bundle": "bun --cwd=packages/coding-agent run gen:bundle", + "gen:mupdf": "bun --cwd=packages/coding-agent run gen:mupdf", + "gen:mupdf:reset": "bun --cwd=packages/coding-agent run gen:mupdf:reset", + "gen:native": "bun --cwd=packages/natives run gen:native", + "gen:native:reset": "bun --cwd=packages/natives run gen:native:reset", "check-spoofed-versions": "bun scripts/check-spoofed-versions.ts" }, "devDependencies": { diff --git a/packages/agent/CHANGELOG.md b/packages/agent/CHANGELOG.md index de5e1776b..1cd733f08 100644 --- a/packages/agent/CHANGELOG.md +++ b/packages/agent/CHANGELOG.md @@ -4,14 +4,14 @@ ### Added -- Added an optional `cwdResolver` to `Agent` (and a `getCwd` per-call resolver on `AgentLoopConfig`) that is read once per LLM call to resolve the working directory, overriding the static `cwd` (falling back to it when the resolver returns `undefined`). Lets a host reflect a session move into provider options without reconstructing the agent — workspace-scoped provider discovery (e.g. GitLab Duo Agent namespace/project) now follows the live directory instead of the directory captured at construction. +- Added an optional `cwdResolver` to `Agent` and `getCwd` to `AgentLoopConfig` to dynamically resolve the working directory per LLM call, allowing workspace-scoped provider discovery (such as GitLab Duo Agent) to follow live directory changes without reconstructing the agent. ### Fixed -- Fixed API-level provider refusals being replayed as assistant dialogue on later requests, which could anchor repeated refusals after a single blocked turn. ([#3592](https://github.com/can1357/oh-my-pi/issues/3592)) -- Fixed `streamProxy` leaking internal `partialJson` streaming state onto the final `AssistantMessage` when the stream ended without a `toolcall_end` event. The field is now accumulated in a side-channel `Map` (eliminating `as any` casts on the accumulation path), written onto the content object via a typed `ToolCall & { partialJson: string }` intersection so downstream renderers can still read it during streaming, and scrubbed from all content blocks at `toolcall_end`, `done`, and `error` — guaranteeing it never appears on the final message. -- Fixed `Agent` forwarding the working directory (`cwd`) into provider stream options so the GitLab Duo Agent provider can scope local tool execution to the workspace. -- Allowed configured custom OpenAI-compatible providers to use native remote compaction instead of falling back to local summarization. ([#3104](https://github.com/can1357/oh-my-pi/issues/3104)) +- Fixed an issue where API-level provider refusals were replayed as assistant dialogue on subsequent requests, preventing repeated refusals after a single blocked turn. +- Fixed a bug where internal streaming state (`partialJson`) could leak onto the final `AssistantMessage` if a stream ended without a `toolcall_end` event. +- Fixed `Agent` to correctly forward the working directory (`cwd`) into provider stream options, enabling providers like GitLab Duo Agent to scope local tool execution to the workspace. +- Enabled custom OpenAI-compatible providers to use native remote compaction instead of falling back to local summarization. ## [16.1.23] - 2026-06-26 diff --git a/packages/agent/package.json b/packages/agent/package.json index bf1dd212d..179ae2ef3 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -1,77 +1,77 @@ { - "type": "module", - "name": "@oh-my-pi/pi-agent-core", - "version": "16.1.23", - "description": "General-purpose agent with transport abstraction, state management, and attachment support", - "homepage": "https://omp.sh", - "author": "Can Boluk", - "contributors": [ - "Mario Zechner" - ], - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/can1357/oh-my-pi.git", - "directory": "packages/agent" - }, - "bugs": { - "url": "https://github.com/can1357/oh-my-pi/issues" - }, - "keywords": [ - "ai", - "agent", - "llm", - "transport", - "state-management" - ], - "main": "./src/index.ts", - "types": "./src/index.ts", - "scripts": { - "check": "biome check . && bun run check:types", - "check:types": "tsgo -p tsconfig.json --noEmit", - "lint": "biome lint .", - "test": "bun test --parallel", - "fix": "biome check --write --unsafe .", - "fmt": "biome format --write ." - }, - "dependencies": { - "@oh-my-pi/pi-ai": "catalog:", - "@oh-my-pi/pi-catalog": "catalog:", - "@oh-my-pi/pi-natives": "catalog:", - "@oh-my-pi/pi-utils": "catalog:", - "@oh-my-pi/pi-wire": "catalog:", - "@oh-my-pi/snapcompact": "catalog:", - "@opentelemetry/api": "catalog:" - }, - "devDependencies": { - "@opentelemetry/context-async-hooks": "catalog:", - "@opentelemetry/sdk-trace-base": "catalog:", - "@types/bun": "catalog:" - }, - "engines": { - "bun": ">=1.3.14" - }, - "files": [ - "src", - "README.md", - "CHANGELOG.md" - ], - "exports": { - ".": { - "types": "./src/index.ts", - "import": "./src/index.ts" - }, - "./compaction": { - "types": "./src/compaction.ts", - "import": "./src/compaction.ts" - }, - "./compaction/*": { - "types": "./src/compaction/*.ts", - "import": "./src/compaction/*.ts" - }, - "./*": { - "types": "./src/*.ts", - "import": "./src/*.ts" - } - } + "type": "module", + "name": "@oh-my-pi/pi-agent-core", + "version": "16.1.23", + "description": "General-purpose agent with transport abstraction, state management, and attachment support", + "homepage": "https://omp.sh", + "author": "Can Boluk", + "contributors": [ + "Mario Zechner" + ], + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/can1357/oh-my-pi.git", + "directory": "packages/agent" + }, + "bugs": { + "url": "https://github.com/can1357/oh-my-pi/issues" + }, + "keywords": [ + "ai", + "agent", + "llm", + "transport", + "state-management" + ], + "main": "./src/index.ts", + "types": "./src/index.ts", + "scripts": { + "check": "biome check . && bun run check:types", + "check:types": "tsgo -p tsconfig.json --noEmit", + "lint": "biome lint .", + "test": "bun test --parallel", + "fix": "biome check --write --unsafe .", + "fmt": "biome format --write ." + }, + "dependencies": { + "@oh-my-pi/pi-ai": "catalog:", + "@oh-my-pi/pi-catalog": "catalog:", + "@oh-my-pi/pi-natives": "catalog:", + "@oh-my-pi/pi-utils": "catalog:", + "@oh-my-pi/pi-wire": "catalog:", + "@oh-my-pi/snapcompact": "catalog:", + "@opentelemetry/api": "catalog:" + }, + "devDependencies": { + "@opentelemetry/context-async-hooks": "catalog:", + "@opentelemetry/sdk-trace-base": "catalog:", + "@types/bun": "catalog:" + }, + "engines": { + "bun": ">=1.3.14" + }, + "files": [ + "src", + "README.md", + "CHANGELOG.md" + ], + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./src/index.ts" + }, + "./compaction": { + "types": "./src/compaction.ts", + "import": "./src/compaction.ts" + }, + "./compaction/*": { + "types": "./src/compaction/*.ts", + "import": "./src/compaction/*.ts" + }, + "./*": { + "types": "./src/*.ts", + "import": "./src/*.ts" + } + } } diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 9e17def00..7888cd279 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -4,77 +4,36 @@ ### Breaking Changes -- Removed the `@oh-my-pi/pi-ai/utils/json-parse` module. The JSON repair/parse helpers (`repairJson`, `parseJsonWithRepair`, `parseStreamingJson`, `parseStreamingJsonThrottled`) now live in `@oh-my-pi/pi-utils` so the SSE reader and other utilities can share one parser; import them from `@oh-my-pi/pi-utils`. +- Removed the `@oh-my-pi/pi-ai/utils/json-parse` module. The JSON repair and parsing helpers (`repairJson`, `parseJsonWithRepair`, `parseStreamingJson`, `parseStreamingJsonThrottled`) have been moved to `@oh-my-pi/pi-utils` to be shared across utilities. ### Added -- Added runaway detection for Gemini models to interrupt streams stuck in excessive planning steps -- Added a per-provider in-flight request limiter for LLM streams, shared across local OMP processes and configurable by callers with `maxInFlightRequests`. -- Added `credits` field (array of `UsageResetCreditDetail` with `grantedAt`, `expiresAt`, `status`) to `UsageResetCredits`, so display layers can show when banked rate-limit resets expire. The OpenAI Codex usage provider now calls `listCodexResetCredits` to populate individual credit details when `availableCount > 0` ([#3339](https://github.com/can1357/oh-my-pi/issues/3339)). -- Added the GitLab Duo Agent provider (id `gitlab-duo-agent`, API id `gitlab-duo-agent`), registry metadata, and the built-in implementation. The id mirrors GitLab's official "GitLab Duo Agent Platform" naming; the existing AI Gateway proxy provider is renamed to display "GitLab Duo Non-Agentic" (id `gitlab-duo` unchanged) to match GitLab's "Non-Agentic" classification. -- Added GitLab Duo Workflow provider protocol helpers, stream routing, WebSocket action response handling, and provider-level contract tests. -- Added GitLab Duo Workflow OAuth login using GitLab's official VS Code OAuth application, with paste-code instructions for the `vscode://gitlab.gitlab-workflow/authentication` callback. -- Added GitLab Duo Workflow project auto-discovery: the inline `ambient` flow requires a GitLab project server-side but OMP has no project of its own, so when no project is configured the provider discovers an accessible one (preferring a project under the resolved namespace group, then any membership project) and scopes `direct_access`, workflow creation, and WebSocket routing to it. -- Added GitLab Duo Workflow login-time namespace Duo settings enablement: before the first run each session the provider checks the resolved root namespace's Duo settings and, when any of `duo_agent_platform_enabled` / `duo_workflow_mcp_enabled` / `experiment_features_enabled` is off, sends a minimal group `PUT` to turn on exactly those three flags the inline MCP-only ambient flow requires. The enable is best-effort (a permission failure for non-owners/maintainers is logged and never blocks the run) and runs at most once per session. +- Added the GitLab Duo Agent provider (`gitlab-duo-agent`) and built-in implementation, renaming the existing AI Gateway proxy provider to "GitLab Duo Non-Agentic" (`gitlab-duo`). +- Added GitLab Duo Workflow provider support, featuring OAuth login via the official VS Code OAuth application, automatic project discovery, and automatic session-time namespace Duo settings enablement. +- Added runaway detection for Gemini models to interrupt streams stuck in excessive planning steps. +- Added a per-provider in-flight request limiter for LLM streams, shared across local OMP processes and configurable via `maxInFlightRequests`. +- Added a `credits` field to `UsageResetCredits` to display when banked rate-limit resets expire, with support for OpenAI Codex usage details. ### Changed -- Changed the GitLab Duo Agent rendered-`goal` soft overflow threshold from 1.25 MB to 1 MB (`1_048_576`), so a goal at or above 1 MB enters the jitter zone and an `There was an error` failure there is re-labeled as a context overflow to trigger auto-compaction earlier. The 1.25 MB ceiling almost never fired in practice; lowering it makes auto-compaction engage before the goal grows into the high-failure band. The hard (necessary-fail) threshold stays at 2 MB. -- Changed the GitLab Duo Agent `goal` transcript to drop bytes the model never reads: tool-call ids and tool-result ids are omitted (the inline ambient flow issues one tool call per turn and the result rides the very next turn, so call→result pair by adjacency, not by UUID), the OMP-internal per-call intent (`i`) field is stripped from replayed tool-call arguments, and the tool-call JSON is no longer `<`/`>`-escaped (the goal is plain transcript text, not an HTML/script context). On a real long session this trims roughly 7% of the rendered goal with no semantic change; live tool dispatch is unaffected because it never reads the replayed transcript. -- Changed GitLab Duo Agent to bound the rendered `goal` by two byte thresholds and treat oversized goals as context overflow so the session auto-compacts instead of hard-failing. Empirically the DWS/Workhorse transport has no fixed token wall but its failure probability climbs with the rendered-goal byte size (≤~1.25 MB basically always succeeds, ~1.4–1.7 MB is a jitter band, ≥~2 MB basically always fails, 4 MB is the DWS gRPC `MAX_MESSAGE_SIZE` hard cap). The soft threshold is the measured last-guaranteed-success ceiling (1.25 MB) and the hard threshold is the necessary-fail floor (2 MB): a goal in `[soft, hard)` is still attempted once (it can succeed) and only re-labeled as an `OVERFLOW_PATTERNS`-matching `prompt is too long: …` error if the run actually fails; a goal at or above the hard threshold is not sent at all — the provider proactively ends the stream with the overflow error (stopping the just-created server-side workflow) so no quota is spent on a near-certain failure. The goal body itself is never truncated, and a goal below the soft threshold that errors surfaces its raw server message verbatim so genuine transient faults are not misclassified. -- Changed GitLab Duo Agent to register OMP's MCP tools under their bare names (`read`, `bash`, …) instead of the `mcp__omp__`-prefixed form. The server does not strip prefixes — it binds the model's tool schema and matches incoming tool calls under the exact wire name (`sanitize_llm_name` only replaces illegal characters) — so a prefixed wire name only forced the model to learn `mcp__omp__read` while OMP's own tool docs refer to `read`, with no namespacing benefit. Registering the bare name aligns the name the model sees, the toolset key it is matched against, and OMP's docs. The inbound parser still strips a leading `mcp__omp__` defensively in case a model or server echoes a prefixed name. -- Changed the GitLab Duo Agent `goal` transcript to render replayed tool calls as past-tense `{args}` records and tool results as `` / ``, replacing the prior `{"name":…,"arguments":…}` / `` markers. The model was mimicking the old `{name,arguments}` shape as its own emittable text instead of using the structured tool-use channel, because the historical-record markers were byte-identical to a plausible live-call grammar. The new form reads as a past record (a call that already ran), drops the `{name,arguments}` wrapper (the tool name moves onto the tag, args ride the body), and omits the tool name from the result header (call→result pair by adjacency). This also trims ~48 bytes per call/result pair in the worked example. -- Changed the GitLab Duo Agent inline flow's system prompt to append a short history-note whenever the `goal` is a multi-turn ChatML transcript, telling the model the transcript's `<|im_start|>`/``/`` markers are a past record of already-executed turns and tool calls — not a syntax to emit — and to call tools only through its structured tool-use channel. Reframing the markers to past tense reduced but did not eliminate the model copying them as its own output; the explicit instruction closes the remaining gap. The note rides the system slot (not the goal), is appended only for multi-turn transcripts (a lone bare-text prompt has no markers), and lives in a static `.md` file imported as text. -- Changed GitLab Duo Workflow provider to run an inline custom `ambient` flow (`flowConfig` over the WebSocket, schema `v1`) instead of the built-in `chat` flow, with MCP-only agent privileges so GitLab native tools stay hidden and OMP MCP tools receive `runMCPTool` actions. The inline flow supplies OMP's own system prompt (no server-side jinja wrapper or GitLab project/namespace metadata reaches the agent) and opts into `on_agent_reasoning`, and uses the `DUO_AGENT_PLATFORM` unit primitive. -- Changed GitLab Duo Workflow start requests to restore GitLab's official routing/model metadata while leaving `additional_context` empty because custom client-context items can make namespace-only chat workflows open the WebSocket without replying. -- Changed GitLab Duo Workflow to carry OMP's system prompt in the inline flow's `prompt_template.system` slot and render the conversation as a flat ChatML transcript in the `goal` (no `` wrapper, no privileged ``). Every turn is equal-weight so a mid-task reminder or IRC wake no longer outranks the actual task, and each assistant turn replays its tool calls (`` name + args) paired with the next `tool` turn so the call→result chain stays intact. `additional_context` stays empty. -- Changed GitLab Duo Agent namespace auto-discovery to cache the discovered root namespace per account (a non-reversible fingerprint of the credential plus base URL) and reuse it as the first choice on every later turn and session, instead of re-running discovery on each request. The namespace is a function of the GitLab credential, not of the conversation/cwd, and the inline ambient flow never exposes it to the model, so a stable per-account cache is correct; a cached namespace is only re-discovered (once) if its dependent `direct_access`/workflow-create calls later fail. Explicit `rootNamespaceId`/`namespaceId`/project configuration (option or env) bypasses the cache and stays authoritative. -- Changed GitLab Duo Workflow tool-call streaming to finalize one assistant message per `runMCPTool` action and resume on the same WebSocket with that single tool result. The DWS inline ambient flow dispatches MCP tool calls serially — its `ToolNode` runs `for tool_call ...: await tool.ainvoke(...)`, and each MCP `ainvoke` blocks until the client returns the matching `actionResponse` before the next action is dispatched — so there is no parallel burst to merge. Each tool call is therefore its own assistant message (one `done`, one usage row), matching the server's actual one-action-per-turn wire behavior. +- Optimized GitLab Duo Agent and Workflow providers to use an inline custom "ambient" flow with MCP-only agent privileges, registering MCP tools under their bare names. +- Improved GitLab Duo Agent context management and auto-compaction by lowering the soft overflow threshold to 1 MB and stripping redundant bytes (such as tool-call UUIDs and escaped JSON) from the goal transcript. +- Enhanced GitLab Duo Agent prompt engineering to render replayed tool calls as past-tense records, reducing model confusion and preventing the model from mimicking historical markers. +- Added caching for discovered GitLab Duo Agent root namespaces per account to avoid redundant discovery requests. ### Fixed -- Fixed llama.cpp OpenAI-compatible capture follow-ups sending named forced `tool_choice` as an object; the chat-completions encoder now downgrades that shape to string `"required"` for llama.cpp so its parser no longer falls back with `type must be string, but is object`. ([#3593](https://github.com/can1357/oh-my-pi/issues/3593)) -- Fixed `omp usage` silently omitting Ollama and Ollama Cloud accounts by registering placeholder usage providers for both until an upstream quota endpoint is available. ([#3555](https://github.com/can1357/oh-my-pi/issues/3555)) -- Fixed Gemini reasoning-runaway detection to expose a dedicated thought-summary header guard for streams that keep emitting fresh planning titles without making a tool call, so higher layers can interrupt that shape without reusing the generic silent retry marker. -- Fixed GitLab Duo Workflow `direct_access` errors dropping the HTTP status when GitLab returned a JSON error body (e.g. a 401 `{"message":"Unauthorized"}` from an expired OAuth token, or a 429 quota body). The thrown error now embeds `HTTP ` alongside the body message so the streaming auth-retry path (`extractStatusFromAssistantError` → `extractHttpStatusFromError`) can recover the status and refresh/rotate the parked broker credential instead of surfacing a hard failure. -- Fixed `AuthStorage.login` always synthesizing a default manual-code paste prompt, which made the loopback `OAuthCallbackFlow` race a readline prompt against the HTTP callback for normal (non-paste-code) OAuth providers and could leave that prompt dangling — a dirty/blocked terminal — when the browser callback won. The default prompt is now synthesized only for `pasteCodeFlow` providers (`PASTE_CODE_LOGIN_PROVIDERS`); loopback providers get no manual-code race unless a caller explicitly supplies `onManualCodeInput`. This is the authoritative gate covering every caller (not just the auth-broker CLI). -- Fixed GitLab Duo Agent checkpoint deduplication swallowing a legitimate later agent message whose text equalled an earlier turn (e.g. two turns that both say "Done", or repeated reasoning). The content-signature fallback that suppresses replayed text across renamed `message_id`s is now scoped to the message's turn position (boundary count within the snapshot) instead of global text equality, so a replayed message reappearing at the same turn is still deduped while a genuinely new message at a later turn emits. -- Fixed GitLab Duo Agent re-issuing the same tool call in an infinite loop when a resumed inline-flow workflow stopped advancing server-side. The earlier stall guard compared the checkpoint `ui_chat_log` message count, but that count is an incremental-streaming slice window capped at ~2 even on a healthy `FINISHED` run, so it could not discriminate a real loop. Detection now compares the server checkpoint's byte length across consecutive tool-call boundaries of the same workflow: a healthy turn emits checkpoints whose size progresses, while a stalled workflow re-emits a byte-identical checkpoint. When two consecutive boundaries carry byte-identical checkpoints the provider settles `stalled` and restarts on a fresh workflow (bounded by `GITLAB_DUO_WORKFLOW_MAX_STALL_RESTARTS`) instead of running the doomed tool call; a single boundary is never falsely flagged. -- Fixed GitLab Duo Agent sending a full project path as the WebSocket `project_id` when `projectId`/`GITLAB_DUO_PROJECT_ID` was configured as a `group/project` path (a form namespace discovery already accepts). A path-valued `projectId` is now routed through the same numeric-id resolution as `projectPath`, so the socket receives the numeric project id project-scoped routing requires instead of the raw path string. -- Fixed GitLab Duo Agent dropping the resolved namespace/root namespace from the WebSocket route whenever no numeric project id was available (a configured project path that could not be resolved, or auto-discovery finding no project). The socket then opened with no scope and could route or fail outside the selected namespace; the namespace/root are now always sent regardless of whether a project id resolved. -- Fixed GitLab Duo Workflow `direct_access` failures to surface sanitized GitLab quota/auth details instead of a bare HTTP status. -- Fixed GitLab Duo Agent repeatedly re-calling the same tool until the user interrupts and re-sends a message. When a resumed turn held pending `runMCPTool` actions whose `requestID` could not be paired with a persisted tool result (id mismatch), the provider silently created a fresh workflow while leaving the previous one running server-side. The stranded workflow's LangGraph still treated the tool call as in-flight, so the model never saw the result and re-issued the same call in a loop; only a user interrupt broke it. The unresolvable-batch path now follows the same cleanup as a mid-batch steer — it stops the stranded workflow server-side and drops the resumable session before seeding the fresh workflow. -- Hardened GitLab Duo Agent action handling to require the server-assigned `requestID` on each `runMCPTool` action frame instead of synthesizing a random one when it appears absent. A fabricated non-empty id is silently discarded by the DWS executor outbox (it misses the awaiting-futures map), which would strand the tool call; DWS always assigns every action a non-empty `requestID`, so a genuinely missing id now fails the turn fast with diagnostics rather than stalling. -- Fixed GitLab Duo Agent treating the server's per-workflow step (graph-recursion) limit as a fatal error. A long but healthy OMP tool-call loop legitimately overruns the cap and surfaced as `FAILED`; the provider now transparently starts a fresh workflow that continues the same conversation (the accumulated context replays through the goal envelope) instead of failing the turn, bounded so a perpetually-overrunning task degrades to a graceful stop. Genuine `FAILED`/`STOPPED` statuses still surface as errors. -- Fixed GitLab Duo Workflow `direct_access` to send `root_namespace_id` in GitLab's GraphQL GID form, avoiding `404 Namespace Not Found` responses when OAuth credentials require canonical namespace metadata. -- Fixed GitLab Duo Workflow runtime namespace resolution so Workflow startup no longer requires `aiChatAvailableModels` to return a selectable model before sending the prompt. -- Fixed GitLab Duo Workflow create to use the discovered namespace path when no project is configured, avoiding `404 Namespace Not Found` responses with OAuth credentials. -- Fixed GitLab Duo Workflow project-path runs to resolve the numeric project id for WebSocket routing while keeping REST `direct_access` and workflow creation scoped to the original project path. -- Fixed GitLab Duo Workflow runtime model selection to honor GitLab `pinnedModel` metadata in both WebSocket routing and start request metadata when available. -- Fixed GitLab Duo Workflow runtime namespace selection to ignore stale model metadata and discover the namespace for the current OAuth credential unless an explicit namespace is configured. -- Fixed GitLab Duo Workflow action handlers so class-based OMP bridge methods keep their receiver when executing `runMCPTool` and native action callbacks. -- Fixed GitLab Duo Workflow action replay IDs so provider-driven `runMCPTool` results can be paired with synthetic assistant tool-call blocks instead of persisting standalone tool results after a stopped assistant. -- Fixed GitLab Duo Workflow checkpoint streaming to process `ui_chat_log` entries in order, preserving tool boundaries and per-entry agent deltas instead of only rendering the last agent message. -- Fixed GitLab Duo Workflow checkpoint streaming to map `ui_chat_log` agent entries tagged `message_sub_type: "reasoning"` (the inline flow's `on_agent_reasoning` pre-tool-call commentary) to thinking blocks, and other agent text to assistant text, matching the chain-of-thought the official Duo CLI surfaces. -- Fixed GitLab Duo Workflow checkpoint streaming to ignore same-key non-prefix checkpoint rewrites instead of appending the rewritten text as a duplicate continuation. -- Fixed GitLab Duo Workflow goal instructions to treat `workflowMetadata`, `additional_context`, `mcpTools`, preapprovals, namespace/project IDs, and `mcp__omp__*` names as GitLab transport metadata instead of user-visible OMP tool policy. -- Fixed GitLab Duo Workflow WebSocket transport errors to report sanitized event fields (`type`, `message`, `error`, `code`, `reason`) instead of a bare `[object ErrorEvent]`. -- Fixed GitLab Duo Workflow tracing so trace-file directory/append failures can never raise an unhandled rejection that crashes the host process. -- Fixed GitLab Duo Workflow server-side tool steps (GitLab native `gitlab_*` tools the agent runs without a client `runMCPTool` action) to emit a `pause_turn` stop at each checkpoint tool boundary, so multi-step server-side reasoning resumes on the same WebSocket and renders as separate independent assistant messages instead of one collapsed block. -- Fixed GitLab Duo Workflow usage reporting to map each checkpoint's per-agent `agent_context_usage` (`total_tokens`/`max_tokens`, preferring the `Chat Agent` then `context_builder` entry) onto the assistant message's `usage.input`/`totalTokens` so the per-message usage row reflects the real server-side context occupancy, without inflating output or cost. -- Fixed GitLab Duo Workflow to stop redacting credential-like substrings from goals, conversation history, and tool results before sending them to GitLab; content redaction is not a provider responsibility, and the over-broad marker was clobbering ordinary prose. Content is now forwarded verbatim. -- Fixed GitLab Duo Workflow runs hanging indefinitely when the WebSocket silently goes half-open (a proxy/LB drops the TCP link without delivering `close`/`error`); the socket now aborts after a 90s idle deadline and recovers by starting a FRESH workflow that replays the conversation through the goal transcript. Same-`workflowID` reconnect is not used because a second connection on an inline flow re-compiles the flow from the live `flowConfig` and the server's checkpoint replay rejects the rebuilt graph topology, so the recovery mirrors the step-limit restart. -- Fixed GitLab Duo Workflow leaving the remote workflow running and the resumable session pointing at a dead socket when a turn ends abnormally — either a user abort or the WebSocket rejecting (e.g. `onerror`). The stop `PATCH` previously got the request's already-aborted signal and only ran when the socket loop returned normally. It now runs from a `finally` path with a fresh signal whenever the turn aborts or the socket loop throws, and drops `providerSessionState.active` so the next turn does not reuse the closed socket. -- Fixed GitLab Duo Workflow dropping a paused session when a tool-result resume crosses a server-side tool boundary: the post-action resume now preserves `providerSessionState.active` on a `pause` result instead of clearing it, so the buffered continuation replays on the next turn instead of starting a new workflow. -- Fixed GitLab Duo Workflow resume turns hanging when a preserved socket, replayed for a tool result or a paused continuation, returned a non-terminal result (`closed`/`approval`/`timeout`) for which the socket emits no `done` event: both resume paths now share the fresh-workflow finalizer, which drops the resumable session and pushes a terminal `done` for every non-`action`/non-`pause`/non-`terminal` result so the assistant stream always closes instead of waiting forever. -- Fixed GitLab Duo Workflow turning normal streaming into a `pause_turn` per checkpoint: since GitLab checkpoints are full `ui_chat_log` snapshots, a later frame replays the earlier `request`/`tool` boundary before the new agent delta. Pause now fires only on a boundary that follows a delta emitted in the current checkpoint, so a stale replayed boundary no longer pauses. -- Fixed GitLab Duo Workflow dropping a user steer issued mid-tool-loop: when a new user/developer message lands after the pending batch's tool results, returning those results on the live socket would silently discard the steer. The provider now detects the mid-batch steer, stops the live workflow, and re-seeds a FRESH workflow whose goal transcript includes the steer so it reaches the model. -- Fixed GitLab Duo Workflow treating the server's de-identified catch-all `FAILED` as fatal. It wraps transient upstream faults (model 5xx that exhausted retries, AgentStuckError, etc.), so the provider now retries once on a FRESH workflow by replaying the conversation through the goal transcript, and only surfaces the error if the retry also fails. Genuine non-generic `FAILED`/`STOPPED` statuses still surface immediately. -- Fixed GitLab Duo Workflow API URL construction dropping a self-managed GitLab relative install base path: building request/WebSocket URLs with a leading-slash path against `https://host/gitlab` discarded `/gitlab`. `direct_access`, workflow creation, `aiChatAvailableModels`, project discovery/lookup, and the non-`serviceEndpoint` WebSocket URL now append onto the normalized base so the install path is preserved. +- Fixed various GitLab Duo Agent and Workflow stability issues, including infinite tool-call loops, connection hangs on half-open WebSockets, and unhandled step-limit or generic server-side failures. +- Improved GitLab Duo Workflow routing, namespace resolution, and project-path handling, ensuring correct numeric ID resolution and support for self-managed GitLab relative install base paths. +- Fixed GitLab Duo Workflow checkpoint streaming to correctly map reasoning entries to thinking blocks, preserve tool boundaries, and accurately report token usage. +- Fixed `AuthStorage.login` to only synthesize manual-code paste prompts for paste-code providers, preventing terminal-blocking races on loopback OAuth flows. +- Fixed llama.cpp compatibility by downgrading named forced `tool_choice` objects to the string `"required"` in the chat-completions encoder. +- Fixed `omp usage` omitting Ollama and Ollama Cloud accounts by registering placeholder usage providers. +- Fixed Gemini reasoning-runaway detection to expose a dedicated thought-summary header guard to interrupt streams stuck in planning loops. ### Removed -- Removed the GitLab Duo Workflow legacy `chat`/`software_development` flow paths and the non-MCP action bridge. The inline custom `ambient` flow (MCP-only) is now the sole code path, so the server-side flow-registry branch, the `chat`/`software_development` workflow definitions, and the action-to-OMP-tool mappers for native GitLab actions (plus the `GitLabHttpResponse` action-response shape) are gone; only `runMCPTool`/`run_mcp_tool` actions remain. +- Removed legacy GitLab Duo Workflow `chat` and `software_development` flow paths and the non-MCP action bridge in favor of the inline custom `ambient` flow. ## [16.1.23] - 2026-06-26 diff --git a/packages/ai/package.json b/packages/ai/package.json index 019782366..475fb7edf 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -1,134 +1,134 @@ { - "type": "module", - "name": "@oh-my-pi/pi-ai", - "version": "16.1.23", - "description": "Unified LLM API with automatic model discovery and provider configuration", - "homepage": "https://omp.sh", - "author": "Can Boluk", - "contributors": [ - "Mario Zechner" - ], - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/can1357/oh-my-pi.git", - "directory": "packages/ai" - }, - "bugs": { - "url": "https://github.com/can1357/oh-my-pi/issues" - }, - "keywords": [ - "ai", - "llm", - "openai", - "anthropic", - "gemini", - "unified", - "api" - ], - "main": "./src/index.ts", - "types": "./src/index.ts", - "scripts": { - "check": "biome check . && bun run check:types", - "check:types": "tsgo -p tsconfig.json --noEmit", - "lint": "biome lint .", - "test": "bun test --parallel", - "fix": "biome check --write --unsafe .", - "fmt": "biome format --write ." - }, - "dependencies": { - "@bufbuild/protobuf": "catalog:", - "@oh-my-pi/pi-catalog": "catalog:", - "@oh-my-pi/pi-utils": "catalog:", - "@oh-my-pi/pi-wire": "catalog:", - "arktype": "catalog:", - "zod": "catalog:" - }, - "devDependencies": { - "@bufbuild/protoc-gen-es": "catalog:", - "@types/bun": "catalog:" - }, - "engines": { - "bun": ">=1.3.14" - }, - "files": [ - "src", - "README.md", - "CHANGELOG.md" - ], - "exports": { - ".": { - "types": "./src/index.ts", - "import": "./src/index.ts" - }, - "./*": { - "types": "./src/*.ts", - "import": "./src/*.ts" - }, - "./auth-broker": { - "types": "./src/auth-broker/index.ts", - "import": "./src/auth-broker/index.ts" - }, - "./auth-broker/*": { - "types": "./src/auth-broker/*.ts", - "import": "./src/auth-broker/*.ts" - }, - "./auth-gateway": { - "types": "./src/auth-gateway/index.ts", - "import": "./src/auth-gateway/index.ts" - }, - "./auth-gateway/*": { - "types": "./src/auth-gateway/*.ts", - "import": "./src/auth-gateway/*.ts" - }, - "./providers/*": { - "types": "./src/providers/*.ts", - "import": "./src/providers/*.ts" - }, - "./providers/openai-codex/*": { - "types": "./src/providers/openai-codex/*.ts", - "import": "./src/providers/openai-codex/*.ts" - }, - "./usage/*": { - "types": "./src/usage/*.ts", - "import": "./src/usage/*.ts" - }, - "./utils/harmony-leak": { - "types": "./src/utils/harmony-leak.ts", - "import": "./src/utils/harmony-leak.ts" - }, - "./dialect": { - "types": "./src/dialect/index.ts", - "import": "./src/dialect/index.ts" - }, - "./utils/*": { - "types": "./src/utils/*.ts", - "import": "./src/utils/*.ts" - }, - "./oauth": { - "types": "./src/registry/oauth/index.ts", - "import": "./src/registry/oauth/index.ts" - }, - "./oauth/*": { - "types": "./src/registry/oauth/*.ts", - "import": "./src/registry/oauth/*.ts" - }, - "./registry": { - "types": "./src/registry/index.ts", - "import": "./src/registry/index.ts" - }, - "./registry/oauth": { - "types": "./src/registry/oauth/index.ts", - "import": "./src/registry/oauth/index.ts" - }, - "./utils/schema": { - "types": "./src/utils/schema/index.ts", - "import": "./src/utils/schema/index.ts" - }, - "./utils/schema/*": { - "types": "./src/utils/schema/*.ts", - "import": "./src/utils/schema/*.ts" - }, - "./*.js": "./src/*.ts" - } + "type": "module", + "name": "@oh-my-pi/pi-ai", + "version": "16.1.23", + "description": "Unified LLM API with automatic model discovery and provider configuration", + "homepage": "https://omp.sh", + "author": "Can Boluk", + "contributors": [ + "Mario Zechner" + ], + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/can1357/oh-my-pi.git", + "directory": "packages/ai" + }, + "bugs": { + "url": "https://github.com/can1357/oh-my-pi/issues" + }, + "keywords": [ + "ai", + "llm", + "openai", + "anthropic", + "gemini", + "unified", + "api" + ], + "main": "./src/index.ts", + "types": "./src/index.ts", + "scripts": { + "check": "biome check . && bun run check:types", + "check:types": "tsgo -p tsconfig.json --noEmit", + "lint": "biome lint .", + "test": "bun test --parallel", + "fix": "biome check --write --unsafe .", + "fmt": "biome format --write ." + }, + "dependencies": { + "@bufbuild/protobuf": "catalog:", + "@oh-my-pi/pi-catalog": "catalog:", + "@oh-my-pi/pi-utils": "catalog:", + "@oh-my-pi/pi-wire": "catalog:", + "arktype": "catalog:", + "zod": "catalog:" + }, + "devDependencies": { + "@bufbuild/protoc-gen-es": "catalog:", + "@types/bun": "catalog:" + }, + "engines": { + "bun": ">=1.3.14" + }, + "files": [ + "src", + "README.md", + "CHANGELOG.md" + ], + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./src/index.ts" + }, + "./*": { + "types": "./src/*.ts", + "import": "./src/*.ts" + }, + "./auth-broker": { + "types": "./src/auth-broker/index.ts", + "import": "./src/auth-broker/index.ts" + }, + "./auth-broker/*": { + "types": "./src/auth-broker/*.ts", + "import": "./src/auth-broker/*.ts" + }, + "./auth-gateway": { + "types": "./src/auth-gateway/index.ts", + "import": "./src/auth-gateway/index.ts" + }, + "./auth-gateway/*": { + "types": "./src/auth-gateway/*.ts", + "import": "./src/auth-gateway/*.ts" + }, + "./providers/*": { + "types": "./src/providers/*.ts", + "import": "./src/providers/*.ts" + }, + "./providers/openai-codex/*": { + "types": "./src/providers/openai-codex/*.ts", + "import": "./src/providers/openai-codex/*.ts" + }, + "./usage/*": { + "types": "./src/usage/*.ts", + "import": "./src/usage/*.ts" + }, + "./utils/harmony-leak": { + "types": "./src/utils/harmony-leak.ts", + "import": "./src/utils/harmony-leak.ts" + }, + "./dialect": { + "types": "./src/dialect/index.ts", + "import": "./src/dialect/index.ts" + }, + "./utils/*": { + "types": "./src/utils/*.ts", + "import": "./src/utils/*.ts" + }, + "./oauth": { + "types": "./src/registry/oauth/index.ts", + "import": "./src/registry/oauth/index.ts" + }, + "./oauth/*": { + "types": "./src/registry/oauth/*.ts", + "import": "./src/registry/oauth/*.ts" + }, + "./registry": { + "types": "./src/registry/index.ts", + "import": "./src/registry/index.ts" + }, + "./registry/oauth": { + "types": "./src/registry/oauth/index.ts", + "import": "./src/registry/oauth/index.ts" + }, + "./utils/schema": { + "types": "./src/utils/schema/index.ts", + "import": "./src/utils/schema/index.ts" + }, + "./utils/schema/*": { + "types": "./src/utils/schema/*.ts", + "import": "./src/utils/schema/*.ts" + }, + "./*.js": "./src/*.ts" + } } diff --git a/packages/ai/test/models-json-no-local-endpoints.test.ts b/packages/ai/test/models-json-no-local-endpoints.test.ts index 2d4e3e57b..b939c6aa2 100644 --- a/packages/ai/test/models-json-no-local-endpoints.test.ts +++ b/packages/ai/test/models-json-no-local-endpoints.test.ts @@ -14,7 +14,7 @@ import MODELS_JSON from "@oh-my-pi/pi-catalog/models.json" with { type: "json" } // DISCOVERY_ONLY_PROVIDERS in scripts/generate-models.ts. // // Failure here means: a local provider slipped into models.json — add it to -// DISCOVERY_ONLY_PROVIDERS, then `bun run generate-models` and commit the diff. +// DISCOVERY_ONLY_PROVIDERS, then `bun run gen:models` and commit the diff. describe("models.json local-endpoint leak guard (regression)", () => { const catalog = MODELS_JSON as unknown as Record>; diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index b08937026..39593faee 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -4,27 +4,19 @@ ### Added -- Added `OpenAICompat.supportsNamedToolChoice` so string-only OpenAI-compatible chat servers can keep forced tool use without emitting the named function-object `tool_choice` shape. ([#3593](https://github.com/can1357/oh-my-pi/issues/3593)) -- Added GitLab Duo Agent catalog discovery for `gitlab-duo-agent`, including namespace selection and live `aiChatAvailableModels` model mapping. -- Added model metadata for provider-native remote compaction and compaction-only model selection. ([#3104](https://github.com/can1357/oh-my-pi/issues/3104)) +- Added GitLab Duo Agent catalog discovery, including namespace selection, live model mapping, and a bundled fallback model for fresh installs. +- Added OpenAICompat.supportsNamedToolChoice to support forced tool use on string-only OpenAI-compatible chat servers without emitting the named function-object tool_choice shape. +- Added model metadata support for provider-native remote compaction and compaction-only model selection. ### Changed -- Changed GitLab Duo Agent model specs to `reasoning: false`. The Duo Agent Platform path exposes no client-controllable thinking knob (the underlying Anthropic model params are server-fixed), so OMP no longer shows a thinking-effort selector for these models. +- Disabled the thinking-effort selector for GitLab Duo Agent models since the underlying platform parameters are server-fixed. ### Fixed -- Fixed built-in LiteLLM discovery to prefer rich proxy metadata from LiteLLM management endpoints before falling back to /models, with a versioned cache namespace so stale bare-id cache rows do not hide capability data. -- Fixed the bundled catalog omitting the GitLab Duo Agent provider so a fresh install (before any credentialed dynamic discovery populates the cache) could not surface its default model. The generator now seeds the `gitlab-duo-agent` fallback model (`claude_sonnet_4_6_vertex`) into `models.json`, deduped behind live `aiChatAvailableModels` discovery when generation has credentials. -- Fixed GitLab Duo Agent namespace discovery only inspecting the first page of top-level groups, so a token belonging to more than 100 top-level groups could miss a usable Duo namespace on a later page and fail or select the wrong group. Discovery now follows GitLab's `x-next-page` pagination (bounded) and validates candidates across all pages. -- Fixed GitLab Duo Agent namespace discovery rejecting a workspace SSH remote whose port differs from the configured web `baseUrl` (self-managed GitLab commonly exposes SSH on a dedicated port, e.g. `ssh://git@host:2222/group/project.git` against `https://host`). SSH and SCP-style remotes now compare on the bare hostname; HTTP(S) remotes still compare host:port strictly so a different service on the same host is not adopted. -- Fixed GitLab Duo Workflow runtime namespace discovery so agent startup can resolve a root namespace without requiring live `aiChatAvailableModels` results. -- Fixed GitLab Duo Workflow runtime namespace discovery to preserve namespace paths for Workflow creation, including numeric/GID namespace overrides that must be resolved through GitLab group metadata. -- Fixed GitLab Duo Workflow project namespace discovery to fall back to the GraphQL `rootAncestor` query whenever a REST project payload exposes no explicit root (the normal payload only carries the immediate `namespace`), including numeric `projectId`/`GITLAB_DUO_PROJECT_ID` values: the fallback now keys off the project's `path_with_namespace` from the REST payload instead of being blocked by the missing slash, so a numeric id pinning a leaf subgroup project resolves the correct root namespace instead of falling through to remotes or top-level groups. -- Fixed GitLab Duo Workflow model specs to resolve a static `contextWindow` from the model ref family (Claude/Gemini → 1,000,000, default 200,000) instead of leaving it null, so OMP's context panel, usage percentage, and long-context auto-compaction work; GitLab exposes the real window only at runtime in each checkpoint's `agent_context_usage`, which the catalog ModelSpec cannot backfill. -- Fixed GitLab Duo Workflow catalog discovery ignoring `GITLAB_DUO_PROJECT_PATH`: namespace discovery now resolves the configured project from a `projectPath` config field and the `GITLAB_DUO_PROJECT_PATH` env var (in addition to `projectId`/`GITLAB_DUO_PROJECT_ID`), so workspaces that pin a project by path no longer fall through to the wrong group or fail before runtime project handling applies. -- Fixed GitLab Duo Workflow remote project discovery missing the current GitLab project in linked Git worktree checkouts: a worktree's `.git` points at `.git/worktrees/`, whose own `config` holds no remotes — those live in the common directory named by the gitdir's `commondir` file. Discovery now follows `commondir` to read the common `config`, so workspaces in a worktree resolve the correct namespace instead of falling back to top-level group candidates. -- Fixed GitLab Duo Workflow remote project discovery on self-managed GitLab installed under a relative path (e.g. `https://host/gitlab`): HTTPS remotes look like `https://host/gitlab/group/project.git` but project full paths stay `group/project`, so discovery previously queried `/api/v4/projects/gitlab%2Fgroup%2Fproject` and missed the project. The parser now strips the install base path from the remote before deriving the project full path. +- Improved GitLab Duo Agent and Duo Workflow namespace and project discovery to robustly handle paginated groups, SSH remotes with custom ports, Git worktrees, self-managed GitLab instances with relative paths, and configuration via GITLAB_DUO_PROJECT_PATH or GITLAB_DUO_PROJECT_ID. +- Fixed built-in LiteLLM discovery to prefer rich proxy metadata from management endpoints and avoid caching stale capability data. +- Fixed GitLab Duo Workflow model specifications to resolve correct static context windows, enabling accurate context usage tracking and auto-compaction. ## [16.1.23] - 2026-06-26 diff --git a/packages/catalog/README.md b/packages/catalog/README.md index e5ca2be31..93477f3e9 100644 --- a/packages/catalog/README.md +++ b/packages/catalog/README.md @@ -24,7 +24,7 @@ Import from subpaths (`@oh-my-pi/pi-catalog/`) or the root barrel. Never edit `src/models.json` by hand — it is produced from upstream sources (models.dev, provider catalog discovery, OpenCode docs) by `scripts/generate-models.ts` and the resolvers in `src/provider-models/`. Regenerate with: ```sh -bun --cwd=packages/catalog run generate-models +bun run gen:models ``` To change an entry, fix the source: resolver overrides in `provider-models/openai-compat.ts`, provider entries in `provider-models/descriptors.ts`, generator fixups in `scripts/generate-models.ts`, or thinking policies in `model-thinking.ts`. diff --git a/packages/catalog/package.json b/packages/catalog/package.json index e7a733a78..996018244 100644 --- a/packages/catalog/package.json +++ b/packages/catalog/package.json @@ -1,100 +1,100 @@ { - "type": "module", - "name": "@oh-my-pi/pi-catalog", - "version": "16.1.23", - "description": "Model catalog for omp: bundled model database, provider discovery descriptors, model identity, classification, and equivalence", - "homepage": "https://omp.sh", - "author": "Can Boluk", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/can1357/oh-my-pi.git", - "directory": "packages/catalog" - }, - "bugs": { - "url": "https://github.com/can1357/oh-my-pi/issues" - }, - "keywords": [ - "ai", - "llm", - "models", - "catalog", - "discovery" - ], - "main": "./src/index.ts", - "types": "./src/index.ts", - "scripts": { - "check": "biome check . && bun run check:types", - "check:types": "tsgo -p tsconfig.json --noEmit", - "lint": "biome lint .", - "test": "bun test --parallel", - "fix": "biome check --write --unsafe .", - "fmt": "biome format --write .", - "generate-models": "bun scripts/generate-models.ts" - }, - "dependencies": { - "@bufbuild/protobuf": "catalog:", - "@oh-my-pi/pi-utils": "catalog:", - "arktype": "catalog:", - "zod": "catalog:" - }, - "devDependencies": { - "@oh-my-pi/pi-ai": "catalog:", - "@types/bun": "catalog:" - }, - "engines": { - "bun": ">=1.3.14" - }, - "files": [ - "src", - "README.md", - "CHANGELOG.md" - ], - "exports": { - ".": { - "types": "./src/index.ts", - "import": "./src/index.ts" - }, - "./models.json": { - "types": "./src/models.json.d.ts", - "import": "./src/models.json" - }, - "./provider-models": { - "types": "./src/provider-models/index.ts", - "import": "./src/provider-models/index.ts" - }, - "./provider-models/*": { - "types": "./src/provider-models/*.ts", - "import": "./src/provider-models/*.ts" - }, - "./discovery": { - "types": "./src/discovery/index.ts", - "import": "./src/discovery/index.ts" - }, - "./discovery/*": { - "types": "./src/discovery/*.ts", - "import": "./src/discovery/*.ts" - }, - "./identity": { - "types": "./src/identity/index.ts", - "import": "./src/identity/index.ts" - }, - "./identity/*": { - "types": "./src/identity/*.ts", - "import": "./src/identity/*.ts" - }, - "./wire/*": { - "types": "./src/wire/*.ts", - "import": "./src/wire/*.ts" - }, - "./compat/*": { - "types": "./src/compat/*.ts", - "import": "./src/compat/*.ts" - }, - "./*": { - "types": "./src/*.ts", - "import": "./src/*.ts" - }, - "./*.js": "./src/*.ts" - } + "type": "module", + "name": "@oh-my-pi/pi-catalog", + "version": "16.1.23", + "description": "Model catalog for omp: bundled model database, provider discovery descriptors, model identity, classification, and equivalence", + "homepage": "https://omp.sh", + "author": "Can Boluk", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/can1357/oh-my-pi.git", + "directory": "packages/catalog" + }, + "bugs": { + "url": "https://github.com/can1357/oh-my-pi/issues" + }, + "keywords": [ + "ai", + "llm", + "models", + "catalog", + "discovery" + ], + "main": "./src/index.ts", + "types": "./src/index.ts", + "scripts": { + "check": "biome check . && bun run check:types", + "check:types": "tsgo -p tsconfig.json --noEmit", + "lint": "biome lint .", + "test": "bun test --parallel", + "fix": "biome check --write --unsafe .", + "fmt": "biome format --write .", + "gen:models": "bun scripts/generate-models.ts" + }, + "dependencies": { + "@bufbuild/protobuf": "catalog:", + "@oh-my-pi/pi-utils": "catalog:", + "arktype": "catalog:", + "zod": "catalog:" + }, + "devDependencies": { + "@oh-my-pi/pi-ai": "catalog:", + "@types/bun": "catalog:" + }, + "engines": { + "bun": ">=1.3.14" + }, + "files": [ + "src", + "README.md", + "CHANGELOG.md" + ], + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./src/index.ts" + }, + "./models.json": { + "types": "./src/models.json.d.ts", + "import": "./src/models.json" + }, + "./provider-models": { + "types": "./src/provider-models/index.ts", + "import": "./src/provider-models/index.ts" + }, + "./provider-models/*": { + "types": "./src/provider-models/*.ts", + "import": "./src/provider-models/*.ts" + }, + "./discovery": { + "types": "./src/discovery/index.ts", + "import": "./src/discovery/index.ts" + }, + "./discovery/*": { + "types": "./src/discovery/*.ts", + "import": "./src/discovery/*.ts" + }, + "./identity": { + "types": "./src/identity/index.ts", + "import": "./src/identity/index.ts" + }, + "./identity/*": { + "types": "./src/identity/*.ts", + "import": "./src/identity/*.ts" + }, + "./wire/*": { + "types": "./src/wire/*.ts", + "import": "./src/wire/*.ts" + }, + "./compat/*": { + "types": "./src/compat/*.ts", + "import": "./src/compat/*.ts" + }, + "./*": { + "types": "./src/*.ts", + "import": "./src/*.ts" + }, + "./*.js": "./src/*.ts" + } } diff --git a/packages/catalog/src/provider-models/descriptor-types.ts b/packages/catalog/src/provider-models/descriptor-types.ts index 97bdb0a41..042213319 100644 --- a/packages/catalog/src/provider-models/descriptor-types.ts +++ b/packages/catalog/src/provider-models/descriptor-types.ts @@ -55,7 +55,7 @@ export function allowsUnauthenticatedCatalogDiscovery(descriptor: CatalogProvide * - Every entry is a member of `KnownProvider`. * - `createModelManagerOptions` present (and not `specialModelManager`) ⇒ * appears in `PROVIDER_DESCRIPTORS` for runtime model discovery. - * - `catalogDiscovery` present ⇒ participates in `generate-models.ts`. + * - \`catalogDiscovery\` present ⇒ participates in \`generate-models.ts\`. */ export interface ProviderCatalogEntry { readonly id: string; diff --git a/packages/catalog/src/provider-models/openai-compat.ts b/packages/catalog/src/provider-models/openai-compat.ts index aa02939d8..12776ea48 100644 --- a/packages/catalog/src/provider-models/openai-compat.ts +++ b/packages/catalog/src/provider-models/openai-compat.ts @@ -1066,7 +1066,7 @@ function applyXAIOAuthCuration(dynamic: readonly ModelSpec<"openai-responses">[] * Single source of truth for the curated to Model fan-in, consumed by both * - {@link xaiOAuthModelManagerOptions} (runtime static seed handed to the model * manager so the picker is populated on a fresh login), and - * - `packages/ai/scripts/generate-models.ts` (bundles the same entries into + * - \`packages/catalog/scripts/generate-models.ts\` (bundles the same entries into * `models.json`, so the synchronous `ModelRegistry.#loadModels()` boot path * sees `xai-oauth` without waiting for a refresh — fixes the boot-time * default-model reset when `modelRoles.default = "xai-oauth/"`). @@ -1115,7 +1115,7 @@ export function xaiOAuthModelManagerOptions( // Static seed handed to the runtime model manager so the picker populates on // a fresh login even before `fetchDynamicModels` fires (it is gated on // `config.apiKey` at construction time, and OAuth tokens resolve later via - // AuthStorage). `generate-models.ts` calls the same builder so `models.json` + // AuthStorage). \`generate-models.ts\` calls the same builder so \`models.json\` // carries these entries too — making the synchronous `#loadModels()` boot // path honor `modelRoles.default = "xai-oauth/"` without `await refresh()`. const staticModels = buildXaiOAuthStaticSeed(resolvedBaseUrl); diff --git a/packages/catalog/test/gitlab-duo-workflow-discovery.test.ts b/packages/catalog/test/gitlab-duo-workflow-discovery.test.ts index 895052bd3..8a586ac8a 100644 --- a/packages/catalog/test/gitlab-duo-workflow-discovery.test.ts +++ b/packages/catalog/test/gitlab-duo-workflow-discovery.test.ts @@ -531,7 +531,7 @@ describe("GitLab Duo Workflow discovery", () => { it("keeps the gitlab-duo-agent descriptor out of catalog generation discovery", () => { // The descriptor must NOT carry `catalogDiscovery`: that field is the sole gate // for the generator's discovery loop (`isCatalogDescriptor`). Were it present, - // `generate-models` running on a machine with GitLab credentials would fetch the + // \`gen:models\` running on a machine with GitLab credentials would fetch the // account's namespace-scoped `aiChatAvailableModels` and bundle one private // namespace's pinned/selectable catalog into models.json as authoritative for // every fresh install. Only the generic, namespace-free fallback may be bundled; diff --git a/packages/catalog/test/xai-oauth-bundle.test.ts b/packages/catalog/test/xai-oauth-bundle.test.ts index 24a59691c..d86ea88dc 100644 --- a/packages/catalog/test/xai-oauth-bundle.test.ts +++ b/packages/catalog/test/xai-oauth-bundle.test.ts @@ -11,7 +11,7 @@ import type { ModelSpec } from "@oh-my-pi/pi-catalog/types"; // `refresh()`, but interactive boot resolves the persisted default // synchronously from `#loadModels()`, which reads only `models.json`. // -// Failure here means: run `bun run generate-models` and commit the diff. +// Failure here means: run `bun run gen:models` and commit the diff. describe("xai-oauth bundled catalog (regression)", () => { const bundled = (MODELS_JSON as unknown as Record>>)["xai-oauth"] ?? {}; @@ -52,7 +52,7 @@ describe("xai-oauth bundled catalog (regression)", () => { expect(composer!.contextWindow).toBe(200_000); expect(composer!.input).toEqual(["text"]); // The bundled models.json entry is byte-identical to the generator's - // deterministic xai-oauth output: generate-models.ts pushes + // deterministic xai-oauth output: gen:models pushes // buildXaiOAuthStaticSeed() (offline — xai-oauth has no upstream catalog // source) and applyGeneratedModelPolicies(), so a regen reproduces these // exact bytes; only unrelated other-provider network churn was excluded diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 09a5c7347..b331f9817 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,71 +2,47 @@ ## [Unreleased] +### Breaking Changes + +- Renamed the `search` tool to `grep` and the `find` tool to `glob`. Existing user settings are automatically migrated to the new configuration keys. + ### Added -- Added Loop Guard "Tool-Call Reminder" to automatically interrupt Gemini reasoning loops that generate excessive planning headers without acting -- Added support for file deletion and moving within file editing operations -- Added `providers.maxInFlightRequests` to cap concurrent LLM requests per provider across local OMP processes from settings. -- The `/usage` display (TUI, ACP text, and `omp usage` CLI) now shows expiry dates for banked Codex rate-limit resets, so users can plan when to redeem them before they expire ([#3339](https://github.com/can1357/oh-my-pi/issues/3339)). -- Added `ssh://host/path` support to `read`, `search`, and `write` for single text files on pre-configured SSH hosts (or `~/.ssh/config` aliases); POSIX remotes only (Linux/macOS/BSD; Windows hosts are rejected — use the `ssh` tool), UTF-8 text only, up to 1 MiB. Requires the same approval as the `ssh` tool, rejects argument-injecting hosts/usernames (leading `-`), validates the entire file as UTF-8, peels read selectors so `write` targets the same file `read` does, and replaces (rather than writes through) a non-directory symlinked write destination via a uniquely named remote temp. `read` also lists an `ssh://` directory one level deep (dotfiles included, directories first; `ssh://host/` lists the remote root), while `search` refuses an `ssh://` directory and `write` refuses to overwrite one. Bare `ssh://` lists the configured hosts, which are also offered as autocompletions after typing `ssh://`. -- Added an interactive `/move` overlay: typing `/move` with no argument opens a path autocomplete picker (type to filter, ↑↓ to navigate, Tab to accept, Enter to confirm). `/move ` still works for direct invocation. The command now starts a fresh empty session in the target directory instead of relocating the current session file, leaving the previous session resumable via `/resume`. If the target directory does not exist, a confirmation prompt offers to create it. Empty move sessions (no user/assistant messages) are automatically cleaned up on shutdown so they don't accumulate. -- Added Tab completion for `/move` destination directories. Typing `/move ` now offers directory suggestions relative to the current working directory, including support for `~`, absolute, and relative paths. -- Added TinyFish, DuckDuckGo, xAI, and Firecrawl web_search providers. -- Added `models.yml` `remoteCompaction` and `compactionModel` config so custom providers can opt into provider-native compaction and run compaction on a separate model without changing the active session model. ([#3104](https://github.com/can1357/oh-my-pi/issues/3104)) -- Added project/user/plugin `dap.json` and `dap.yaml` support for defining or overriding debugger adapters used by the `debug` tool. ([#2999](https://github.com/can1357/oh-my-pi/issues/2999)) +- Added `ssh://host/path` support to `read`, `search`, and `write` tools for single text files and directory listings on pre-configured POSIX SSH hosts. +- Added an interactive `/move` overlay with path autocompletion and directory creation prompts, starting a fresh session in the target directory while leaving the previous session resumable. +- Added support for file deletion and moving within file editing operations. +- Added `providers.maxInFlightRequests` setting to cap concurrent LLM requests per provider across local OMP processes. +- Added support for `discovery.type: litellm` in `models.yml` to automatically discover model metadata from LiteLLM gateways. +- Added `models.yml` configuration options `remoteCompaction` and `compactionModel` to run compaction on a separate model or opt into provider-native compaction. +- Added project, user, and plugin-level `dap.json` and `dap.yaml` support for defining or overriding debugger adapters used by the `debug` tool. +- Added TinyFish, DuckDuckGo, xAI, and Firecrawl web search providers. - Added an Appearance setting for OSC 9;4 native terminal progress indicators during active agent turns and context maintenance. +- Added Loop Guard "Tool-Call Reminder" to automatically interrupt Gemini reasoning loops that generate excessive planning headers without acting. ### Changed -- Renamed `search` tool to `grep` -- Renamed `find` tool to `glob` -- Updated configuration keys and UI labels to reflect `grep` and `glob` tool naming -- Automatically migrated existing user settings for `search` and `find` to `grep` and `glob` configs -- Changed the `inlineToolDescriptors` setting ("Inline Tool Descriptors") from a boolean to a three-way enum (`auto` | `on` | `off`), defaulting to `auto`. `auto` inlines tool descriptors into the system prompt (and strips them from provider tool schemas) only for Gemini models, leaving them in the schemas otherwise; `on`/`off` force the behavior regardless of model. Existing `true`/`false` configs migrate to `on`/`off`. -- Replaced `as string | undefined` inline casts with `typeof` guards in the TUI usage renderer's account identity resolution (`formatAccountLabel`, `formatUnlimitedReportLabel`, reset-credits label, and unlimited-plan tier), so empty-string metadata values fall through to the next fallback instead of being displayed -- Reused shared TUI mouse-routing helpers across fullscreen overlay selectors. -- Cached successful document conversions so repeated reads of unchanged PDFs, Office documents, and EPUBs reuse converted markdown instead of rerunning markit conversion. -- Hardened the document conversion cache: random-suffixed temp filenames, orphaned `.tmp` sweeping during prune, and regression coverage for the sweep. +- Changed the `inlineToolDescriptors` setting from a boolean to a three-way enum (`auto` | `on` | `off`), defaulting to `auto` to inline descriptors only for Gemini models. +- Added caching for successful document conversions (PDFs, Office documents, EPUBs) to avoid redundant conversions on repeated reads. ### Fixed -- Fixed `snapcompact` compaction silently falling back to an LLM summary when local preflight rejects the archive; manual and auto snapcompact now fail locally with the blocker instead of making provider calls. ([#3599](https://github.com/can1357/oh-my-pi/issues/3599)) -- Fixed garbled casing in auto-generated session titles. `normalizeGeneratedTitle` (`packages/coding-agent/src/tiny/text.ts`) used to force Title Case via a `\b\p{Ll}` regex, capitalizing function words ("for" → "For") and amplifying stray model capitals ("dAemon" → "DAemon"). It now reconciles each title token against the user's own message: tokens typed verbatim are kept; proper nouns the user cased distinctively are restored when the model flattened them ("tinyvmm" → "TinyVMM"); lowercase words carrying a stray interior capital the user never wrote are flattened ("dAemon" → "daemon"); and model-cased PascalCase proper nouns ("GitHub", "OAuth") are left untouched. Restoration is limited to distinctively cased source tokens so a message that merely starts with "For" can't force a mid-title "for" to "For". Applies to both the local tiny-model and online pi/smol title paths. -- Fixed IRC broadcasts (`to: "all"`) rendering twice in the main agent's transcript. A subagent broadcast fans out one `bus.send` per live peer, and `listVisibleTo` always includes `Main`, so the main agent received the body once as its own `irc:incoming` card *and* once per other recipient as an `irc:relay` observation of the sibling legs (`Sender → Other`) — identical text shown N+1 times. `IrcTool.#executeSend` now sets `suppressRelay` on every broadcast leg when `Main` is among the targets (it already has the body via its direct incoming card), and `IrcBus.send` skips `#relayToMainUi` for suppressed legs. Direct sub→sub relays, direct messages to `Main`, and `Main`'s own outbound sends are unaffected. -- Fixed Claude API refusals polluting the replayed session context and causing later prompts to refuse again. ([#3592](https://github.com/can1357/oh-my-pi/issues/3592)) -- Fixed browser screenshots reporting `0x0` dimensions when `Bun.Image` rejects an image whose PNG/JPEG header still exposes real dimensions. ([#3577](https://github.com/can1357/oh-my-pi/issues/3577)) -- Fixed Anthropic classifier refusals being persisted as assistant dialogue after no fallback handled them; refusal stops are now displayed as errors but pruned from active and saved context before the next prompt. ([#3591](https://github.com/can1357/oh-my-pi/issues/3591)) -- Fixed the `eval` `tool.*` bridge leaking the harness-internal `i` ("intent") field into MCP `tools/call` requests, so strict-schema servers (Linear, anything with `additionalProperties:false` / Zod `.strict()`) rejected every call with `-32602 unrecognized_keys: ["i"]` while the same call via the direct model tool-call path succeeded. `MCPTool.execute` / `DeferredMCPTool.execute` (`packages/coding-agent/src/mcp/tool-bridge.ts`) now strip `INTENT_FIELD` at the MCP boundary, so an MCP call behaves identically whether issued by the model directly or via the eval `tool.*` bridge; servers that legitimately declare `i` as a real parameter keep it untouched. ([#3575](https://github.com/can1357/oh-my-pi/issues/3575)) -- Fixed the advisor entering a spam loop in which it emitted hundreds of repeated `Stop.`, `Done.`, and `No issue; continue.` `` injections, polluting the primary transcript and destabilizing the watched agent after the task was already complete. The advisor system prompt's rules ("at most one `advise` per update", "NEVER send the same advice twice") are now enforced in code by a new `AdvisorEmissionGuard` on the `enqueueAdvice` boundary in `AgentSession`: it normalizes each note (case-insensitive, punctuation-folded), drops content-free self-talk filler (`stop`/`done`/`no issue continue`/`lgtm`/etc.), dedupes by exact normalized text across the session (bounded FIFO history), and rate-limits to one accepted note per advisor model prompt cycle. Reset on advisor reset (compaction, session switch, `/new`) so a re-primed reviewer can re-raise old issues. ([#3520](https://github.com/can1357/oh-my-pi/issues/3520)) -- Fixed auto-compaction thrashing on a session whose single most-recent kept turn already exceeds the compaction threshold. `prepareCompaction` keeps that turn verbatim (`findCutPoint` never cuts at tool results), so the rewritten context stays above threshold; the context-full / snapcompact success tail scheduled the agent-authored auto-continue (and the overflow/incomplete retry) unconditionally, so the next `agent_end` re-entered `#checkCompaction` over the same oversized tail and re-fired forever. This is the residual loop left after #3247 capped snapcompact's own frame projection — once the frame cap drops below one frame, snapcompact is skipped and the context-full summarizer path still made no headroom. `#runAutoCompaction` now gates the threshold auto-continue on a post-maintenance headroom check (`#compactionCreatedHeadroom`, sharing shake's `COMPACTION_RECOVERY_BAND` hysteresis from #2275) and the overflow/incomplete retry on a separate fit check (`#compactionCreatedRetryFit`, measured after the failed turn is dropped) so a recoverable overflow that fits the window still retries; when a pass frees too little for the relevant path it pauses automatic maintenance and emits a single warning instead of looping. The post-turn threshold check also ignores an assistant's stale pre-compaction `usage` so the scheduled auto-continue cannot re-trip on the kept assistant's old high token count. The headroom check now treats any residual at or below the recovery band as progress: the band sits strictly under the compaction threshold, so a stale/tool-output prune that already pushed the trigger sub-band no longer makes a residual that merely holds the line report a false "no progress" and suppress a valid auto-continue. -- Fixed the advisor prompt allowing confident root-cause claims about tool-call arguments absent from its reviewed transcript, so timeout advice now has to cite observed fields instead of inventing mechanisms like `paths[0]` array flattening. ([#3483](https://github.com/can1357/oh-my-pi/issues/3483)) -- Fixed eval `agent()` helper subagents remaining visible as idle/IRC-revivable peers after the helper call returns; one-shot eval subagents now dispose and unregister at completion. ([#3407](https://github.com/can1357/oh-my-pi/pull/3407)) -- Fixed the parent interactive prompt wedging (frozen, 0% CPU) after a subagent's yield-triggered abort. The executor's abort monitor and abort listener each called `session.abort()` fire-and-forget, so `runSubprocess` could resolve and adopt the subagent before its abort cleanup finished clearing in-flight session state. Active-subagent aborts are now deduped through a single cached cleanup promise that the subprocess awaits (bounded) before finalizing. ([#2805](https://github.com/can1357/oh-my-pi/issues/2805)) -- Fixed thinking blocks appearing in the UI when thinking level is "off". Some providers (MiniMax, GLM, DeepSeek) return thinking blocks even with reasoning disabled; thinking blocks are now auto-hidden when the thinking level is "off", regardless of the `hideThinkingBlock` setting. Toggling thinking block visibility while thinking is off shows a status message instead of silently no-op'ing. ([#626](https://github.com/can1357/oh-my-pi/issues/626)) -- Fixed the TUI usage display failing to resolve a used fraction for limits that only populate `remainingFraction` (no `usedFraction`, `used`/`limit`, or `percent`+`used`). The TUI's local `resolveFraction` was missing the inverted-remaining fallback that the shared `resolveUsedFraction` from `@oh-my-pi/pi-ai` already handles — replaced the local copy with the shared function so the TUI and CLI paths resolve fractions identically. -- Fixed long-running SSH command boxes leaving a stale `⏳ SSH: [host]` header above the final `⇄ SSH: [host]` header in terminal scrollback. The SSH renderer now keeps its partial-result chrome on the pending icon/state and opts the block out of stream-commit while `isPartial` holds (via the new `ToolRenderer.provisionalPartialResult` flag honored by `ToolExecutionComponent.isTranscriptBlockCommitStable`), so the stable-prefix ratchet can't promote the partial header to native scrollback only to have the final render strand it above the settled frame ([#3177](https://github.com/can1357/oh-my-pi/issues/3177)). -- Fixed Gemini over-planning runs that emit long chains of thinking headers (`**Refining …**`, `## Examining …`) without ever issuing a tool call. The session now interrupts that stream, discards the partial reasoning turn, injects a hidden tool-call reminder, and continues with the corrective context instead of burning the full budget on planning. -- `find`, `ast_grep`, and `ast_edit` now reject an `ssh://` path before opening any SSH connection. These read/write-tier tools never shared the exec-tier approval `read`/`search`/`write` got for `ssh://`, and they have no local file to operate on for a remote URL, so they previously connected and ran a remote command only to then error — they now fail fast (pointing at `read`/`search`) and never connect. -- `ssh://` `write` now overwrites an existing regular file in place from a fully-staged temp, preserving its ordinary permission bits (a `0600` remote file no longer resets to the umask default), ACLs, extended attributes, and inode; new files and non-directory symlink destinations still commit via an atomic temp+rename (a symlink to a directory is rejected like a directory). -- A host-less `ssh://` URL that carries a path (e.g. `ssh:///etc/hosts`) now errors with a host-required message instead of silently listing the configured hosts and dropping the path. -- `ssh://` `write` now refuses an existing special file (FIFO/socket/device) instead of replacing it with a regular file, and removes the staged remote temp on every failure (staging, in-place copy, or rename) via an EXIT trap. -- `ssh://` `read`/`search`/`write` now reject a Windows remote host with a clear POSIX-only error (use the `ssh` tool for Windows) instead of failing with a raw remote-shell error. -- `ssh://` `search` now rejects a malformed or multi-range line selector (e.g. `:-10`, `:1-1:1-2`) instead of silently searching the whole remote file, matching `read`'s selector grammar, and validates the pattern against the native RE2 dialect for remote/virtual-only searches so unsupported regexes fail consistently with local search. -- `ssh://` host names containing URL-reserved characters (e.g. `prod:2222`, `alice@prod`) are now percent-encoded in the host index and autocomplete and matched against the decoded authority, so configured aliases resolve correctly (a literal `user@`/`:port` in the URL is still rejected as an override on a configured host). -- `ssh://` `read`/`search`/`write` now reject an explicit `:0` port (e.g. `ssh://host:0/path`) before connecting, instead of silently dropping it and connecting on the default SSH port; a path-less `ssh://host:2222` also no longer mistakes its authority port for a read selector. -- `ssh://` now strips IPv6 URL brackets before invoking OpenSSH (so `ssh://[::1]/path` targets `::1` instead of failing to resolve), rejects a malformed or out-of-range port (`ssh://host:abc`, `ssh://host:65536`) before connecting instead of treating the bad authority as an opaque default-port host, and `search` no longer drains a remote directory listing only to reject it (it requests directory metadata via `skipDirectoryListing`). -- `ssh://` `read`/`search`/`write` now reject a URL query string or fragment (e.g. `ssh://host/tmp/a?draft`, `ssh://host/tmp/a#draft`) instead of silently operating on the path truncated before the `?`/`#`; a literal `?`/`#` in a remote filename must be percent-encoded (`%3F`/`%23`). -- `ssh://` `read`/`search`/`write` now restrict transfers to remotes whose login shell is `sh`, `bash`, or `zsh`. A non-POSIX login shell (fish, csh/tcsh) can't parse the POSIX transfer snippets — and csh/tcsh apply `!` history expansion to the command line — so it is refused with a clear error (use the `ssh` tool) instead of running a broken remote command; host-shell detection no longer misclassifies `fish`/`csh`/`tcsh` as `sh`. -- Added models.yml discovery.type: litellm so custom LiteLLM gateways can discover context windows, output caps, vision, and reasoning metadata from LiteLLM before falling back to OpenAI-compatible /models. -- Fixed the main TUI status line hiding live subagents unless users already enabled the `subagents` segment; running subagents now force a hub-key badge into the editor border, and advisor rows in Agent Hub are labeled read-only. ([#3499](https://github.com/can1357/oh-my-pi/issues/3499)) -- Fixed Ctrl+Z hanging the terminal after any tool call had run: the TUI tore down (`ui.stop()`) but the process kept running in `Sl+` state, leaving the user with a dead terminal recoverable only via `kill -9`. The embedded `brush-core` shell behind every bash tool call installs a tokio SIGTSTP listener on `Process::wait` (`crates/brush-core-vendored/src/sys/unix/signal.rs::tstp_signal_listener` → `tokio::signal::unix::signal(SIGTSTP)`); per tokio's contract, the first call for a SignalKind permanently replaces the kernel-default handler for the lifetime of the process. So the first bash invocation — even `/usr/bin/true` — silently overrode SIGTSTP's "stop" default, and `InputController.handleCtrlZ`'s subsequent `process.kill(0, "SIGTSTP")` was swallowed by tokio. The handler now sends `SIGSTOP` (uncatchable, unblockable, unignorable) to the foreground process group, so the kernel parks omp regardless of installed handlers and the shell sees the whole job stop even when omp runs behind a wrapper (`npx`, `pnpm exec`, `bunx`, …) or as one stage of a pipeline. MCP stdio servers now spawn detached into their own session — they're insulated both from terminal job-control signals (which used to stop their process trees and leave the JSONL read loop blocked on silent pipes) and from the new pgid=0 suspend itself ([#3461](https://github.com/can1357/oh-my-pi/issues/3461)). -- Fixed streaming Esc handling so the first Esc arms a 2s cancel hint and only a second Esc in that window aborts the active response ([#3493](https://github.com/can1357/oh-my-pi/issues/3493)). -- Migrated 203 test files from `fs.rm`/`fs.rmSync` to `removeWithRetries`/`removeSyncWithRetries` (356 call sites) to reduce EBUSY test failures on Windows. `removeWithRetries` is now exported from `@oh-my-pi/pi-utils` -- Fixed GitLab Duo Agent namespace/project discovery reading the original repo's git remote after a `/move`. The session's working directory is now resolved live (per LLM call) from the `SessionManager` instead of being captured when the agent was constructed, so moving the session re-scopes Duo workspace discovery to the new repository. -- Fixed `omp auth-broker login gitlab-duo-agent` (and `--via`) hanging until timeout: the provider uses GitLab's fixed `vscode://` OAuth redirect, which never reaches the broker's local callback server, and `runLocalLogin` supplied no `onManualCodeInput` fallback. The broker login now offers the same paste-the-redirect-URL prompt the interactive sign-in uses, so credentials can be saved. -- Fixed the persistent todo HUD above the editor reading as ambient status text rather than an anchored panel — the small "Todos" block now sits inside dim horizontal rules (matching `BtwPanel` / `OmfgPanel`) and the header inlines progress and the active-phase pointer (`Todos · 2/7 done · I/III Foundation`), so the list stays self-describing without scrolling back to the tool-result block in chat ([#3213](https://github.com/can1357/oh-my-pi/issues/3213)). -- Fixed advisor and status-line context when the session cwd is a parent of a single child git repo, so nested-repo work is surfaced before missing parent-cwd paths are treated as destroyed. ([#3130](https://github.com/can1357/oh-my-pi/issues/3130)) -- Fixed `Cmd+V` on macOS silently dropping image-only clipboard pastes (screenshot via `Cmd+Shift+5` "save to clipboard", Chrome image copy, …) — the user had to fall back to `Ctrl+V`. Follow-up to #3506: that fix handled clipboards exposing a file URL or path; the screenshot path leaves only raw image bytes on the pasteboard. macOS terminals (iTerm2, Terminal.app, Warp, Ghostty without OSC 5522, Windows Terminal forwarding, …) intercept `Cmd+V` and read `NSPasteboardTypeString` first; for an image-only clipboard that read returns `""`, so the terminal forwards a complete-but-empty bracketed paste (`\x1b[200~\x1b[201~`). `CustomEditor.handleInput` inserted the empty payload and the keystroke disappeared. `CustomEditor` now runs its own `BracketedPasteHandler` ahead of the inherited handler so the assembled paste payload is routed regardless of whether the start marker, payload, and end marker arrive in one stdin chunk or are fragmented across several (Windows Terminal under load, certain SSH muxes, tmux extended-keys passthrough, …). Strict-zero-length assembled payloads route to the same `onPasteImage` smart reader the `app.clipboard.pasteImage` keybind uses (attaches the clipboard image, or falls back to the #1628 smart text paste / "clipboard is empty" diagnostic); explicit image-file paths route to `onPasteImagePath` (the #3506 path also benefits from split-chunk assembly); everything else hands off to the base editor's `pasteText` so `[Paste #N]` markers, autocomplete, and undo state stay intact. Whitespace-only pastes are preserved as literal text. ([#3601](https://github.com/can1357/oh-my-pi/issues/3601)) +- Fixed terminal hangs on Ctrl+Z (SIGTSTP) after running bash tool calls, and insulated MCP stdio servers from terminal job-control signals. +- Fixed macOS `Cmd+V` silently dropping image-only clipboard pastes in supported terminals. +- Fixed an infinite loop in auto-compaction when a single recent turn exceeded the compaction threshold. +- Fixed an issue where the advisor could enter a spam loop of repeated blocker injections, polluting the transcript. +- Fixed Claude API and Anthropic classifier refusals polluting the replayed session context or persisting as assistant dialogue. +- Fixed MCP tool calls failing with strict-schema servers by stripping internal metadata fields at the MCP boundary. +- Fixed a terminal freeze/hang when a subagent abort was triggered. +- Fixed thinking blocks appearing in the UI when thinking level is "off" for providers that return them anyway. +- Fixed GitLab Duo Agent namespace/project discovery and authentication flow hangs. +- Hardened and improved `ssh://` protocol handling, including support for IPv6 brackets, percent-encoded hosts, port validation, and POSIX shell restrictions, while preventing unsupported tools from attempting SSH connections. +- Fixed TUI usage display fraction resolution and added expiry dates for banked Codex rate-limit resets to the `/usage` display. +- Fixed a rendering issue where long-running SSH commands left stale pending headers in terminal scrollback. +- Fixed garbled casing in auto-generated session titles by reconciling title tokens against the user's original messages. +- Fixed IRC broadcasts rendering twice in the main agent's transcript. +- Improved the persistent Todo HUD styling and progress indicators to make it self-describing and visually distinct. +- Fixed browser screenshots reporting `0x0` dimensions when image headers expose real dimensions. +- Fixed `snapcompact` compaction silently falling back to LLM summaries when local preflight rejects the archive. ## [16.1.23] - 2026-06-26 diff --git a/packages/coding-agent/DEVELOPMENT.md b/packages/coding-agent/DEVELOPMENT.md index 018634af6..562d498a0 100644 --- a/packages/coding-agent/DEVELOPMENT.md +++ b/packages/coding-agent/DEVELOPMENT.md @@ -21,12 +21,12 @@ Run from `packages/coding-agent/` (or add `--cwd=packages/coding-agent`): | Tests | `bun run test` | | Autofix: lint + format prompts + docs index | `bun run fix` | | Reformat prompt `.md` assets | `bun run format-prompts` | -| Regenerate the `docs://` index | `bun run generate-docs-index` | +| Regenerate the `docs://` index | `bun run gen:docs` | | Build the `dist/omp` binary | `bun run build` | Never invoke `tsc`/`npx tsc` directly — `bun run check` is the typecheck gate. After changing the React tool renderers under `collab-web/src/tool-render/`, rebuild them -with `bun --cwd=packages/collab-web run build:tool-views`. +with `bun run gen:tool-views`. ## Boot flow diff --git a/packages/coding-agent/package.json b/packages/coding-agent/package.json index 2ec21e115..6224b1b02 100644 --- a/packages/coding-agent/package.json +++ b/packages/coding-agent/package.json @@ -1,584 +1,591 @@ { - "type": "module", - "name": "@oh-my-pi/pi-coding-agent", - "version": "16.1.23", - "description": "Coding agent CLI with read, bash, edit, write tools and session management", - "homepage": "https://omp.sh", - "author": "Can Boluk", - "contributors": [ - "Mario Zechner" - ], - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/can1357/oh-my-pi.git", - "directory": "packages/coding-agent" - }, - "bugs": { - "url": "https://github.com/can1357/oh-my-pi/issues" - }, - "keywords": [ - "coding-agent", - "ai", - "llm", - "cli", - "tui", - "agent" - ], - "main": "./src/index.ts", - "types": "./src/index.ts", - "bin": { - "omp": "src/cli.ts" - }, - "scripts": { - "build": "bun scripts/build-binary.ts", - "check": "biome check . && bun run check:types", - "check:types": "tsgo -p tsconfig.json --noEmit", - "lint": "biome lint .", - "test": "bun ../../scripts/ci-test-ts.ts coding-agent-heavy --full", - "fix": "biome check --write --unsafe . && bun run format-prompts", - "fmt": "biome format --write . && bun run format-prompts", - "format-prompts": "bun scripts/format-prompts.ts", - "generate-docs-index": "bun scripts/generate-docs-index.ts --generate", - "prepack": "bun scripts/generate-docs-index.ts --generate && bun --cwd=../collab-web run build:tool-views && bun scripts/bundle-dist.ts || ( bun scripts/generate-docs-index.ts --reset; exit 1 )", - "postpack": "bun scripts/generate-docs-index.ts --reset", - "bench:guard": "bun scripts/bench-guard.ts" - }, - "dependencies": { - "@agentclientprotocol/sdk": "catalog:", - "@babel/parser": "catalog:", - "@mozilla/readability": "catalog:", - "@oh-my-pi/hashline": "catalog:", - "@oh-my-pi/omp-stats": "catalog:", - "@oh-my-pi/pi-agent-core": "catalog:", - "@oh-my-pi/pi-ai": "catalog:", - "@oh-my-pi/pi-catalog": "catalog:", - "@oh-my-pi/pi-mnemopi": "catalog:", - "@oh-my-pi/pi-natives": "catalog:", - "@oh-my-pi/pi-tui": "catalog:", - "@oh-my-pi/pi-utils": "catalog:", - "@oh-my-pi/pi-wire": "catalog:", - "@oh-my-pi/snapcompact": "catalog:", - "@opentelemetry/api": "catalog:", - "@opentelemetry/context-async-hooks": "catalog:", - "@opentelemetry/exporter-trace-otlp-proto": "catalog:", - "@opentelemetry/resources": "catalog:", - "@opentelemetry/sdk-trace-base": "catalog:", - "@opentelemetry/sdk-trace-node": "catalog:", - "@puppeteer/browsers": "catalog:", - "@types/turndown": "catalog:", - "@xterm/headless": "catalog:", - "arktype": "catalog:", - "chalk": "catalog:", - "diff": "catalog:", - "fast-xml-parser": "catalog:", - "handlebars": "catalog:", - "linkedom": "catalog:", - "lru-cache": "catalog:", - "mammoth": "catalog:", - "mupdf": "catalog:", - "puppeteer-core": "catalog:", - "turndown": "catalog:", - "turndown-plugin-gfm": "catalog:", - "zod": "catalog:" - }, - "optionalDependencies": { - "@huggingface/transformers": "catalog:", - "sherpa-onnx-node": "1.13.2" - }, - "devDependencies": { - "@types/bun": "catalog:" - }, - "engines": { - "bun": ">=1.3.14" - }, - "files": [ - "src", - "dist/cli.js", - "dist/*.node", - "scripts", - "examples", - "README.md", - "CHANGELOG.md" - ], - "exports": { - ".": { - "types": "./src/index.ts", - "import": "./src/index.ts" - }, - "./*": { - "types": "./src/*.ts", - "import": "./src/*.ts" - }, - "./async": { - "types": "./src/async/index.ts", - "import": "./src/async/index.ts" - }, - "./async/*": { - "types": "./src/async/*.ts", - "import": "./src/async/*.ts" - }, - "./autoresearch": { - "types": "./src/autoresearch/index.ts", - "import": "./src/autoresearch/index.ts" - }, - "./autoresearch/*": { - "types": "./src/autoresearch/*.ts", - "import": "./src/autoresearch/*.ts" - }, - "./autoresearch/tools/*": { - "types": "./src/autoresearch/tools/*.ts", - "import": "./src/autoresearch/tools/*.ts" - }, - "./capability": { - "types": "./src/capability/index.ts", - "import": "./src/capability/index.ts" - }, - "./capability/*": { - "types": "./src/capability/*.ts", - "import": "./src/capability/*.ts" - }, - "./cli/*": { - "types": "./src/cli/*.ts", - "import": "./src/cli/*.ts" - }, - "./cli/gallery-fixtures": { - "types": "./src/cli/gallery-fixtures/index.ts", - "import": "./src/cli/gallery-fixtures/index.ts" - }, - "./cli/gallery-fixtures/*": { - "types": "./src/cli/gallery-fixtures/*.ts", - "import": "./src/cli/gallery-fixtures/*.ts" - }, - "./cli/commands/*": { - "types": "./src/cli/commands/*.ts", - "import": "./src/cli/commands/*.ts" - }, - "./commands/*": { - "types": "./src/commands/*.ts", - "import": "./src/commands/*.ts" - }, - "./commit": { - "types": "./src/commit/index.ts", - "import": "./src/commit/index.ts" - }, - "./commit/*": { - "types": "./src/commit/*.ts", - "import": "./src/commit/*.ts" - }, - "./commit/agentic": { - "types": "./src/commit/agentic/index.ts", - "import": "./src/commit/agentic/index.ts" - }, - "./commit/agentic/*": { - "types": "./src/commit/agentic/*.ts", - "import": "./src/commit/agentic/*.ts" - }, - "./commit/agentic/tools": { - "types": "./src/commit/agentic/tools/index.ts", - "import": "./src/commit/agentic/tools/index.ts" - }, - "./commit/agentic/tools/*": { - "types": "./src/commit/agentic/tools/*.ts", - "import": "./src/commit/agentic/tools/*.ts" - }, - "./commit/analysis": { - "types": "./src/commit/analysis/index.ts", - "import": "./src/commit/analysis/index.ts" - }, - "./commit/analysis/*": { - "types": "./src/commit/analysis/*.ts", - "import": "./src/commit/analysis/*.ts" - }, - "./commit/changelog": { - "types": "./src/commit/changelog/index.ts", - "import": "./src/commit/changelog/index.ts" - }, - "./commit/changelog/*": { - "types": "./src/commit/changelog/*.ts", - "import": "./src/commit/changelog/*.ts" - }, - "./commit/git/*": { - "types": "./src/commit/git/*.ts", - "import": "./src/commit/git/*.ts" - }, - "./commit/map-reduce": { - "types": "./src/commit/map-reduce/index.ts", - "import": "./src/commit/map-reduce/index.ts" - }, - "./commit/map-reduce/*": { - "types": "./src/commit/map-reduce/*.ts", - "import": "./src/commit/map-reduce/*.ts" - }, - "./commit/utils/*": { - "types": "./src/commit/utils/*.ts", - "import": "./src/commit/utils/*.ts" - }, - "./config/*": { - "types": "./src/config/*.ts", - "import": "./src/config/*.ts" - }, - "./dap": { - "types": "./src/dap/index.ts", - "import": "./src/dap/index.ts" - }, - "./dap/*": { - "types": "./src/dap/*.ts", - "import": "./src/dap/*.ts" - }, - "./debug": { - "types": "./src/debug/index.ts", - "import": "./src/debug/index.ts" - }, - "./debug/*": { - "types": "./src/debug/*.ts", - "import": "./src/debug/*.ts" - }, - "./discovery": { - "types": "./src/discovery/index.ts", - "import": "./src/discovery/index.ts" - }, - "./discovery/*": { - "types": "./src/discovery/*.ts", - "import": "./src/discovery/*.ts" - }, - "./edit": { - "types": "./src/edit/index.ts", - "import": "./src/edit/index.ts" - }, - "./edit/*": { - "types": "./src/edit/*.ts", - "import": "./src/edit/*.ts" - }, - "./edit/modes/*": { - "types": "./src/edit/modes/*.ts", - "import": "./src/edit/modes/*.ts" - }, - "./exa": { - "types": "./src/exa/index.ts", - "import": "./src/exa/index.ts" - }, - "./exa/*": { - "types": "./src/exa/*.ts", - "import": "./src/exa/*.ts" - }, - "./exec/*": { - "types": "./src/exec/*.ts", - "import": "./src/exec/*.ts" - }, - "./export/*": { - "types": "./src/export/*.ts", - "import": "./src/export/*.ts" - }, - "./export/html": { - "types": "./src/export/html/index.ts", - "import": "./src/export/html/index.ts" - }, - "./export/html/*": { - "types": "./src/export/html/*.ts", - "import": "./src/export/html/*.ts" - }, - "./extensibility/*": { - "types": "./src/extensibility/*.ts", - "import": "./src/extensibility/*.ts" - }, - "./extensibility/custom-commands": { - "types": "./src/extensibility/custom-commands/index.ts", - "import": "./src/extensibility/custom-commands/index.ts" - }, - "./extensibility/custom-commands/*": { - "types": "./src/extensibility/custom-commands/*.ts", - "import": "./src/extensibility/custom-commands/*.ts" - }, - "./extensibility/custom-commands/bundled/ci-green": { - "types": "./src/extensibility/custom-commands/bundled/ci-green/index.ts", - "import": "./src/extensibility/custom-commands/bundled/ci-green/index.ts" - }, - "./extensibility/custom-commands/bundled/review": { - "types": "./src/extensibility/custom-commands/bundled/review/index.ts", - "import": "./src/extensibility/custom-commands/bundled/review/index.ts" - }, - "./extensibility/custom-tools": { - "types": "./src/extensibility/custom-tools/index.ts", - "import": "./src/extensibility/custom-tools/index.ts" - }, - "./extensibility/custom-tools/*": { - "types": "./src/extensibility/custom-tools/*.ts", - "import": "./src/extensibility/custom-tools/*.ts" - }, - "./extensibility/extensions": { - "types": "./src/extensibility/extensions/index.ts", - "import": "./src/extensibility/extensions/index.ts" - }, - "./extensibility/extensions/*": { - "types": "./src/extensibility/extensions/*.ts", - "import": "./src/extensibility/extensions/*.ts" - }, - "./extensibility/hooks": { - "types": "./src/extensibility/hooks/index.ts", - "import": "./src/extensibility/hooks/index.ts" - }, - "./extensibility/hooks/*": { - "types": "./src/extensibility/hooks/*.ts", - "import": "./src/extensibility/hooks/*.ts" - }, - "./extensibility/plugins": { - "types": "./src/extensibility/plugins/index.ts", - "import": "./src/extensibility/plugins/index.ts" - }, - "./extensibility/plugins/*": { - "types": "./src/extensibility/plugins/*.ts", - "import": "./src/extensibility/plugins/*.ts" - }, - "./extensibility/plugins/marketplace": { - "types": "./src/extensibility/plugins/marketplace/index.ts", - "import": "./src/extensibility/plugins/marketplace/index.ts" - }, - "./extensibility/plugins/marketplace/*": { - "types": "./src/extensibility/plugins/marketplace/*.ts", - "import": "./src/extensibility/plugins/marketplace/*.ts" - }, - "./internal-urls": { - "types": "./src/internal-urls/index.ts", - "import": "./src/internal-urls/index.ts" - }, - "./internal-urls/*": { - "types": "./src/internal-urls/*.ts", - "import": "./src/internal-urls/*.ts" - }, - "./eval": { - "types": "./src/eval/index.ts", - "import": "./src/eval/index.ts" - }, - "./eval/js/*": { - "types": "./src/eval/js/*.ts", - "import": "./src/eval/js/*.ts" - }, - "./eval/py/*": { - "types": "./src/eval/py/*.ts", - "import": "./src/eval/py/*.ts" - }, - "./lsp": { - "types": "./src/lsp/index.ts", - "import": "./src/lsp/index.ts" - }, - "./lsp/*": { - "types": "./src/lsp/*.ts", - "import": "./src/lsp/*.ts" - }, - "./lsp/clients": { - "types": "./src/lsp/clients/index.ts", - "import": "./src/lsp/clients/index.ts" - }, - "./lsp/clients/*": { - "types": "./src/lsp/clients/*.ts", - "import": "./src/lsp/clients/*.ts" - }, - "./markit": { - "types": "./src/markit/index.ts", - "import": "./src/markit/index.ts" - }, - "./markit/*": { - "types": "./src/markit/*.ts", - "import": "./src/markit/*.ts" - }, - "./mcp": { - "types": "./src/mcp/index.ts", - "import": "./src/mcp/index.ts" - }, - "./mcp/*": { - "types": "./src/mcp/*.ts", - "import": "./src/mcp/*.ts" - }, - "./mcp/transports": { - "types": "./src/mcp/transports/index.ts", - "import": "./src/mcp/transports/index.ts" - }, - "./mcp/transports/*": { - "types": "./src/mcp/transports/*.ts", - "import": "./src/mcp/transports/*.ts" - }, - "./memories": { - "types": "./src/memories/index.ts", - "import": "./src/memories/index.ts" - }, - "./memories/*": { - "types": "./src/memories/*.ts", - "import": "./src/memories/*.ts" - }, - "./memory-backend": { - "types": "./src/memory-backend/index.ts", - "import": "./src/memory-backend/index.ts" - }, - "./memory-backend/*": { - "types": "./src/memory-backend/*.ts", - "import": "./src/memory-backend/*.ts" - }, - "./hindsight": { - "types": "./src/hindsight/index.ts", - "import": "./src/hindsight/index.ts" - }, - "./hindsight/*": { - "types": "./src/hindsight/*.ts", - "import": "./src/hindsight/*.ts" - }, - "./modes": { - "types": "./src/modes/index.ts", - "import": "./src/modes/index.ts" - }, - "./modes/*": { - "types": "./src/modes/*.ts", - "import": "./src/modes/*.ts" - }, - "./modes/acp": { - "types": "./src/modes/acp/index.ts", - "import": "./src/modes/acp/index.ts" - }, - "./modes/acp/*": { - "types": "./src/modes/acp/*.ts", - "import": "./src/modes/acp/*.ts" - }, - "./modes/components": { - "types": "./src/modes/components/index.ts", - "import": "./src/modes/components/index.ts" - }, - "./modes/components/*": { - "types": "./src/modes/components/*.ts", - "import": "./src/modes/components/*.ts" - }, - "./modes/components/extensions": { - "types": "./src/modes/components/extensions/index.ts", - "import": "./src/modes/components/extensions/index.ts" - }, - "./modes/components/extensions/*": { - "types": "./src/modes/components/extensions/*.ts", - "import": "./src/modes/components/extensions/*.ts" - }, - "./modes/components/status-line": { - "types": "./src/modes/components/status-line/index.ts", - "import": "./src/modes/components/status-line/index.ts" - }, - "./modes/components/status-line/*": { - "types": "./src/modes/components/status-line/*.ts", - "import": "./src/modes/components/status-line/*.ts" - }, - "./modes/controllers/*": { - "types": "./src/modes/controllers/*.ts", - "import": "./src/modes/controllers/*.ts" - }, - "./modes/setup-wizard": { - "types": "./src/modes/setup-wizard/index.ts", - "import": "./src/modes/setup-wizard/index.ts" - }, - "./modes/setup-wizard/*": { - "types": "./src/modes/setup-wizard/*.ts", - "import": "./src/modes/setup-wizard/*.ts" - }, - "./modes/rpc/*": { - "types": "./src/modes/rpc/*.ts", - "import": "./src/modes/rpc/*.ts" - }, - "./modes/theme/*": { - "types": "./src/modes/theme/*.ts", - "import": "./src/modes/theme/*.ts" - }, - "./modes/theme/defaults": { - "types": "./src/modes/theme/defaults/index.ts", - "import": "./src/modes/theme/defaults/index.ts" - }, - "./modes/utils/*": { - "types": "./src/modes/utils/*.ts", - "import": "./src/modes/utils/*.ts" - }, - "./plan-mode/*": { - "types": "./src/plan-mode/*.ts", - "import": "./src/plan-mode/*.ts" - }, - "./prompts/*": "./src/prompts/*.md", - "./secrets": { - "types": "./src/secrets/index.ts", - "import": "./src/secrets/index.ts" - }, - "./secrets/*": { - "types": "./src/secrets/*.ts", - "import": "./src/secrets/*.ts" - }, - "./session/*": { - "types": "./src/session/*.ts", - "import": "./src/session/*.ts" - }, - "./slash-commands/*": { - "types": "./src/slash-commands/*.ts", - "import": "./src/slash-commands/*.ts" - }, - "./ssh/*": { - "types": "./src/ssh/*.ts", - "import": "./src/ssh/*.ts" - }, - "./stt": { - "types": "./src/stt/index.ts", - "import": "./src/stt/index.ts" - }, - "./stt/*": { - "types": "./src/stt/*.ts", - "import": "./src/stt/*.ts" - }, - "./task": { - "types": "./src/task/index.ts", - "import": "./src/task/index.ts" - }, - "./task/*": { - "types": "./src/task/*.ts", - "import": "./src/task/*.ts" - }, - "./tool-discovery/*": { - "types": "./src/tool-discovery/*.ts", - "import": "./src/tool-discovery/*.ts" - }, - "./tools": { - "types": "./src/tools/index.ts", - "import": "./src/tools/index.ts" - }, - "./tools/*": { - "types": "./src/tools/*.ts", - "import": "./src/tools/*.ts" - }, - "./tui": { - "types": "./src/tui/index.ts", - "import": "./src/tui/index.ts" - }, - "./tui/*": { - "types": "./src/tui/*.ts", - "import": "./src/tui/*.ts" - }, - "./utils/*": { - "types": "./src/utils/*.ts", - "import": "./src/utils/*.ts" - }, - "./web/*": { - "types": "./src/web/*.ts", - "import": "./src/web/*.ts" - }, - "./web/scrapers": { - "types": "./src/web/scrapers/index.ts", - "import": "./src/web/scrapers/index.ts" - }, - "./web/scrapers/*": { - "types": "./src/web/scrapers/*.ts", - "import": "./src/web/scrapers/*.ts" - }, - "./web/search": { - "types": "./src/web/search/index.ts", - "import": "./src/web/search/index.ts" - }, - "./web/search/*": { - "types": "./src/web/search/*.ts", - "import": "./src/web/search/*.ts" - }, - "./web/search/providers/*": { - "types": "./src/web/search/providers/*.ts", - "import": "./src/web/search/providers/*.ts" - }, - "./*.js": "./src/*.ts" - } + "type": "module", + "name": "@oh-my-pi/pi-coding-agent", + "version": "16.1.23", + "description": "Coding agent CLI with read, bash, edit, write tools and session management", + "homepage": "https://omp.sh", + "author": "Can Boluk", + "contributors": [ + "Mario Zechner" + ], + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/can1357/oh-my-pi.git", + "directory": "packages/coding-agent" + }, + "bugs": { + "url": "https://github.com/can1357/oh-my-pi/issues" + }, + "keywords": [ + "coding-agent", + "ai", + "llm", + "cli", + "tui", + "agent" + ], + "main": "./src/index.ts", + "types": "./src/index.ts", + "bin": { + "omp": "src/cli.ts" + }, + "scripts": { + "build": "bun scripts/build-binary.ts", + "check": "biome check . && bun run check:types", + "check:types": "tsgo -p tsconfig.json --noEmit", + "lint": "biome lint .", + "test": "bun ../../scripts/ci-test-ts.ts coding-agent-heavy --full", + "fix": "biome check --write --unsafe . && bun run format-prompts", + "fmt": "biome format --write . && bun run format-prompts", + "format-prompts": "bun scripts/format-prompts.ts", + "gen:docs": "bun scripts/generate-docs-index.ts --generate", + "gen:docs:reset": "bun scripts/generate-docs-index.ts --reset", + "gen:tool-views": "bun --cwd=../collab-web run gen:tool-views", + "gen:bundle": "bun scripts/bundle-dist.ts", + "gen:mupdf": "bun scripts/embed-mupdf-wasm.ts --generate", + "gen:mupdf:reset": "bun scripts/embed-mupdf-wasm.ts --reset", + "gen:native": "bun --cwd=../natives run gen:native", + "gen:native:reset": "bun --cwd=../natives run gen:native:reset", + "prepack": "bun run gen:docs && bun run gen:tool-views && bun run gen:bundle || ( bun run gen:docs:reset; exit 1 )", + "postpack": "bun run gen:docs:reset", + "bench:guard": "bun scripts/bench-guard.ts" + }, + "dependencies": { + "@agentclientprotocol/sdk": "catalog:", + "@babel/parser": "catalog:", + "@mozilla/readability": "catalog:", + "@oh-my-pi/hashline": "catalog:", + "@oh-my-pi/omp-stats": "catalog:", + "@oh-my-pi/pi-agent-core": "catalog:", + "@oh-my-pi/pi-ai": "catalog:", + "@oh-my-pi/pi-catalog": "catalog:", + "@oh-my-pi/pi-mnemopi": "catalog:", + "@oh-my-pi/pi-natives": "catalog:", + "@oh-my-pi/pi-tui": "catalog:", + "@oh-my-pi/pi-utils": "catalog:", + "@oh-my-pi/pi-wire": "catalog:", + "@oh-my-pi/snapcompact": "catalog:", + "@opentelemetry/api": "catalog:", + "@opentelemetry/context-async-hooks": "catalog:", + "@opentelemetry/exporter-trace-otlp-proto": "catalog:", + "@opentelemetry/resources": "catalog:", + "@opentelemetry/sdk-trace-base": "catalog:", + "@opentelemetry/sdk-trace-node": "catalog:", + "@puppeteer/browsers": "catalog:", + "@types/turndown": "catalog:", + "@xterm/headless": "catalog:", + "arktype": "catalog:", + "chalk": "catalog:", + "diff": "catalog:", + "fast-xml-parser": "catalog:", + "handlebars": "catalog:", + "linkedom": "catalog:", + "lru-cache": "catalog:", + "mammoth": "catalog:", + "mupdf": "catalog:", + "puppeteer-core": "catalog:", + "turndown": "catalog:", + "turndown-plugin-gfm": "catalog:", + "zod": "catalog:" + }, + "optionalDependencies": { + "@huggingface/transformers": "catalog:", + "sherpa-onnx-node": "1.13.2" + }, + "devDependencies": { + "@types/bun": "catalog:" + }, + "engines": { + "bun": ">=1.3.14" + }, + "files": [ + "src", + "dist/cli.js", + "dist/*.node", + "scripts", + "examples", + "README.md", + "CHANGELOG.md" + ], + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./src/index.ts" + }, + "./*": { + "types": "./src/*.ts", + "import": "./src/*.ts" + }, + "./async": { + "types": "./src/async/index.ts", + "import": "./src/async/index.ts" + }, + "./async/*": { + "types": "./src/async/*.ts", + "import": "./src/async/*.ts" + }, + "./autoresearch": { + "types": "./src/autoresearch/index.ts", + "import": "./src/autoresearch/index.ts" + }, + "./autoresearch/*": { + "types": "./src/autoresearch/*.ts", + "import": "./src/autoresearch/*.ts" + }, + "./autoresearch/tools/*": { + "types": "./src/autoresearch/tools/*.ts", + "import": "./src/autoresearch/tools/*.ts" + }, + "./capability": { + "types": "./src/capability/index.ts", + "import": "./src/capability/index.ts" + }, + "./capability/*": { + "types": "./src/capability/*.ts", + "import": "./src/capability/*.ts" + }, + "./cli/*": { + "types": "./src/cli/*.ts", + "import": "./src/cli/*.ts" + }, + "./cli/gallery-fixtures": { + "types": "./src/cli/gallery-fixtures/index.ts", + "import": "./src/cli/gallery-fixtures/index.ts" + }, + "./cli/gallery-fixtures/*": { + "types": "./src/cli/gallery-fixtures/*.ts", + "import": "./src/cli/gallery-fixtures/*.ts" + }, + "./cli/commands/*": { + "types": "./src/cli/commands/*.ts", + "import": "./src/cli/commands/*.ts" + }, + "./commands/*": { + "types": "./src/commands/*.ts", + "import": "./src/commands/*.ts" + }, + "./commit": { + "types": "./src/commit/index.ts", + "import": "./src/commit/index.ts" + }, + "./commit/*": { + "types": "./src/commit/*.ts", + "import": "./src/commit/*.ts" + }, + "./commit/agentic": { + "types": "./src/commit/agentic/index.ts", + "import": "./src/commit/agentic/index.ts" + }, + "./commit/agentic/*": { + "types": "./src/commit/agentic/*.ts", + "import": "./src/commit/agentic/*.ts" + }, + "./commit/agentic/tools": { + "types": "./src/commit/agentic/tools/index.ts", + "import": "./src/commit/agentic/tools/index.ts" + }, + "./commit/agentic/tools/*": { + "types": "./src/commit/agentic/tools/*.ts", + "import": "./src/commit/agentic/tools/*.ts" + }, + "./commit/analysis": { + "types": "./src/commit/analysis/index.ts", + "import": "./src/commit/analysis/index.ts" + }, + "./commit/analysis/*": { + "types": "./src/commit/analysis/*.ts", + "import": "./src/commit/analysis/*.ts" + }, + "./commit/changelog": { + "types": "./src/commit/changelog/index.ts", + "import": "./src/commit/changelog/index.ts" + }, + "./commit/changelog/*": { + "types": "./src/commit/changelog/*.ts", + "import": "./src/commit/changelog/*.ts" + }, + "./commit/git/*": { + "types": "./src/commit/git/*.ts", + "import": "./src/commit/git/*.ts" + }, + "./commit/map-reduce": { + "types": "./src/commit/map-reduce/index.ts", + "import": "./src/commit/map-reduce/index.ts" + }, + "./commit/map-reduce/*": { + "types": "./src/commit/map-reduce/*.ts", + "import": "./src/commit/map-reduce/*.ts" + }, + "./commit/utils/*": { + "types": "./src/commit/utils/*.ts", + "import": "./src/commit/utils/*.ts" + }, + "./config/*": { + "types": "./src/config/*.ts", + "import": "./src/config/*.ts" + }, + "./dap": { + "types": "./src/dap/index.ts", + "import": "./src/dap/index.ts" + }, + "./dap/*": { + "types": "./src/dap/*.ts", + "import": "./src/dap/*.ts" + }, + "./debug": { + "types": "./src/debug/index.ts", + "import": "./src/debug/index.ts" + }, + "./debug/*": { + "types": "./src/debug/*.ts", + "import": "./src/debug/*.ts" + }, + "./discovery": { + "types": "./src/discovery/index.ts", + "import": "./src/discovery/index.ts" + }, + "./discovery/*": { + "types": "./src/discovery/*.ts", + "import": "./src/discovery/*.ts" + }, + "./edit": { + "types": "./src/edit/index.ts", + "import": "./src/edit/index.ts" + }, + "./edit/*": { + "types": "./src/edit/*.ts", + "import": "./src/edit/*.ts" + }, + "./edit/modes/*": { + "types": "./src/edit/modes/*.ts", + "import": "./src/edit/modes/*.ts" + }, + "./exa": { + "types": "./src/exa/index.ts", + "import": "./src/exa/index.ts" + }, + "./exa/*": { + "types": "./src/exa/*.ts", + "import": "./src/exa/*.ts" + }, + "./exec/*": { + "types": "./src/exec/*.ts", + "import": "./src/exec/*.ts" + }, + "./export/*": { + "types": "./src/export/*.ts", + "import": "./src/export/*.ts" + }, + "./export/html": { + "types": "./src/export/html/index.ts", + "import": "./src/export/html/index.ts" + }, + "./export/html/*": { + "types": "./src/export/html/*.ts", + "import": "./src/export/html/*.ts" + }, + "./extensibility/*": { + "types": "./src/extensibility/*.ts", + "import": "./src/extensibility/*.ts" + }, + "./extensibility/custom-commands": { + "types": "./src/extensibility/custom-commands/index.ts", + "import": "./src/extensibility/custom-commands/index.ts" + }, + "./extensibility/custom-commands/*": { + "types": "./src/extensibility/custom-commands/*.ts", + "import": "./src/extensibility/custom-commands/*.ts" + }, + "./extensibility/custom-commands/bundled/ci-green": { + "types": "./src/extensibility/custom-commands/bundled/ci-green/index.ts", + "import": "./src/extensibility/custom-commands/bundled/ci-green/index.ts" + }, + "./extensibility/custom-commands/bundled/review": { + "types": "./src/extensibility/custom-commands/bundled/review/index.ts", + "import": "./src/extensibility/custom-commands/bundled/review/index.ts" + }, + "./extensibility/custom-tools": { + "types": "./src/extensibility/custom-tools/index.ts", + "import": "./src/extensibility/custom-tools/index.ts" + }, + "./extensibility/custom-tools/*": { + "types": "./src/extensibility/custom-tools/*.ts", + "import": "./src/extensibility/custom-tools/*.ts" + }, + "./extensibility/extensions": { + "types": "./src/extensibility/extensions/index.ts", + "import": "./src/extensibility/extensions/index.ts" + }, + "./extensibility/extensions/*": { + "types": "./src/extensibility/extensions/*.ts", + "import": "./src/extensibility/extensions/*.ts" + }, + "./extensibility/hooks": { + "types": "./src/extensibility/hooks/index.ts", + "import": "./src/extensibility/hooks/index.ts" + }, + "./extensibility/hooks/*": { + "types": "./src/extensibility/hooks/*.ts", + "import": "./src/extensibility/hooks/*.ts" + }, + "./extensibility/plugins": { + "types": "./src/extensibility/plugins/index.ts", + "import": "./src/extensibility/plugins/index.ts" + }, + "./extensibility/plugins/*": { + "types": "./src/extensibility/plugins/*.ts", + "import": "./src/extensibility/plugins/*.ts" + }, + "./extensibility/plugins/marketplace": { + "types": "./src/extensibility/plugins/marketplace/index.ts", + "import": "./src/extensibility/plugins/marketplace/index.ts" + }, + "./extensibility/plugins/marketplace/*": { + "types": "./src/extensibility/plugins/marketplace/*.ts", + "import": "./src/extensibility/plugins/marketplace/*.ts" + }, + "./internal-urls": { + "types": "./src/internal-urls/index.ts", + "import": "./src/internal-urls/index.ts" + }, + "./internal-urls/*": { + "types": "./src/internal-urls/*.ts", + "import": "./src/internal-urls/*.ts" + }, + "./eval": { + "types": "./src/eval/index.ts", + "import": "./src/eval/index.ts" + }, + "./eval/js/*": { + "types": "./src/eval/js/*.ts", + "import": "./src/eval/js/*.ts" + }, + "./eval/py/*": { + "types": "./src/eval/py/*.ts", + "import": "./src/eval/py/*.ts" + }, + "./lsp": { + "types": "./src/lsp/index.ts", + "import": "./src/lsp/index.ts" + }, + "./lsp/*": { + "types": "./src/lsp/*.ts", + "import": "./src/lsp/*.ts" + }, + "./lsp/clients": { + "types": "./src/lsp/clients/index.ts", + "import": "./src/lsp/clients/index.ts" + }, + "./lsp/clients/*": { + "types": "./src/lsp/clients/*.ts", + "import": "./src/lsp/clients/*.ts" + }, + "./markit": { + "types": "./src/markit/index.ts", + "import": "./src/markit/index.ts" + }, + "./markit/*": { + "types": "./src/markit/*.ts", + "import": "./src/markit/*.ts" + }, + "./mcp": { + "types": "./src/mcp/index.ts", + "import": "./src/mcp/index.ts" + }, + "./mcp/*": { + "types": "./src/mcp/*.ts", + "import": "./src/mcp/*.ts" + }, + "./mcp/transports": { + "types": "./src/mcp/transports/index.ts", + "import": "./src/mcp/transports/index.ts" + }, + "./mcp/transports/*": { + "types": "./src/mcp/transports/*.ts", + "import": "./src/mcp/transports/*.ts" + }, + "./memories": { + "types": "./src/memories/index.ts", + "import": "./src/memories/index.ts" + }, + "./memories/*": { + "types": "./src/memories/*.ts", + "import": "./src/memories/*.ts" + }, + "./memory-backend": { + "types": "./src/memory-backend/index.ts", + "import": "./src/memory-backend/index.ts" + }, + "./memory-backend/*": { + "types": "./src/memory-backend/*.ts", + "import": "./src/memory-backend/*.ts" + }, + "./hindsight": { + "types": "./src/hindsight/index.ts", + "import": "./src/hindsight/index.ts" + }, + "./hindsight/*": { + "types": "./src/hindsight/*.ts", + "import": "./src/hindsight/*.ts" + }, + "./modes": { + "types": "./src/modes/index.ts", + "import": "./src/modes/index.ts" + }, + "./modes/*": { + "types": "./src/modes/*.ts", + "import": "./src/modes/*.ts" + }, + "./modes/acp": { + "types": "./src/modes/acp/index.ts", + "import": "./src/modes/acp/index.ts" + }, + "./modes/acp/*": { + "types": "./src/modes/acp/*.ts", + "import": "./src/modes/acp/*.ts" + }, + "./modes/components": { + "types": "./src/modes/components/index.ts", + "import": "./src/modes/components/index.ts" + }, + "./modes/components/*": { + "types": "./src/modes/components/*.ts", + "import": "./src/modes/components/*.ts" + }, + "./modes/components/extensions": { + "types": "./src/modes/components/extensions/index.ts", + "import": "./src/modes/components/extensions/index.ts" + }, + "./modes/components/extensions/*": { + "types": "./src/modes/components/extensions/*.ts", + "import": "./src/modes/components/extensions/*.ts" + }, + "./modes/components/status-line": { + "types": "./src/modes/components/status-line/index.ts", + "import": "./src/modes/components/status-line/index.ts" + }, + "./modes/components/status-line/*": { + "types": "./src/modes/components/status-line/*.ts", + "import": "./src/modes/components/status-line/*.ts" + }, + "./modes/controllers/*": { + "types": "./src/modes/controllers/*.ts", + "import": "./src/modes/controllers/*.ts" + }, + "./modes/setup-wizard": { + "types": "./src/modes/setup-wizard/index.ts", + "import": "./src/modes/setup-wizard/index.ts" + }, + "./modes/setup-wizard/*": { + "types": "./src/modes/setup-wizard/*.ts", + "import": "./src/modes/setup-wizard/*.ts" + }, + "./modes/rpc/*": { + "types": "./src/modes/rpc/*.ts", + "import": "./src/modes/rpc/*.ts" + }, + "./modes/theme/*": { + "types": "./src/modes/theme/*.ts", + "import": "./src/modes/theme/*.ts" + }, + "./modes/theme/defaults": { + "types": "./src/modes/theme/defaults/index.ts", + "import": "./src/modes/theme/defaults/index.ts" + }, + "./modes/utils/*": { + "types": "./src/modes/utils/*.ts", + "import": "./src/modes/utils/*.ts" + }, + "./plan-mode/*": { + "types": "./src/plan-mode/*.ts", + "import": "./src/plan-mode/*.ts" + }, + "./prompts/*": "./src/prompts/*.md", + "./secrets": { + "types": "./src/secrets/index.ts", + "import": "./src/secrets/index.ts" + }, + "./secrets/*": { + "types": "./src/secrets/*.ts", + "import": "./src/secrets/*.ts" + }, + "./session/*": { + "types": "./src/session/*.ts", + "import": "./src/session/*.ts" + }, + "./slash-commands/*": { + "types": "./src/slash-commands/*.ts", + "import": "./src/slash-commands/*.ts" + }, + "./ssh/*": { + "types": "./src/ssh/*.ts", + "import": "./src/ssh/*.ts" + }, + "./stt": { + "types": "./src/stt/index.ts", + "import": "./src/stt/index.ts" + }, + "./stt/*": { + "types": "./src/stt/*.ts", + "import": "./src/stt/*.ts" + }, + "./task": { + "types": "./src/task/index.ts", + "import": "./src/task/index.ts" + }, + "./task/*": { + "types": "./src/task/*.ts", + "import": "./src/task/*.ts" + }, + "./tool-discovery/*": { + "types": "./src/tool-discovery/*.ts", + "import": "./src/tool-discovery/*.ts" + }, + "./tools": { + "types": "./src/tools/index.ts", + "import": "./src/tools/index.ts" + }, + "./tools/*": { + "types": "./src/tools/*.ts", + "import": "./src/tools/*.ts" + }, + "./tui": { + "types": "./src/tui/index.ts", + "import": "./src/tui/index.ts" + }, + "./tui/*": { + "types": "./src/tui/*.ts", + "import": "./src/tui/*.ts" + }, + "./utils/*": { + "types": "./src/utils/*.ts", + "import": "./src/utils/*.ts" + }, + "./web/*": { + "types": "./src/web/*.ts", + "import": "./src/web/*.ts" + }, + "./web/scrapers": { + "types": "./src/web/scrapers/index.ts", + "import": "./src/web/scrapers/index.ts" + }, + "./web/scrapers/*": { + "types": "./src/web/scrapers/*.ts", + "import": "./src/web/scrapers/*.ts" + }, + "./web/search": { + "types": "./src/web/search/index.ts", + "import": "./src/web/search/index.ts" + }, + "./web/search/*": { + "types": "./src/web/search/*.ts", + "import": "./src/web/search/*.ts" + }, + "./web/search/providers/*": { + "types": "./src/web/search/providers/*.ts", + "import": "./src/web/search/providers/*.ts" + }, + "./*.js": "./src/*.ts" + } } diff --git a/packages/coding-agent/scripts/build-binary.ts b/packages/coding-agent/scripts/build-binary.ts index 8bd3a4168..cf8ae3ebd 100644 --- a/packages/coding-agent/scripts/build-binary.ts +++ b/packages/coding-agent/scripts/build-binary.ts @@ -49,8 +49,8 @@ async function main(): Promise { // Generate inside the try so the finally always restores the empty checked-in // placeholders (stats client archive, docs index) even on failure. try { - await runCommand(["bun", "--cwd=../stats", "scripts/generate-client-bundle.ts", "--generate"]); - await runCommand(["bun", "scripts/generate-docs-index.ts", "--generate"]); + await runCommand(["bun", "--cwd=../stats", "run", "gen:stats"]); + await runCommand(["bun", "run", "gen:docs"]); // `legacy-pi-bundled-registry.ts` static-imports // `@oh-my-pi/pi-coding-agent/export/html` (one of pi-coding-agent's // named subpath exports, see scripts/generate-legacy-pi-bundled-registry.ts), @@ -61,14 +61,14 @@ async function main(): Promise { // resolving the missing generated bundle. Rebuilding the tool views // here makes the compile self-contained and matches what `prepack` // does for the npm bundle. - await runCommand(["bun", "--cwd=../collab-web", "run", "build:tool-views"]); + await runCommand(["bun", "--cwd=../collab-web", "run", "gen:tool-views"]); await runCommand( - ["bun", "--cwd=../natives", "run", "embed:native"], + ["bun", "--cwd=../natives", "run", "gen:native"], crossTarget ? { ...Bun.env, TARGET_PLATFORM: crossPlatform as string, TARGET_ARCH: crossArch as string } : Bun.env, ); - await runCommand(["bun", "scripts/embed-mupdf-wasm.ts", "--generate"]); + await runCommand(["bun", "run", "gen:mupdf"]); // Regenerate the bundled-pi registry + key set before the compile so any // new pi-* subpath export added under `packages/*/package.json` is served // from the host's in-process copy. Without this, `bun build --compile` @@ -123,12 +123,12 @@ async function main(): Promise { await runCommand(["codesign", "--force", "--sign", "-", outputPath]); } } finally { - await runCommand(["bun", "scripts/embed-mupdf-wasm.ts", "--reset"]); - await runCommand(["bun", "--cwd=../natives", "run", "embed:native", "--reset"]); + await runCommand(["bun", "run", "gen:mupdf:reset"]); + await runCommand(["bun", "--cwd=../natives", "run", "gen:native:reset"]); } } finally { - await runCommand(["bun", "--cwd=../stats", "scripts/generate-client-bundle.ts", "--reset"]); - await runCommand(["bun", "scripts/generate-docs-index.ts", "--reset"]); + await runCommand(["bun", "--cwd=../stats", "run", "gen:stats:reset"]); + await runCommand(["bun", "run", "gen:docs:reset"]); } } diff --git a/packages/coding-agent/scripts/bundle-dist.ts b/packages/coding-agent/scripts/bundle-dist.ts index 322121802..010b8cf3b 100755 --- a/packages/coding-agent/scripts/bundle-dist.ts +++ b/packages/coding-agent/scripts/bundle-dist.ts @@ -79,7 +79,7 @@ async function main(): Promise { // so embed the dashboard archive the same way compiled binaries do // (scripts/build-binary.ts). Reset afterwards to keep the checked-in // placeholder empty. - await runCommand(["bun", "--cwd=../stats", "scripts/generate-client-bundle.ts", "--generate"]); + await runCommand(["bun", "--cwd=../stats", "run", "gen:stats"]); try { await runCommand([ "bun", @@ -97,7 +97,7 @@ async function main(): Promise { "./src/cli.ts", ]); } finally { - await runCommand(["bun", "--cwd=../stats", "scripts/generate-client-bundle.ts", "--reset"]); + await runCommand(["bun", "--cwd=../stats", "run", "gen:stats:reset"]); } await ensureShebang(); const stat = await fs.stat(cliPath); diff --git a/packages/coding-agent/src/export/html/index.ts b/packages/coding-agent/src/export/html/index.ts index e22f2e2ee..5b7b06e03 100644 --- a/packages/coding-agent/src/export/html/index.ts +++ b/packages/coding-agent/src/export/html/index.ts @@ -9,7 +9,7 @@ import { SessionManager } from "../../session/session-manager"; import templateCss from "./template.css" with { type: "text" }; import templateHtml from "./template.html" with { type: "text" }; import templateJs from "./template.js" with { type: "text" }; -// Pre-built React tool renderers: built by `bun --cwd=packages/collab-web run build:tool-views`, +// Pre-built React tool renderers: built by `gen:tool-views` (`bun run gen:tool-views`), // run automatically by root `prepare` on install and by `prepack` at publish. import toolViewsJs from "./tool-views.generated.js" with { type: "text" }; import { webExportThemeVars } from "./web-palette"; diff --git a/packages/coding-agent/src/internal-urls/docs-index.ts b/packages/coding-agent/src/internal-urls/docs-index.ts index c35b794b1..77ccf1f6d 100644 --- a/packages/coding-agent/src/internal-urls/docs-index.ts +++ b/packages/coding-agent/src/internal-urls/docs-index.ts @@ -2,8 +2,7 @@ * Harness documentation index for the `omp://` protocol. * * Compiled binaries and the prepacked npm bundle inline a compressed index from - * `docs-index.generated.txt` (populated by `scripts/generate-docs-index.ts - * --generate` at build time). The format is two lines: + * `docs-index.generated.txt` (populated by `gen:docs` at build time). The format is two lines: * 1. a plain JSON array of the sorted doc file names, and * 2. a base64 gzip blob of the index-aligned doc bodies (`string[]`). * Listing/completion (`getDocFilenames`) parses only the small first line and @@ -84,7 +83,7 @@ function getIndex(): DocsIndex { if (decoded === null) { throw new Error( "Malformed embedded docs index (docs-index.generated.txt): non-empty payload without a newline separator. " + - "Rebuild with `bun --cwd=packages/coding-agent scripts/generate-docs-index.ts --generate`.", + "Rebuild with `bun run gen:docs`.", ); } index = decoded; diff --git a/packages/coding-agent/src/task/render.ts b/packages/coding-agent/src/task/render.ts index 439932ce0..459ea0624 100644 --- a/packages/coding-agent/src/task/render.ts +++ b/packages/coding-agent/src/task/render.ts @@ -18,6 +18,7 @@ import { formatExpandHint, formatMoreItems, formatStatusIcon, + previewLine, replaceTabs, type ToolUIStatus, truncateToWidth, @@ -749,7 +750,7 @@ function renderAgentProgress( const showBadge = settings.get("task.showResolvedModelBadge"); if (progress.status === "running") { if (!description) { - const taskPreview = truncateToWidth(progress.assignment ?? progress.task, 40); + const taskPreview = previewLine(progress.assignment ?? progress.task, 40); statusLine += ` ${theme.fg("muted", taskPreview)}`; } statusLine = appendAgentStats(statusLine, { ...progress, showResolvedModelBadge: showBadge }, theme); @@ -767,7 +768,7 @@ function renderAgentProgress( let toolLine = `${continuePrefix}${theme.tree.hook} ${theme.fg("muted", progress.currentTool)}`; const toolDetail = progress.lastIntent ?? progress.currentToolArgs; if (toolDetail) { - toolLine += `: ${theme.fg("dim", truncateToWidth(replaceTabs(toolDetail), 40))}`; + toolLine += `: ${theme.fg("dim", previewLine(toolDetail, 40))}`; } if (progress.currentToolStartMs) { const elapsed = Date.now() - progress.currentToolStartMs; @@ -782,7 +783,7 @@ function renderAgentProgress( let toolLine = `${continuePrefix}${theme.tree.hook} ${theme.fg("dim", recent.tool)}`; const toolDetail = progress.lastIntent ?? recent.args; if (toolDetail) { - toolLine += `: ${theme.fg("dim", truncateToWidth(replaceTabs(toolDetail), 40))}`; + toolLine += `: ${theme.fg("dim", previewLine(toolDetail, 40))}`; } lines.push(toolLine); } @@ -796,12 +797,12 @@ function renderAgentProgress( const waitLabel = remainingMs > 0 ? `in ${formatDuration(remainingMs)}` : "now"; const summary = `retrying ${progress.retryState.attempt}/${progress.retryState.maxAttempts} ${waitLabel}: ` + - truncateToWidth(replaceTabs(progress.retryState.errorMessage), 60); + previewLine(progress.retryState.errorMessage, 60); lines.push(`${continuePrefix}${theme.tree.hook} ${theme.fg("warning", summary)}`); } else if (progress.retryFailure && progress.status !== "running") { const summary = `auto-retry gave up after ${progress.retryFailure.attempt} attempt${ progress.retryFailure.attempt === 1 ? "" : "s" - }: ${truncateToWidth(replaceTabs(progress.retryFailure.errorMessage), 80)}`; + }: ${previewLine(progress.retryFailure.errorMessage, 80)}`; lines.push(`${continuePrefix}${theme.tree.hook} ${theme.fg("error", summary)}`); } @@ -1064,7 +1065,7 @@ function renderAgentResult( if (aborted && result.abortReason) { lines.push( - `${continuePrefix}${theme.fg("error", theme.status.aborted)} ${theme.fg("dim", truncateToWidth(replaceTabs(result.abortReason), 80))}`, + `${continuePrefix}${theme.fg("error", theme.status.aborted)} ${theme.fg("dim", previewLine(result.abortReason, 80))}`, ); } // Check for review result (yield with review schema + report_finding) @@ -1176,9 +1177,7 @@ function renderAgentResult( // Error message if (result.error && (!success || mergeFailed) && (!aborted || result.error !== result.abortReason)) { - lines.push( - `${continuePrefix}${theme.fg(mergeFailed ? "warning" : "error", truncateToWidth(replaceTabs(result.error), 70))}`, - ); + lines.push(`${continuePrefix}${theme.fg(mergeFailed ? "warning" : "error", previewLine(result.error, 70))}`); } return lines; diff --git a/packages/coding-agent/src/tools/render-utils.ts b/packages/coding-agent/src/tools/render-utils.ts index 17dc5a0b3..8161e06d6 100644 --- a/packages/coding-agent/src/tools/render-utils.ts +++ b/packages/coding-agent/src/tools/render-utils.ts @@ -99,6 +99,17 @@ export function getPreviewLines(text: string, maxLines: number, maxLineLen: numb return lines.slice(0, maxLines).map(l => truncateToWidth(l.trim(), maxLineLen, ellipsis)); } +/** + * Collapse a possibly multi-line string into a single line, then truncate it to + * `maxWidth` display cells. {@link truncateToWidth} alone caps width but + * newlines are zero-width, so multi-line content (markdown briefs, tool args, + * provider errors) would otherwise spill a single status row across several + * visual lines. Whitespace runs collapse to one space, so tabs are handled too. + */ +export function previewLine(text: string, maxWidth: number, ellipsis?: Ellipsis): string { + return truncateToWidth(text.replace(/\s+/g, " ").trim(), maxWidth, ellipsis); +} + // ============================================================================= // URL Utilities // ============================================================================= diff --git a/packages/collab-web/CHANGELOG.md b/packages/collab-web/CHANGELOG.md index f30352b91..00aa8e366 100644 --- a/packages/collab-web/CHANGELOG.md +++ b/packages/collab-web/CHANGELOG.md @@ -4,8 +4,7 @@ ### Added -- Added dedicated renderers for `glob` and `grep` tools to improve result readability -- Enabled display support for legacy `find` and `search` tool outputs via new renderers +- Added dedicated renderers for glob, grep, and legacy find and search tools to improve the readability of search and file discovery results. ## [16.1.23] - 2026-06-26 diff --git a/packages/collab-web/package.json b/packages/collab-web/package.json index adb55b211..21b332cfe 100644 --- a/packages/collab-web/package.json +++ b/packages/collab-web/package.json @@ -1,63 +1,63 @@ { - "type": "module", - "name": "@oh-my-pi/collab-web", - "version": "15.11.7", - "private": true, - "description": "Browser guest client and local relay tools for omp collab live sessions", - "homepage": "https://omp.sh", - "author": "Can Boluk", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/can1357/oh-my-pi.git", - "directory": "packages/collab-web" - }, - "bugs": { - "url": "https://github.com/can1357/oh-my-pi/issues" - }, - "keywords": [ - "collab", - "relay", - "websocket", - "react", - "agent" - ], - "scripts": { - "dev": "bun ./index.html", - "mock-host": "bun scripts/mock-host.ts", - "relay": "bun scripts/local-relay.ts", - "build": "rm -rf dist && bun build ./index.html --outdir=dist --minify --entry-naming=[hash].[ext] --chunk-naming=[hash].[ext] --asset-naming=[hash].[ext] && mv dist/*.html dist/index.html && cp -R public/. dist/", - "build:tool-views": "bun scripts/build-tool-views.ts", - "prepack": "bun run build", - "test": "bun test --parallel", - "check": "biome check . && bun run check:types", - "check:types": "tsgo -p tsconfig.json --noEmit", - "lint": "biome lint .", - "fix": "biome check --write --unsafe .", - "fmt": "biome format --write ." - }, - "dependencies": { - "@oh-my-pi/pi-wire": "catalog:", - "lucide-react": "catalog:", - "marked": "catalog:", - "react": "catalog:", - "react-dom": "catalog:" - }, - "devDependencies": { - "@types/bun": "catalog:", - "@types/react": "catalog:", - "@types/react-dom": "catalog:" - }, - "engines": { - "bun": ">=1.3.14" - }, - "files": [ - "dist", - "src", - "scripts", - "public", - "index.html", - "README.md", - "CHANGELOG.md" - ] + "type": "module", + "name": "@oh-my-pi/collab-web", + "version": "15.11.7", + "private": true, + "description": "Browser guest client and local relay tools for omp collab live sessions", + "homepage": "https://omp.sh", + "author": "Can Boluk", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/can1357/oh-my-pi.git", + "directory": "packages/collab-web" + }, + "bugs": { + "url": "https://github.com/can1357/oh-my-pi/issues" + }, + "keywords": [ + "collab", + "relay", + "websocket", + "react", + "agent" + ], + "scripts": { + "dev": "bun ./index.html", + "mock-host": "bun scripts/mock-host.ts", + "relay": "bun scripts/local-relay.ts", + "build": "rm -rf dist && bun build ./index.html --outdir=dist --minify --entry-naming=[hash].[ext] --chunk-naming=[hash].[ext] --asset-naming=[hash].[ext] && mv dist/*.html dist/index.html && cp -R public/. dist/", + "gen:tool-views": "bun scripts/build-tool-views.ts", + "prepack": "bun run build", + "test": "bun test --parallel", + "check": "biome check . && bun run check:types", + "check:types": "tsgo -p tsconfig.json --noEmit", + "lint": "biome lint .", + "fix": "biome check --write --unsafe .", + "fmt": "biome format --write ." + }, + "dependencies": { + "@oh-my-pi/pi-wire": "catalog:", + "lucide-react": "catalog:", + "marked": "catalog:", + "react": "catalog:", + "react-dom": "catalog:" + }, + "devDependencies": { + "@types/bun": "catalog:", + "@types/react": "catalog:", + "@types/react-dom": "catalog:" + }, + "engines": { + "bun": ">=1.3.14" + }, + "files": [ + "dist", + "src", + "scripts", + "public", + "index.html", + "README.md", + "CHANGELOG.md" + ] } diff --git a/packages/collab-web/scripts/build-tool-views.ts b/packages/collab-web/scripts/build-tool-views.ts index 08ad33fd4..b181b395f 100755 --- a/packages/collab-web/scripts/build-tool-views.ts +++ b/packages/collab-web/scripts/build-tool-views.ts @@ -5,7 +5,7 @@ * embedding in coding-agent HTML session exports. * * Output: packages/coding-agent/src/export/html/tool-views.generated.js - * Run via `bun run build:tool-views` after changing src/tool-render/. + * Run via `bun run gen:tool-views` after changing src/tool-render/. */ import * as path from "node:path"; diff --git a/packages/hashline/CHANGELOG.md b/packages/hashline/CHANGELOG.md index df00706ee..8a751a242 100644 --- a/packages/hashline/CHANGELOG.md +++ b/packages/hashline/CHANGELOG.md @@ -4,9 +4,7 @@ ### Added -- Added `REM` section op to delete files -- Added `MV` section op to rename or move files, including snapshot history migration -- Added whole-file `REM` and `MV DEST` section ops so a hashline patch can delete or rename/move files (optionally after line edits) without leaving the edit tool. `MV` relocates read/snapshot history to the destination path. +- Added `REM` (remove) and `MV` (move/rename) section operations to hashline patches, allowing files to be deleted or relocated (with snapshot history migration) directly within the edit tool. ## [16.1.23] - 2026-06-26 diff --git a/packages/hashline/package.json b/packages/hashline/package.json index c7054104f..51966c6ee 100644 --- a/packages/hashline/package.json +++ b/packages/hashline/package.json @@ -1,63 +1,63 @@ { - "type": "module", - "name": "@oh-my-pi/hashline", - "version": "16.1.23", - "description": "Hashline: a compact, line-anchored patch language and applier. Pluggable FS/IO so it works over disk, in-memory, or any custom backend.", - "homepage": "https://omp.sh", - "author": "Can Boluk", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/can1357/oh-my-pi.git", - "directory": "packages/hashline" - }, - "bugs": { - "url": "https://github.com/can1357/oh-my-pi/issues" - }, - "keywords": [ - "patch", - "diff", - "edit", - "hashline", - "agent", - "llm" - ], - "main": "./src/index.ts", - "types": "./src/index.ts", - "scripts": { - "check": "biome check . && bun run check:types", - "check:types": "tsgo -p tsconfig.json --noEmit", - "lint": "biome lint .", - "test": "bun test --parallel", - "fix": "biome check --write --unsafe .", - "fmt": "biome format --write ." - }, - "dependencies": { - "diff": "catalog:", - "lru-cache": "catalog:" - }, - "devDependencies": { - "@types/bun": "catalog:" - }, - "engines": { - "bun": ">=1.3.14" - }, - "files": [ - "src", - "README.md", - "CHANGELOG.md" - ], - "exports": { - ".": { - "types": "./src/index.ts", - "import": "./src/index.ts" - }, - "./grammar.lark": "./src/grammar.lark", - "./prompt.md": "./src/prompt.md", - "./*": { - "types": "./src/*.ts", - "import": "./src/*.ts" - }, - "./*.js": "./src/*.ts" - } + "type": "module", + "name": "@oh-my-pi/hashline", + "version": "16.1.23", + "description": "Hashline: a compact, line-anchored patch language and applier. Pluggable FS/IO so it works over disk, in-memory, or any custom backend.", + "homepage": "https://omp.sh", + "author": "Can Boluk", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/can1357/oh-my-pi.git", + "directory": "packages/hashline" + }, + "bugs": { + "url": "https://github.com/can1357/oh-my-pi/issues" + }, + "keywords": [ + "patch", + "diff", + "edit", + "hashline", + "agent", + "llm" + ], + "main": "./src/index.ts", + "types": "./src/index.ts", + "scripts": { + "check": "biome check . && bun run check:types", + "check:types": "tsgo -p tsconfig.json --noEmit", + "lint": "biome lint .", + "test": "bun test --parallel", + "fix": "biome check --write --unsafe .", + "fmt": "biome format --write ." + }, + "dependencies": { + "diff": "catalog:", + "lru-cache": "catalog:" + }, + "devDependencies": { + "@types/bun": "catalog:" + }, + "engines": { + "bun": ">=1.3.14" + }, + "files": [ + "src", + "README.md", + "CHANGELOG.md" + ], + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./src/index.ts" + }, + "./grammar.lark": "./src/grammar.lark", + "./prompt.md": "./src/prompt.md", + "./*": { + "types": "./src/*.ts", + "import": "./src/*.ts" + }, + "./*.js": "./src/*.ts" + } } diff --git a/packages/mnemopi/package.json b/packages/mnemopi/package.json index 8c9eebf99..8f807d847 100644 --- a/packages/mnemopi/package.json +++ b/packages/mnemopi/package.json @@ -1,107 +1,107 @@ { - "type": "module", - "name": "@oh-my-pi/pi-mnemopi", - "version": "16.1.23", - "description": "Local SQLite memory engine for Oh My Pi agents", - "homepage": "https://omp.sh", - "author": "Can Boluk", - "contributors": [ - "Abdias J" - ], - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/can1357/oh-my-pi.git", - "directory": "packages/mnemopi" - }, - "bugs": { - "url": "https://github.com/can1357/oh-my-pi/issues" - }, - "keywords": [ - "memory", - "sqlite", - "agent", - "embeddings", - "mcp" - ], - "main": "./src/index.ts", - "module": "./src/index.ts", - "types": "./src/index.ts", - "bin": { - "mnemopi": "src/cli.ts" - }, - "scripts": { - "check": "biome check . && bun run check:types", - "check:types": "tsgo -p tsconfig.json --noEmit", - "lint": "biome lint .", - "test": "bun test --parallel", - "fix": "biome check --write --unsafe .", - "fmt": "biome format --write ." - }, - "dependencies": { - "@oh-my-pi/pi-ai": "catalog:", - "@oh-my-pi/pi-catalog": "catalog:", - "@oh-my-pi/pi-utils": "catalog:", - "lru-cache": "catalog:" - }, - "peerDependencies": { - "fastembed": "2.1.0", - "onnxruntime-node": "1.21.0" - }, - "peerDependenciesMeta": { - "fastembed": { - "optional": true - }, - "onnxruntime-node": { - "optional": true - } - }, - "devDependencies": { - "@types/bun": "catalog:", - "fastembed": "catalog:", - "onnxruntime-node": "catalog:" - }, - "engines": { - "bun": ">=1.3.14" - }, - "files": [ - "src", - "README.md", - "CHANGELOG.md" - ], - "exports": { - ".": { - "types": "./src/index.ts", - "import": "./src/index.ts" - }, - "./core": { - "types": "./src/core/index.ts", - "import": "./src/core/index.ts" - }, - "./core/beam": { - "types": "./src/core/beam/index.ts", - "import": "./src/core/beam/index.ts" - }, - "./beam": { - "types": "./src/core/beam/index.ts", - "import": "./src/core/beam/index.ts" - }, - "./diagnose": { - "types": "./src/diagnose.ts", - "import": "./src/diagnose.ts" - }, - "./mcp": { - "types": "./src/mcp-tools.ts", - "import": "./src/mcp-tools.ts" - }, - "./cli": { - "types": "./src/cli.ts", - "import": "./src/cli.ts" - }, - "./*": { - "types": "./src/*.ts", - "import": "./src/*.ts" - }, - "./*.js": "./src/*.ts" - } + "type": "module", + "name": "@oh-my-pi/pi-mnemopi", + "version": "16.1.23", + "description": "Local SQLite memory engine for Oh My Pi agents", + "homepage": "https://omp.sh", + "author": "Can Boluk", + "contributors": [ + "Abdias J" + ], + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/can1357/oh-my-pi.git", + "directory": "packages/mnemopi" + }, + "bugs": { + "url": "https://github.com/can1357/oh-my-pi/issues" + }, + "keywords": [ + "memory", + "sqlite", + "agent", + "embeddings", + "mcp" + ], + "main": "./src/index.ts", + "module": "./src/index.ts", + "types": "./src/index.ts", + "bin": { + "mnemopi": "src/cli.ts" + }, + "scripts": { + "check": "biome check . && bun run check:types", + "check:types": "tsgo -p tsconfig.json --noEmit", + "lint": "biome lint .", + "test": "bun test --parallel", + "fix": "biome check --write --unsafe .", + "fmt": "biome format --write ." + }, + "dependencies": { + "@oh-my-pi/pi-ai": "catalog:", + "@oh-my-pi/pi-catalog": "catalog:", + "@oh-my-pi/pi-utils": "catalog:", + "lru-cache": "catalog:" + }, + "peerDependencies": { + "fastembed": "2.1.0", + "onnxruntime-node": "1.21.0" + }, + "peerDependenciesMeta": { + "fastembed": { + "optional": true + }, + "onnxruntime-node": { + "optional": true + } + }, + "devDependencies": { + "@types/bun": "catalog:", + "fastembed": "catalog:", + "onnxruntime-node": "catalog:" + }, + "engines": { + "bun": ">=1.3.14" + }, + "files": [ + "src", + "README.md", + "CHANGELOG.md" + ], + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./src/index.ts" + }, + "./core": { + "types": "./src/core/index.ts", + "import": "./src/core/index.ts" + }, + "./core/beam": { + "types": "./src/core/beam/index.ts", + "import": "./src/core/beam/index.ts" + }, + "./beam": { + "types": "./src/core/beam/index.ts", + "import": "./src/core/beam/index.ts" + }, + "./diagnose": { + "types": "./src/diagnose.ts", + "import": "./src/diagnose.ts" + }, + "./mcp": { + "types": "./src/mcp-tools.ts", + "import": "./src/mcp-tools.ts" + }, + "./cli": { + "types": "./src/cli.ts", + "import": "./src/cli.ts" + }, + "./*": { + "types": "./src/*.ts", + "import": "./src/*.ts" + }, + "./*.js": "./src/*.ts" + } } diff --git a/packages/natives/package.json b/packages/natives/package.json index fcedaa158..2d340017f 100644 --- a/packages/natives/package.json +++ b/packages/natives/package.json @@ -36,7 +36,8 @@ "test": "bun test --parallel", "fix": "biome check --write --unsafe .", "fmt": "biome format --write .", - "embed:native": "bun scripts/embed-native.ts", + "gen:native": "bun scripts/embed-native.ts", + "gen:native:reset": "bun scripts/embed-native.ts --reset", "gen:npm": "bun scripts/gen-npm-packages.ts", "bench": "bun bench/grep.ts" }, diff --git a/packages/snapcompact/package.json b/packages/snapcompact/package.json index f81d6910b..9e5493ec8 100644 --- a/packages/snapcompact/package.json +++ b/packages/snapcompact/package.json @@ -1,64 +1,64 @@ { - "type": "module", - "name": "@oh-my-pi/snapcompact", - "version": "16.1.23", - "description": "Bitmap-frame context compression for vision-capable LLMs", - "homepage": "https://omp.sh", - "author": "Can Boluk", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/can1357/oh-my-pi.git", - "directory": "packages/snapcompact" - }, - "bugs": { - "url": "https://github.com/can1357/oh-my-pi/issues" - }, - "keywords": [ - "context-compression", - "vision", - "compaction", - "llm" - ], - "main": "./src/index.ts", - "types": "./src/index.ts", - "scripts": { - "check": "biome check . && bun run check:types", - "check:types": "tsgo -p tsconfig.json --noEmit", - "lint": "biome lint .", - "test": "bun test --parallel", - "fix": "biome check --write --unsafe .", - "fmt": "biome format --write ." - }, - "dependencies": { - "@oh-my-pi/pi-ai": "catalog:", - "@oh-my-pi/pi-natives": "catalog:", - "@oh-my-pi/pi-utils": "catalog:", - "@oh-my-pi/pi-wire": "catalog:" - }, - "devDependencies": { - "@types/bun": "catalog:" - }, - "engines": { - "bun": ">=1.3.14" - }, - "files": [ - "src", - "README.md", - "CHANGELOG.md" - ], - "exports": { - ".": { - "types": "./src/index.ts", - "import": "./src/index.ts" - }, - "./snapcompact": { - "types": "./src/snapcompact.ts", - "import": "./src/snapcompact.ts" - }, - "./*": { - "types": "./src/*.ts", - "import": "./src/*.ts" - } - } + "type": "module", + "name": "@oh-my-pi/snapcompact", + "version": "16.1.23", + "description": "Bitmap-frame context compression for vision-capable LLMs", + "homepage": "https://omp.sh", + "author": "Can Boluk", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/can1357/oh-my-pi.git", + "directory": "packages/snapcompact" + }, + "bugs": { + "url": "https://github.com/can1357/oh-my-pi/issues" + }, + "keywords": [ + "context-compression", + "vision", + "compaction", + "llm" + ], + "main": "./src/index.ts", + "types": "./src/index.ts", + "scripts": { + "check": "biome check . && bun run check:types", + "check:types": "tsgo -p tsconfig.json --noEmit", + "lint": "biome lint .", + "test": "bun test --parallel", + "fix": "biome check --write --unsafe .", + "fmt": "biome format --write ." + }, + "dependencies": { + "@oh-my-pi/pi-ai": "catalog:", + "@oh-my-pi/pi-natives": "catalog:", + "@oh-my-pi/pi-utils": "catalog:", + "@oh-my-pi/pi-wire": "catalog:" + }, + "devDependencies": { + "@types/bun": "catalog:" + }, + "engines": { + "bun": ">=1.3.14" + }, + "files": [ + "src", + "README.md", + "CHANGELOG.md" + ], + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./src/index.ts" + }, + "./snapcompact": { + "types": "./src/snapcompact.ts", + "import": "./src/snapcompact.ts" + }, + "./*": { + "types": "./src/*.ts", + "import": "./src/*.ts" + } + } } diff --git a/packages/stats/CHANGELOG.md b/packages/stats/CHANGELOG.md index 0e1729fe7..5fcc8b835 100644 --- a/packages/stats/CHANGELOG.md +++ b/packages/stats/CHANGELOG.md @@ -4,7 +4,7 @@ ### Added -- Gain tab in `omp stats` dashboard (`/#/gain`) — surfaces snapcompact token-savings with project scoping from synced session folders ([#3543](https://github.com/can1357/oh-my-pi/pull/3543)). +- Added a Gain tab to the `omp stats` dashboard (`/#/gain`) to display snapcompact token-savings with project scoping from synced session folders. ## [16.1.17] - 2026-06-24 diff --git a/packages/stats/package.json b/packages/stats/package.json index 8904e92cd..510988f1e 100644 --- a/packages/stats/package.json +++ b/packages/stats/package.json @@ -1,91 +1,93 @@ { - "type": "module", - "name": "@oh-my-pi/omp-stats", - "version": "16.1.23", - "description": "Local observability dashboard for pi AI usage statistics", - "homepage": "https://omp.sh", - "author": "Can Boluk", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/can1357/oh-my-pi.git", - "directory": "packages/stats" - }, - "bugs": { - "url": "https://github.com/can1357/oh-my-pi/issues" - }, - "keywords": [ - "ai", - "observability", - "metrics", - "dashboard", - "llm", - "statistics" - ], - "main": "./src/index.ts", - "types": "./src/index.ts", - "bin": { - "omp-stats": "./src/index.ts" - }, - "scripts": { - "build": "bun run build.ts", - "dev": "bun run src/index.ts", - "check": "biome check . && bun run check:types", - "check:types": "tsgo -p tsconfig.json --noEmit && tsgo -p tsconfig.client.json --noEmit", - "lint": "biome lint .", - "fix": "biome check --write --unsafe .", - "fmt": "biome format --write ." - }, - "dependencies": { - "@oh-my-pi/pi-ai": "catalog:", - "@oh-my-pi/pi-catalog": "catalog:", - "@oh-my-pi/pi-utils": "catalog:", - "@tailwindcss/node": "catalog:", - "chart.js": "catalog:", - "date-fns": "catalog:", - "lucide-react": "catalog:", - "react": "catalog:", - "react-chartjs-2": "catalog:", - "react-dom": "catalog:", - "tailwindcss": "catalog:" - }, - "devDependencies": { - "@types/bun": "catalog:", - "@types/react": "catalog:", - "@types/react-dom": "catalog:", - "postcss": "catalog:" - }, - "engines": { - "bun": ">=1.3.14" - }, - "files": [ - "src", - "build.ts", - "tailwind.config.js", - "README.md", - "CHANGELOG.md" - ], - "exports": { - ".": { - "types": "./src/index.ts", - "import": "./src/index.ts" - }, - "./*": { - "types": "./src/*.ts", - "import": "./src/*.ts" - }, - "./client": { - "types": "./src/client/index.tsx", - "import": "./src/client/index.tsx" - }, - "./client/*": { - "types": "./src/client/*.ts", - "import": "./src/client/*.ts" - }, - "./client/components/*": { - "types": "./src/client/components/*.ts", - "import": "./src/client/components/*.ts" - }, - "./*.js": "./src/*.ts" - } + "type": "module", + "name": "@oh-my-pi/omp-stats", + "version": "16.1.23", + "description": "Local observability dashboard for pi AI usage statistics", + "homepage": "https://omp.sh", + "author": "Can Boluk", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/can1357/oh-my-pi.git", + "directory": "packages/stats" + }, + "bugs": { + "url": "https://github.com/can1357/oh-my-pi/issues" + }, + "keywords": [ + "ai", + "observability", + "metrics", + "dashboard", + "llm", + "statistics" + ], + "main": "./src/index.ts", + "types": "./src/index.ts", + "bin": { + "omp-stats": "./src/index.ts" + }, + "scripts": { + "gen:stats": "bun scripts/generate-client-bundle.ts --generate", + "gen:stats:reset": "bun scripts/generate-client-bundle.ts --reset", + "build": "bun run build.ts", + "dev": "bun run src/index.ts", + "check": "biome check . && bun run check:types", + "check:types": "tsgo -p tsconfig.json --noEmit && tsgo -p tsconfig.client.json --noEmit", + "lint": "biome lint .", + "fix": "biome check --write --unsafe .", + "fmt": "biome format --write ." + }, + "dependencies": { + "@oh-my-pi/pi-ai": "catalog:", + "@oh-my-pi/pi-catalog": "catalog:", + "@oh-my-pi/pi-utils": "catalog:", + "@tailwindcss/node": "catalog:", + "chart.js": "catalog:", + "date-fns": "catalog:", + "lucide-react": "catalog:", + "react": "catalog:", + "react-chartjs-2": "catalog:", + "react-dom": "catalog:", + "tailwindcss": "catalog:" + }, + "devDependencies": { + "@types/bun": "catalog:", + "@types/react": "catalog:", + "@types/react-dom": "catalog:", + "postcss": "catalog:" + }, + "engines": { + "bun": ">=1.3.14" + }, + "files": [ + "src", + "build.ts", + "tailwind.config.js", + "README.md", + "CHANGELOG.md" + ], + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./src/index.ts" + }, + "./*": { + "types": "./src/*.ts", + "import": "./src/*.ts" + }, + "./client": { + "types": "./src/client/index.tsx", + "import": "./src/client/index.tsx" + }, + "./client/*": { + "types": "./src/client/*.ts", + "import": "./src/client/*.ts" + }, + "./client/components/*": { + "types": "./src/client/components/*.ts", + "import": "./src/client/components/*.ts" + }, + "./*.js": "./src/*.ts" + } } diff --git a/packages/stats/src/embedded-client.ts b/packages/stats/src/embedded-client.ts index 32ce10934..74b8ead26 100644 --- a/packages/stats/src/embedded-client.ts +++ b/packages/stats/src/embedded-client.ts @@ -3,7 +3,7 @@ * * `embedded-client.generated.txt` holds the base64 of a gzipped tar of the * built dashboard (`dist/client`). It is populated by - * `scripts/generate-client-bundle.ts --generate` for compiled binaries and the + * `gen:stats` for compiled binaries and the * prepacked npm bundle, and reset to an empty file afterwards so the dev tree * keeps building the dashboard from source. */ diff --git a/packages/swarm-extension/package.json b/packages/swarm-extension/package.json index 7c6e0d558..bf66b9479 100644 --- a/packages/swarm-extension/package.json +++ b/packages/swarm-extension/package.json @@ -1,55 +1,55 @@ { - "type": "module", - "name": "@oh-my-pi/swarm-extension", - "version": "16.1.23", - "description": "Swarm orchestration extension for omp", - "homepage": "https://omp.sh", - "author": "Derek Rynd", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/can1357/oh-my-pi.git", - "directory": "packages/swarm-extension" - }, - "bugs": { - "url": "https://github.com/can1357/oh-my-pi/issues" - }, - "keywords": [ - "swarm", - "orchestration", - "agent", - "extension" - ], - "bin": { - "omp-swarm": "src/cli.ts" - }, - "scripts": { - "check": "biome check . && bun run check:types", - "check:types": "tsgo -p tsconfig.json --noEmit", - "lint": "biome lint .", - "fix": "biome check --write --unsafe .", - "fmt": "biome format --write ." - }, - "dependencies": { - "@oh-my-pi/pi-utils": "workspace:*" - }, - "devDependencies": { - "@types/bun": "^1.3.14" - }, - "peerDependencies": { - "@oh-my-pi/pi-coding-agent": "^13" - }, - "engines": { - "bun": ">=1.3.14" - }, - "omp": { - "extensions": [ - "./src/extension.ts" - ] - }, - "files": [ - "src", - "README.md", - "CHANGELOG.md" - ] + "type": "module", + "name": "@oh-my-pi/swarm-extension", + "version": "16.1.23", + "description": "Swarm orchestration extension for omp", + "homepage": "https://omp.sh", + "author": "Derek Rynd", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/can1357/oh-my-pi.git", + "directory": "packages/swarm-extension" + }, + "bugs": { + "url": "https://github.com/can1357/oh-my-pi/issues" + }, + "keywords": [ + "swarm", + "orchestration", + "agent", + "extension" + ], + "bin": { + "omp-swarm": "src/cli.ts" + }, + "scripts": { + "check": "biome check . && bun run check:types", + "check:types": "tsgo -p tsconfig.json --noEmit", + "lint": "biome lint .", + "fix": "biome check --write --unsafe .", + "fmt": "biome format --write ." + }, + "dependencies": { + "@oh-my-pi/pi-utils": "workspace:*" + }, + "devDependencies": { + "@types/bun": "^1.3.14" + }, + "peerDependencies": { + "@oh-my-pi/pi-coding-agent": "^13" + }, + "engines": { + "bun": ">=1.3.14" + }, + "omp": { + "extensions": [ + "./src/extension.ts" + ] + }, + "files": [ + "src", + "README.md", + "CHANGELOG.md" + ] } diff --git a/packages/terminal-bench/package.json b/packages/terminal-bench/package.json index 80f2d9c4a..33758fe50 100644 --- a/packages/terminal-bench/package.json +++ b/packages/terminal-bench/package.json @@ -1,30 +1,30 @@ { - "type": "module", - "private": true, - "name": "@oh-my-pi/terminal-bench", - "version": "0.0.1", - "description": "Run harbor-framework/terminal-bench-2 against the local omp build with a live progress/spend/success dashboard", - "homepage": "https://omp.sh", - "author": "Can Boluk", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/can1357/oh-my-pi.git", - "directory": "packages/terminal-bench" - }, - "bin": { - "tb2": "src/runner.ts" - }, - "scripts": { - "check": "biome check . && bun run check:types", - "check:types": "tsgo -p tsconfig.json --noEmit", - "lint": "biome lint .", - "start": "bun run src/runner.ts" - }, - "devDependencies": { - "@types/bun": "catalog:" - }, - "engines": { - "bun": ">=1.3.14" - } + "type": "module", + "private": true, + "name": "@oh-my-pi/terminal-bench", + "version": "0.0.1", + "description": "Run harbor-framework/terminal-bench-2 against the local omp build with a live progress/spend/success dashboard", + "homepage": "https://omp.sh", + "author": "Can Boluk", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/can1357/oh-my-pi.git", + "directory": "packages/terminal-bench" + }, + "bin": { + "tb2": "src/runner.ts" + }, + "scripts": { + "check": "biome check . && bun run check:types", + "check:types": "tsgo -p tsconfig.json --noEmit", + "lint": "biome lint .", + "start": "bun run src/runner.ts" + }, + "devDependencies": { + "@types/bun": "catalog:" + }, + "engines": { + "bun": ">=1.3.14" + } } diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index c78cc5eca..5407c1d70 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -4,19 +4,14 @@ ### Added -- Added support for rendering HTML `` tags as theme-styled inline code blocks -- Added support for rendering HTML `
` tags as horizontal rules -- Added support for rendering HTML `
` tags with appropriate quote styling -- Recognized Warp (`TERM_PROGRAM=WarpTerminal`) as a first-class terminal. Inline images now negotiate the Kitty graphics protocol on macOS/Linux (direct placement — Warp has no Unicode-placeholder support); the protocol is dropped on Windows, where Warp ships without Kitty support and the APC sequences would render as visible garbage. True color is enabled. OSC 8 hyperlinks stay off by default because Warp's renderer prints the escape as literal text rather than a clickable link (opt in with `PI_FORCE_HYPERLINKS=1` once Warp lands real support), and synchronized output remains gated on the runtime DECRQM probe ([#3471](https://github.com/can1357/oh-my-pi/issues/3471)). -- Added shared SGR mouse input routing helpers and `SelectList.routeMouse()` support for fullscreen overlay hit-testing. +- Added support for rendering HTML ,
, and
tags with proper theme styling, entity decoding, and layout consistency across Markdown transcripts, table cells, list items, and option labels. +- Added first-class support for Warp terminal (TERM_PROGRAM=WarpTerminal), enabling true color, platform-specific Kitty graphics protocol negotiation for inline images, and safe defaults for OSC 8 hyperlinks and synchronized output. +- Added SelectList.routeMouse() and shared SGR mouse input routing helpers to support fullscreen overlay hit-testing. ### Fixed -- Fixed HTML entity decoding within HTML-based inline code blocks -- Fixed stray or unmatched HTML tags leaking into rendered output -- Improved layout consistency by correctly handling HTML block-level tags in various contexts -- Markdown renderer now handles inline `…` (rendered as themed inline code, identical to a backtick codespan, with HTML entities like `&` decoded), block `
` (rendered as a horizontal rule), and balanced single-line `
…
` (rendered with the quote border) instead of leaking the raw tags as literal text. Applies to the transcript renderer, table cells, list items, and the inline `renderInlineMarkdown` helper used for option labels; fenced code blocks keep such markup verbatim. -- Fixed ordinary render scheduling to yield behind already-queued terminal input, preventing delayed Esc delivery during heavy streaming paints ([#3493](https://github.com/can1357/oh-my-pi/issues/3493)). +- Fixed issues where stray, unmatched, or raw HTML tags would leak into the rendered output. +- Fixed render scheduling to yield behind queued terminal input, preventing delayed Escape key delivery during heavy streaming paints. ## [16.1.20] - 2026-06-25 diff --git a/packages/tui/package.json b/packages/tui/package.json index dc0c7009f..c55eef8fb 100644 --- a/packages/tui/package.json +++ b/packages/tui/package.json @@ -1,72 +1,72 @@ { - "type": "module", - "name": "@oh-my-pi/pi-tui", - "version": "16.1.23", - "description": "Terminal User Interface library with differential rendering for efficient text-based applications", - "homepage": "https://omp.sh", - "author": "Can Boluk", - "contributors": [ - "Mario Zechner" - ], - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/can1357/oh-my-pi.git", - "directory": "packages/tui" - }, - "bugs": { - "url": "https://github.com/can1357/oh-my-pi/issues" - }, - "keywords": [ - "tui", - "terminal", - "ui", - "text-editor", - "differential-rendering", - "typescript", - "cli" - ], - "main": "./src/index.ts", - "types": "./src/index.ts", - "scripts": { - "check": "biome check . && bun run check:types", - "check:types": "tsgo -p tsconfig.json --noEmit", - "lint": "biome lint .", - "test": "bun test --parallel test/*.test.ts", - "fix": "biome check --write --unsafe .", - "fmt": "biome format --write ." - }, - "dependencies": { - "@oh-my-pi/pi-natives": "catalog:", - "@oh-my-pi/pi-utils": "catalog:", - "lru-cache": "catalog:", - "marked": "catalog:" - }, - "devDependencies": { - "chalk": "catalog:", - "ghostty-web": "catalog:" - }, - "engines": { - "bun": ">=1.3.14" - }, - "files": [ - "src", - "README.md", - "CHANGELOG.md" - ], - "exports": { - ".": { - "types": "./src/index.ts", - "import": "./src/index.ts" - }, - "./*": { - "types": "./src/*.ts", - "import": "./src/*.ts" - }, - "./components/*": { - "types": "./src/components/*.ts", - "import": "./src/components/*.ts" - }, - "./*.js": "./src/*.ts" - } + "type": "module", + "name": "@oh-my-pi/pi-tui", + "version": "16.1.23", + "description": "Terminal User Interface library with differential rendering for efficient text-based applications", + "homepage": "https://omp.sh", + "author": "Can Boluk", + "contributors": [ + "Mario Zechner" + ], + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/can1357/oh-my-pi.git", + "directory": "packages/tui" + }, + "bugs": { + "url": "https://github.com/can1357/oh-my-pi/issues" + }, + "keywords": [ + "tui", + "terminal", + "ui", + "text-editor", + "differential-rendering", + "typescript", + "cli" + ], + "main": "./src/index.ts", + "types": "./src/index.ts", + "scripts": { + "check": "biome check . && bun run check:types", + "check:types": "tsgo -p tsconfig.json --noEmit", + "lint": "biome lint .", + "test": "bun test --parallel test/*.test.ts", + "fix": "biome check --write --unsafe .", + "fmt": "biome format --write ." + }, + "dependencies": { + "@oh-my-pi/pi-natives": "catalog:", + "@oh-my-pi/pi-utils": "catalog:", + "lru-cache": "catalog:", + "marked": "catalog:" + }, + "devDependencies": { + "chalk": "catalog:", + "ghostty-web": "catalog:" + }, + "engines": { + "bun": ">=1.3.14" + }, + "files": [ + "src", + "README.md", + "CHANGELOG.md" + ], + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./src/index.ts" + }, + "./*": { + "types": "./src/*.ts", + "import": "./src/*.ts" + }, + "./components/*": { + "types": "./src/components/*.ts", + "import": "./src/components/*.ts" + }, + "./*.js": "./src/*.ts" + } } diff --git a/packages/typescript-edit-benchmark/package.json b/packages/typescript-edit-benchmark/package.json index 21c8553b7..2e4c7c820 100644 --- a/packages/typescript-edit-benchmark/package.json +++ b/packages/typescript-edit-benchmark/package.json @@ -1,63 +1,63 @@ { - "type": "module", - "private": true, - "name": "@oh-my-pi/typescript-edit-benchmark", - "version": "0.0.1", - "description": "Edit benchmark suite using TypeScript source code mutations", - "homepage": "https://omp.sh", - "author": "Can Boluk", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/can1357/oh-my-pi.git", - "directory": "packages/typescript-edit-benchmark" - }, - "main": "./src/index.ts", - "exports": { - ".": { - "types": "./src/index.ts", - "import": "./src/index.ts" - }, - "./*": { - "types": "./src/*.ts", - "import": "./src/*.ts" - }, - "./*.js": "./src/*.ts" - }, - "bin": { - "typescript-edit-benchmark": "src/index.ts" - }, - "scripts": { - "check": "biome check . && bun run check:types", - "check:types": "tsgo -p tsconfig.json --noEmit", - "lint": "biome lint .", - "test": "bun test --parallel", - "fix": "biome check --write --unsafe .", - "fmt": "biome format --write .", - "generate": "bun run src/generate.ts --typescript-dir /tmp/pi-mono-source --count-per-type 4", - "start": "bun run src/index.ts" - }, - "dependencies": { - "@babel/generator": "catalog:", - "@babel/parser": "catalog:", - "@babel/traverse": "catalog:", - "@babel/types": "catalog:", - "@oh-my-pi/hashline": "catalog:", - "@oh-my-pi/pi-agent-core": "catalog:", - "@oh-my-pi/pi-coding-agent": "catalog:", - "@oh-my-pi/pi-utils": "catalog:", - "diff": "catalog:", - "prettier": "catalog:", - "regexp-tree": "catalog:", - "@oh-my-pi/pi-ai": "catalog:", - "@oh-my-pi/pi-tui": "catalog:" - }, - "devDependencies": { - "@types/babel__generator": "catalog:", - "@types/babel__traverse": "catalog:", - "@types/bun": "catalog:" - }, - "engines": { - "bun": ">=1.3.14" - } + "type": "module", + "private": true, + "name": "@oh-my-pi/typescript-edit-benchmark", + "version": "0.0.1", + "description": "Edit benchmark suite using TypeScript source code mutations", + "homepage": "https://omp.sh", + "author": "Can Boluk", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/can1357/oh-my-pi.git", + "directory": "packages/typescript-edit-benchmark" + }, + "main": "./src/index.ts", + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./src/index.ts" + }, + "./*": { + "types": "./src/*.ts", + "import": "./src/*.ts" + }, + "./*.js": "./src/*.ts" + }, + "bin": { + "typescript-edit-benchmark": "src/index.ts" + }, + "scripts": { + "check": "biome check . && bun run check:types", + "check:types": "tsgo -p tsconfig.json --noEmit", + "lint": "biome lint .", + "test": "bun test --parallel", + "fix": "biome check --write --unsafe .", + "fmt": "biome format --write .", + "generate": "bun run src/generate.ts --typescript-dir /tmp/pi-mono-source --count-per-type 4", + "start": "bun run src/index.ts" + }, + "dependencies": { + "@babel/generator": "catalog:", + "@babel/parser": "catalog:", + "@babel/traverse": "catalog:", + "@babel/types": "catalog:", + "@oh-my-pi/hashline": "catalog:", + "@oh-my-pi/pi-agent-core": "catalog:", + "@oh-my-pi/pi-coding-agent": "catalog:", + "@oh-my-pi/pi-utils": "catalog:", + "diff": "catalog:", + "prettier": "catalog:", + "regexp-tree": "catalog:", + "@oh-my-pi/pi-ai": "catalog:", + "@oh-my-pi/pi-tui": "catalog:" + }, + "devDependencies": { + "@types/babel__generator": "catalog:", + "@types/babel__traverse": "catalog:", + "@types/bun": "catalog:" + }, + "engines": { + "bun": ">=1.3.14" + } } diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md index 95d5fa88c..4de0e44b7 100644 --- a/packages/utils/CHANGELOG.md +++ b/packages/utils/CHANGELOG.md @@ -4,16 +4,15 @@ ### Added -- Added a relaxed JSON parser that supports single-quoted strings, unquoted keys, and comments -- Added `parseStreamingJson` for robust parsing of truncated or malformed streaming JSON -- Added `parseStreamingJsonThrottled` for efficient processing of incremental streaming updates -- Added an XDG-aware document conversion cache directory helper for coding-agent document reads. -- Exported `removeWithRetries()` as a standalone async function so tests with async cleanup hooks (`afterEach(async () => …)`) can use the same retry-on-EBUSY cleanup logic as `TempDir.remove()`. Previously only the sync variant was exported, forcing async tests to use `fs.rm` directly — which fails with EBUSY on Windows when SQLite database files are still locked. +- Added a relaxed JSON parser supporting single-quoted strings, unquoted keys, and comments. +- Added `parseStreamingJson` and `parseStreamingJsonThrottled` for robust, efficient parsing of truncated or incremental streaming JSON. +- Added an XDG-aware document conversion cache directory helper. +- Exported `removeWithRetries()` as a standalone asynchronous function to handle retry-on-EBUSY cleanup logic. ### Changed -- Reworked streaming SSE JSON (`readSseJson`) to recover a truncated or lightly malformed final event through the shared streaming JSON parser (relocated here from `@oh-my-pi/pi-ai`), ending the stream cleanly on a cut-off tail instead of throwing. Non-container final events (provider error text, bare scalars) still surface as a `SyntaxError`. -- Increased the EBUSY retry delay from 25ms to 50ms (40 retries × 50ms = 2s total window, up from 1s). Windows can hold file locks on SQLite databases for up to ~1.5s after `close()`, and the previous 1-second window was too short for some test cleanup scenarios — `settings-manager.test.ts` and `sdk-credential-disabled-bridge.test.ts` still failed with EBUSY even when using `removeSyncWithRetries`. +- Improved `readSseJson` to gracefully recover truncated or malformed final events using the streaming JSON parser, ending the stream cleanly instead of throwing. +- Increased the retry delay for EBUSY file-lock errors from 25ms to 50ms (extending the total retry window to 2 seconds) to improve reliability on Windows. ## [16.1.8] - 2026-06-20 diff --git a/packages/utils/package.json b/packages/utils/package.json index 6dbaf4041..88881bb0b 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,61 +1,61 @@ { - "type": "module", - "name": "@oh-my-pi/pi-utils", - "version": "16.1.23", - "description": "Shared utilities for pi packages", - "homepage": "https://omp.sh", - "author": "Can Boluk", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/can1357/oh-my-pi.git", - "directory": "packages/utils" - }, - "bugs": { - "url": "https://github.com/can1357/oh-my-pi/issues" - }, - "keywords": [ - "utilities", - "cli", - "logging", - "streams" - ], - "main": "./src/index.ts", - "types": "./src/index.ts", - "scripts": { - "check": "biome check . && bun run check:types", - "check:types": "tsgo -p tsconfig.json --noEmit", - "lint": "biome lint .", - "test": "bun test --parallel", - "fix": "biome check --write --unsafe .", - "fmt": "biome format --write ." - }, - "dependencies": { - "@oh-my-pi/pi-natives": "catalog:", - "handlebars": "catalog:", - "winston": "catalog:", - "winston-daily-rotate-file": "catalog:" - }, - "devDependencies": { - "@types/bun": "catalog:" - }, - "engines": { - "bun": ">=1.3.14" - }, - "files": [ - "src", - "README.md", - "CHANGELOG.md" - ], - "exports": { - ".": { - "types": "./src/index.ts", - "import": "./src/index.ts" - }, - "./*": { - "types": "./src/*.ts", - "import": "./src/*.ts" - }, - "./*.js": "./src/*.ts" - } + "type": "module", + "name": "@oh-my-pi/pi-utils", + "version": "16.1.23", + "description": "Shared utilities for pi packages", + "homepage": "https://omp.sh", + "author": "Can Boluk", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/can1357/oh-my-pi.git", + "directory": "packages/utils" + }, + "bugs": { + "url": "https://github.com/can1357/oh-my-pi/issues" + }, + "keywords": [ + "utilities", + "cli", + "logging", + "streams" + ], + "main": "./src/index.ts", + "types": "./src/index.ts", + "scripts": { + "check": "biome check . && bun run check:types", + "check:types": "tsgo -p tsconfig.json --noEmit", + "lint": "biome lint .", + "test": "bun test --parallel", + "fix": "biome check --write --unsafe .", + "fmt": "biome format --write ." + }, + "dependencies": { + "@oh-my-pi/pi-natives": "catalog:", + "handlebars": "catalog:", + "winston": "catalog:", + "winston-daily-rotate-file": "catalog:" + }, + "devDependencies": { + "@types/bun": "catalog:" + }, + "engines": { + "bun": ">=1.3.14" + }, + "files": [ + "src", + "README.md", + "CHANGELOG.md" + ], + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./src/index.ts" + }, + "./*": { + "types": "./src/*.ts", + "import": "./src/*.ts" + }, + "./*.js": "./src/*.ts" + } } diff --git a/packages/wire/package.json b/packages/wire/package.json index 76f136624..5c537da35 100644 --- a/packages/wire/package.json +++ b/packages/wire/package.json @@ -1,55 +1,55 @@ { - "type": "module", - "name": "@oh-my-pi/pi-wire", - "version": "16.1.23", - "description": "Shared wire protocol types for Oh My Pi packages", - "homepage": "https://omp.sh", - "author": "Can Boluk", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/can1357/oh-my-pi.git", - "directory": "packages/wire" - }, - "bugs": { - "url": "https://github.com/can1357/oh-my-pi/issues" - }, - "keywords": [ - "wire", - "protocol", - "types", - "collab" - ], - "main": "./src/index.ts", - "types": "./src/index.ts", - "scripts": { - "check": "biome check . && bun run check:types", - "check:types": "tsgo -p tsconfig.json --noEmit", - "lint": "biome lint .", - "fix": "biome check --write --unsafe .", - "fmt": "biome format --write .", - "test": "bun test --parallel" - }, - "devDependencies": { - "@types/bun": "catalog:" - }, - "engines": { - "bun": ">=1.3.14" - }, - "files": [ - "src", - "README.md", - "CHANGELOG.md" - ], - "exports": { - ".": { - "types": "./src/index.ts", - "import": "./src/index.ts" - }, - "./*": { - "types": "./src/*.ts", - "import": "./src/*.ts" - }, - "./*.js": "./src/*.ts" - } + "type": "module", + "name": "@oh-my-pi/pi-wire", + "version": "16.1.23", + "description": "Shared wire protocol types for Oh My Pi packages", + "homepage": "https://omp.sh", + "author": "Can Boluk", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/can1357/oh-my-pi.git", + "directory": "packages/wire" + }, + "bugs": { + "url": "https://github.com/can1357/oh-my-pi/issues" + }, + "keywords": [ + "wire", + "protocol", + "types", + "collab" + ], + "main": "./src/index.ts", + "types": "./src/index.ts", + "scripts": { + "check": "biome check . && bun run check:types", + "check:types": "tsgo -p tsconfig.json --noEmit", + "lint": "biome lint .", + "fix": "biome check --write --unsafe .", + "fmt": "biome format --write .", + "test": "bun test --parallel" + }, + "devDependencies": { + "@types/bun": "catalog:" + }, + "engines": { + "bun": ">=1.3.14" + }, + "files": [ + "src", + "README.md", + "CHANGELOG.md" + ], + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./src/index.ts" + }, + "./*": { + "types": "./src/*.ts", + "import": "./src/*.ts" + }, + "./*.js": "./src/*.ts" + } } diff --git a/scripts/ci-release-build-binaries.ts b/scripts/ci-release-build-binaries.ts index 4d981f5ce..4645d4259 100644 --- a/scripts/ci-release-build-binaries.ts +++ b/scripts/ci-release-build-binaries.ts @@ -96,11 +96,11 @@ async function runCommand(command: string[], cwd: string, env: NodeJS.ProcessEnv async function embedNative(target: BinaryTarget): Promise { if (isDryRun) { - console.log(`DRY RUN bun --cwd=packages/natives run embed:native [${target.platform}/${target.arch}]`); + console.log(`DRY RUN bun run gen:native [${target.platform}/${target.arch}]`); return; } - await runCommand(["bun", "--cwd=packages/natives", "run", "embed:native"], repoRoot, { + await runCommand(["bun", "run", "gen:native"], repoRoot, { ...Bun.env, TARGET_PLATFORM: target.platform, TARGET_ARCH: target.arch, @@ -151,28 +151,28 @@ async function buildBinary(target: BinaryTarget): Promise { async function generateBundle(): Promise { if (isDryRun) { - console.log("DRY RUN bun --cwd=packages/stats scripts/generate-client-bundle.ts --generate"); - console.log("DRY RUN bun --cwd=packages/coding-agent scripts/generate-docs-index.ts --generate"); - console.log("DRY RUN bun --cwd=packages/coding-agent scripts/embed-mupdf-wasm.ts --generate"); + console.log("DRY RUN bun run gen:stats"); + console.log("DRY RUN bun run gen:docs"); + console.log("DRY RUN bun run gen:mupdf"); return; } - await runCommand(["bun", "--cwd=packages/stats", "scripts/generate-client-bundle.ts", "--generate"], repoRoot); - await runCommand(["bun", "--cwd=packages/coding-agent", "scripts/generate-docs-index.ts", "--generate"], repoRoot); - await runCommand(["bun", "--cwd=packages/coding-agent", "scripts/embed-mupdf-wasm.ts", "--generate"], repoRoot); + await runCommand(["bun", "run", "gen:stats"], repoRoot); + await runCommand(["bun", "run", "gen:docs"], repoRoot); + await runCommand(["bun", "run", "gen:mupdf"], repoRoot); } async function resetArtifacts(): Promise { if (isDryRun) { - console.log("DRY RUN bun --cwd=packages/natives run embed:native --reset"); - console.log("DRY RUN bun --cwd=packages/stats scripts/generate-client-bundle.ts --reset"); - console.log("DRY RUN bun --cwd=packages/coding-agent scripts/generate-docs-index.ts --reset"); - console.log("DRY RUN bun --cwd=packages/coding-agent scripts/embed-mupdf-wasm.ts --reset"); + console.log("DRY RUN bun run gen:native:reset"); + console.log("DRY RUN bun run gen:stats:reset"); + console.log("DRY RUN bun run gen:docs:reset"); + console.log("DRY RUN bun run gen:mupdf:reset"); return; } - await runCommand(["bun", "--cwd=packages/natives", "run", "embed:native", "--reset"], repoRoot); - await runCommand(["bun", "--cwd=packages/stats", "scripts/generate-client-bundle.ts", "--reset"], repoRoot); - await runCommand(["bun", "--cwd=packages/coding-agent", "scripts/generate-docs-index.ts", "--reset"], repoRoot); - await runCommand(["bun", "--cwd=packages/coding-agent", "scripts/embed-mupdf-wasm.ts", "--reset"], repoRoot); + await runCommand(["bun", "run", "gen:native:reset"], repoRoot); + await runCommand(["bun", "run", "gen:stats:reset"], repoRoot); + await runCommand(["bun", "run", "gen:docs:reset"], repoRoot); + await runCommand(["bun", "run", "gen:mupdf:reset"], repoRoot); } async function main(): Promise { diff --git a/scripts/fix-changelogs.ts b/scripts/fix-changelogs.ts index 282ffec5e..06053e7b0 100755 --- a/scripts/fix-changelogs.ts +++ b/scripts/fix-changelogs.ts @@ -8,29 +8,29 @@ const ORDERED_SECTION_TITLES = ["Breaking Changes", "Added", "Changed", "Fixed", const CHANGELOG_BASELINE_REF = "refs/clog"; const CHANGELOG_BASELINE_NAME = "clog"; -interface NumberedLine { +export interface NumberedLine { text: string; lineNumber: number; } -interface Subsection { +export interface Subsection { title: string; lines: NumberedLine[]; } -interface ReleaseSection { +export interface ReleaseSection { heading: string; title: string; leadingLines: NumberedLine[]; subsections: Subsection[]; } -interface ChangelogDocument { +export interface ChangelogDocument { prefixLines: NumberedLine[]; sections: ReleaseSection[]; } -interface ParsedItem { +export interface ParsedItem { startLine: number; endLine: number; lines: string[]; @@ -135,7 +135,7 @@ function createNumberedLine(text: string, lineNumber: number): NumberedLine { return { text, lineNumber }; } -function parseChangelog(content: string): ChangelogDocument { +export function parseChangelog(content: string): ChangelogDocument { const lines = splitContentLines(content); const numberedLines = lines.map((text, index) => createNumberedLine(text, index + 1)); const prefixLines: NumberedLine[] = []; @@ -232,7 +232,7 @@ function appendSubsectionLines(target: Subsection, sourceLines: readonly string[ target.lines = syntheticLines([...existing, ...separator, ...trimmedSource]); } -function parseItems(lines: readonly NumberedLine[]): ParsedItem[] { +export function parseItems(lines: readonly NumberedLine[]): ParsedItem[] { const items: ParsedItem[] = []; let index = 0; @@ -264,7 +264,7 @@ function parseItems(lines: readonly NumberedLine[]): ParsedItem[] { return items; } -function lineRangeSet(items: readonly ParsedItem[]): Set { +export function lineRangeSet(items: readonly ParsedItem[]): Set { const lines = new Set(); for (const item of items) { for (let line = item.startLine; line <= item.endLine; line++) { @@ -503,7 +503,7 @@ function rebuildReleasedSectionsFromHistory( } -function renderChangelog(document: ChangelogDocument): string { +export function renderChangelog(document: ChangelogDocument): string { const output: string[] = []; const prefix = trimBlankLines(numberedText(document.prefixLines)); if (prefix.length > 0) { @@ -720,7 +720,7 @@ async function git(args: readonly string[], cwd: string): Promise { return result.text(); } -async function resolveRepoRoot(repoRoot: string | undefined): Promise { +export async function resolveRepoRoot(repoRoot: string | undefined): Promise { if (repoRoot) return path.resolve(repoRoot); return (await git(["rev-parse", "--show-toplevel"], process.cwd())).trim(); } @@ -827,7 +827,7 @@ async function collectHistoricalReleaseRecovery( } -async function changelogPaths(repoRoot: string): Promise { +export async function changelogPaths(repoRoot: string): Promise { const glob = new Glob(CHANGELOG_GLOB); const paths: string[] = []; for await (const changelogPath of glob.scan(repoRoot)) { diff --git a/scripts/rewrite-changelog.ts b/scripts/rewrite-changelog.ts new file mode 100755 index 000000000..78b7ed471 --- /dev/null +++ b/scripts/rewrite-changelog.ts @@ -0,0 +1,450 @@ +#!/usr/bin/env bun +/** + * Rewrite each package's `[Unreleased]` changelog section for release notes. + * + * A release cycle accumulates noisy implementation notes: a feature is added, + * then internal bugs in that same not-yet-released feature are fixed, transport + * plumbing is refactored, and behavior is renamed before anyone uses it. Only + * the final shipped behavior belongs in release notes. + * + * For every non-empty `[Unreleased]` section this script hands the whole section + * to a small model (default `google-vertex/gemini-3.5-flash` via `@oh-my-pi/pi-ai`) + * and asks for a complete replacement grouped by changelog category. The model + * returns structured sections/items; markdown is rendered locally so only the + * Unreleased section changes and formatting stays deterministic. + * + * The prompt defines "user-visible" for package consumers broadly: public + * exports/API, provider behavior, auth/errors, config, performance, and + * breaking changes are visible; pure implementation/test/refactor/internal + * protocol churn is not. + * + * Usage: + * bun scripts/rewrite-changelog.ts # rewrite + write + * bun scripts/rewrite-changelog.ts --dry-run # report only + * bun scripts/rewrite-changelog.ts --check # exit 1 if any would change + * bun scripts/rewrite-changelog.ts --package coding-agent + * bun scripts/rewrite-changelog.ts --model google/gemini-3.5-flash + * + * Auth: resolves the provider API key through omp's auth storage + * (~/.omp/agent/agent.db: stored key, OAuth, or env var fallback). + */ + +import * as path from "node:path"; +import { parseArgs } from "node:util"; +import { type Api, AuthStorage, completeSimple, Effort, type Model, SqliteAuthCredentialStore, type Tool, type ToolCall } from "@oh-my-pi/pi-ai"; +import { type GeneratedProvider, getBundledModel } from "@oh-my-pi/pi-catalog/models"; +import { getAgentDbPath } from "@oh-my-pi/pi-utils"; +import { z } from "zod/v4"; +import { + changelogPaths, + type ChangelogDocument, + type NumberedLine, + parseChangelog, + parseItems, + type ReleaseSection, + renderChangelog, + resolveRepoRoot, +} from "./fix-changelogs"; + +const DEFAULT_MODEL = "google-vertex/gemini-3.5-flash"; + +// -------------------------------------------------------------------------- +// Prompts + +const SYSTEM_PROMPT = `You audit and consolidate the \`[Unreleased]\` section of a package's changelog, rewriting it into high-quality, user-facing release notes before a new release. + +Your goal is to transform technical developer bullets into concise, user-facing release notes by: +1. Dropping non-user-visible internal implementation/test/refactor/infrastructure details. +2. Eliminating intermediate developer churn (e.g. fixes or changes made to features or systems that were *newly introduced in this same batch*). +3. Merging or consolidating multiple related internal milestones into single, unified, high-quality feature bullets. +4. Rewriting technical jargon to be clear, professional, and useful for the package's consumers (audience). + +Call the \`rewrite\` tool with the rewritten release note sections. + +--- + +## 1. What to Drop (Do Not Include) + +- **Intermediate Churn/Fixes**: A bug fix or additional adjustment made to a feature, provider, command, or API that was itself added or introduced *in this same unreleased batch*. Users only ever see the final shipped state, so a line like "Fixed crash in new feature X" is redundant because the feature X they get will already be stable. +- **Pure Implementation Details**: Internal protocol messages/helpers, private exports, local WebSocket routing, transport-metadata handling, logging/tracing modifications, intermediate retry/recovery strategies, cache/session storage internal mechanics, serialization/parsing adjustments, or renamed internal variables. +- ** Bring-Up Spam**: If this batch introduces a completely new provider or major subsystem, do not list 20 separate lines detailing how different parts of that subsystem were wired up. Consolidate them into a single clean summary of the new system's capabilities. +- **Obsolete/Canceled Changes**: If a feature was added and then removed in this same batch, omit both. + +## 2. What to Keep and Consolidate + +- **User-Visible Capabilities**: New features, updated provider capabilities, authentication flow changes, config settings, and CLI commands. +- **Genuine Bug Fixes**: Fixes to issues that existed in a *previously released* version (this is vital, user-facing value!). +- **Breaking Changes**: Any actual backward-incompatible modifications to public exports, configuration, behavior, or API contracts. +- **External Behavior Parity**: Important performance enhancements, support for new model providers/features, and resilience/error handling improvements that developers calling the SDK/CLI will experience. + +## 3. How to Rewrite and Consolidate + +- **Merge Related Bullets**: Instead of listing five technical bullets for different GitLab Duo Workflow features (OAuth callbacks, workspace project auto-discovery, namespace enablement), merge them into one: + > Added GitLab Duo Workflow provider support including official OAuth callback verification, workspace project auto-discovery, and automatic login-time namespace Duo enablement. +- **Be Concise and User-Facing**: Turn developer jargon (e.g., "replayed thinking blocks without context-management.keep") into description of the actual benefit (e.g., "Fixed preserving multi-turn thinking/reasoning context for Anthropic-compatible models"). +- **Remove Leading Symbols**: Write the item as a clean text string without prepending "- " or "* ". The harness will handle bullet formatting locally.`; + +// -------------------------------------------------------------------------- +// Model + auth + +interface RewriteModel { + model: Model; + apiKey: string; + spec: string; +} + +async function openModel(modelSpec: string): Promise { + const slash = modelSpec.indexOf("/"); + if (slash <= 0) throw new Error(`--model must be /, got "${modelSpec}"`); + const provider = modelSpec.slice(0, slash); + const modelId = modelSpec.slice(slash + 1); + const model = getBundledModel(provider as GeneratedProvider, modelId); + if (!model) throw new Error(`unknown model "${modelSpec}" (not in bundled catalog)`); + const store = await SqliteAuthCredentialStore.open(getAgentDbPath()); + const storage = new AuthStorage(store); + await storage.reload(); + const apiKey = await storage.getApiKey(provider); + if (!apiKey) { + throw new Error(`no credentials for provider "${provider}" (run \`omp login\` or set the provider env var)`); + } + return { model, apiKey, spec: modelSpec }; +} + +// -------------------------------------------------------------------------- +// Unreleased entries + +interface UnreleasedEntry { + index: number; + category: string; + text: string; +} + +function unreleasedSection(document: ChangelogDocument): ReleaseSection | undefined { + return document.sections.find(section => section.title === "Unreleased"); +} + +function collectEntries(section: ReleaseSection): UnreleasedEntry[] { + const entries: UnreleasedEntry[] = []; + let index = 1; + for (const subsection of section.subsections) { + for (const item of parseItems(subsection.lines)) { + entries.push({ index: index++, category: subsection.title, text: item.lines.join("\n") }); + } + } + return entries; +} + +// -------------------------------------------------------------------------- +// LLM call + +interface RewrittenSection { + category: string; + items: string[]; +} + +const REWRITE_RESPONSE = z.object({ + sections: z.array( + z.object({ + category: z.enum(["Breaking Changes", "Added", "Changed", "Fixed", "Removed"]), + items: z.array(z.string()), + }) + ).default([]), +}); + +const REWRITE_PARAMETERS = { + type: "object", + additionalProperties: false, + properties: { + sections: { + type: "array", + description: "Rewritten release note sections grouped by changelog category.", + items: { + type: "object", + additionalProperties: false, + properties: { + category: { + type: "string", + enum: ["Breaking Changes", "Added", "Changed", "Fixed", "Removed"], + }, + items: { + type: "array", + description: "Consolidated, user-facing release note items for this category.", + items: { type: "string" }, + }, + }, + required: ["category", "items"], + }, + }, + }, + required: ["sections"], +} as unknown as Tool["parameters"]; + +const REWRITE_TOOL: Tool = { + name: "rewrite", + description: "Return the rewritten, consolidated release sections.", + parameters: REWRITE_PARAMETERS, + strict: false, +}; + +function validateRewrite(args: Record): RewrittenSection[] { + const parsed = REWRITE_RESPONSE.safeParse(args); + if (!parsed.success) { + throw new Error(`invalid tool arguments: ${parsed.error.issues.map(issue => issue.message).join("; ")}`); + } + return parsed.data.sections + .map(sec => ({ + category: sec.category, + items: sec.items.map(item => item.trim()).filter(Boolean), + })) + .filter(sec => sec.items.length > 0); +} + +function normalizeRewriteItem(text: string): string[] { + const lines = text.trim().split("\n").map(l => l.trimEnd()); + if (lines.length === 0) return []; + const first = lines[0] ?? ""; + const content = first.startsWith("- ") ? first.slice(2) : first.startsWith("* ") ? first.slice(2) : first; + const out = [`- ${content}`]; + for (let i = 1; i < lines.length; i++) { + const line = lines[i] ?? ""; + out.push(line.startsWith(" ") ? line : ` ${line}`); + } + return out; +} + +async function requestRewrite(model: RewriteModel, packageName: string, unreleasedBody: string): Promise { + const userText = `Package: \`${packageName}\` + +Original \`[Unreleased]\` section body: +\`\`\`markdown +${unreleasedBody} +\`\`\` + +Consolidate and rewrite this content into user-visible release notes. Keep all public API/config/auth/billing behavior, but drop intermediate churn and implementation-only details. Return the structured sections using the \`rewrite\` tool.`; + let lastError = ""; + for (let attempt = 0; attempt < 3; attempt++) { + const response = await completeSimple( + model.model, + { + systemPrompt: [SYSTEM_PROMPT], + messages: [{ role: "user", content: [{ type: "text", text: userText }], timestamp: Date.now() }], + tools: [REWRITE_TOOL], + }, + { apiKey: model.apiKey, toolChoice: { type: "tool", name: "rewrite" }, reasoning: Effort.Low, temperature: 0 }, + ); + + if (response.stopReason === "error" || response.stopReason === "aborted") { + lastError = response.errorMessage ?? response.stopReason; + await Bun.sleep(1500 * (attempt + 1)); + continue; + } + + const call = response.content.find((content): content is ToolCall => content.type === "toolCall" && content.name === "rewrite"); + if (!call) { + lastError = "model returned no structured tool call"; + continue; + } + try { + return validateRewrite(call.arguments); + } catch (error) { + lastError = error instanceof Error ? error.message : String(error); + continue; + } + } + throw new Error(`rewrite call failed for ${packageName}: ${lastError}`); +} + +// -------------------------------------------------------------------------- +// Run + +interface RewrittenFile { + path: string; + originalCount: number; + rewrittenCount: number; + sections: RewrittenSection[]; +} + +interface RunOptions { + repoRoot?: string; + model: string; + write: boolean; + packageFilter?: string; + concurrency?: number; +} + +interface RunResult { + model: string; + changed: RewrittenFile[]; +} + +function applyRewrite(section: ReleaseSection, sections: RewrittenSection[]): void { + section.subsections = sections.map(sec => { + const rawLines = sec.items.flatMap(normalizeRewriteItem); + const lines: NumberedLine[] = rawLines.map(text => ({ text, lineNumber: 0 })); + return { title: sec.category, lines }; + }).filter(sub => sub.lines.length > 0); +} + +async function run(options: RunOptions): Promise { + const repoRoot = await resolveRepoRoot(options.repoRoot); + const paths = (await changelogPaths(repoRoot)).filter( + changelogPath => !options.packageFilter || changelogPath.includes(options.packageFilter), + ); + const model = await openModel(options.model); + const concurrency = options.concurrency ?? 4; + const results: Array = new Array(paths.length); + + let pathIndex = 0; + async function worker() { + while (pathIndex < paths.length) { + const i = pathIndex++; + const changelogPath = paths[i]; + if (!changelogPath) continue; + + try { + const absolutePath = path.join(repoRoot, changelogPath); + const content = await Bun.file(absolutePath).text(); + const document = parseChangelog(content); + const section = unreleasedSection(document); + if (!section) continue; + + const originalCount = section.subsections.reduce((sum, sub) => sum + parseItems(sub.lines).length, 0); + if (originalCount === 0) continue; + + const unreleasedBody = renderChangelog({ prefixLines: [], sections: [section] }) + .replace(/^## \[Unreleased\]\n?/, "") + .trim(); + + const rewritten = await requestRewrite(model, changelogPath, unreleasedBody); + applyRewrite(section, rewritten); + const next = renderChangelog(document); + if (next === content) continue; + + const rewrittenCount = rewritten.reduce((sum, sec) => sum + sec.items.length, 0); + if (options.write) { + await Bun.write(absolutePath, next); + } + + results[i] = { + path: changelogPath, + originalCount, + rewrittenCount, + sections: rewritten, + }; + } catch (error) { + // Bubble errors from workers + throw error; + } + } + } + + const workers = Array.from({ length: Math.min(concurrency, paths.length) }, worker); + await Promise.all(workers); + + const changed: RewrittenFile[] = []; + for (const res of results) { + if (res !== undefined) changed.push(res); + } + + return { model: model.spec, changed }; +} + +// -------------------------------------------------------------------------- +// CLI + +interface CliOptions { + mode: "write" | "dry-run" | "check"; + model: string; + repoRoot?: string; + packageFilter?: string; + concurrency: number; +} + +function parseCli(argv: string[]): CliOptions | "help" { + const { values } = parseArgs({ + args: argv, + options: { + "dry-run": { type: "boolean", default: false }, + check: { type: "boolean", default: false }, + model: { type: "string", default: DEFAULT_MODEL }, + package: { type: "string" }, + "repo-root": { type: "string" }, + concurrency: { type: "string", default: "4" }, + help: { type: "boolean", default: false }, + }, + }); + if (values.help) return "help"; + return { + mode: values.check ? "check" : values["dry-run"] ? "dry-run" : "write", + model: values.model, + repoRoot: values["repo-root"], + packageFilter: values.package, + concurrency: Number.parseInt(values.concurrency ?? "4", 10), + }; +} + +function usage(): string { + return [ + "Usage: bun scripts/rewrite-changelog.ts [--dry-run|--check] [--model ] [--package ] [--concurrency ]", + "", + "Hands each non-empty [Unreleased] changelog section to a small model and rewrites the entries", + "into user-facing release notes, dropping intermediate developer churn and implementation-only details", + "while preserving public contract, exports, API, config, auth, and billing behavior.", + "", + "Options:", + ` --model Classifier model (default ${DEFAULT_MODEL}).`, + " --package Only changelogs whose path contains this substring.", + " --concurrency Max concurrent changelogs to process in parallel (default 4).", + " --dry-run Report what would be dropped without writing files.", + " --check Exit 1 if any changelog would change.", + " --repo-root Run against an explicit repository root.", + ].join("\n"); +} + +function printSummary(result: RunResult, mode: CliOptions["mode"]): void { + if (result.changed.length === 0) { + console.log(`No redundant or non-user-visible [Unreleased] entries to rewrite (model ${result.model}).`); + return; + } + const suffix = mode === "write" ? "" : ` (${mode}, not written)`; + console.log(`Rewrote [Unreleased] sections across ${result.changed.length} changelog(s)${suffix}:`); + for (const file of result.changed) { + console.log(`\n ${file.path} (${file.originalCount} items -> ${file.rewrittenCount} items):`); + for (const sec of file.sections) { + console.log(` ### ${sec.category}`); + for (const item of sec.items) { + console.log(` - ${item}`); + } + } + } +} + +async function main(): Promise { + try { + const cli = parseCli(process.argv.slice(2)); + if (cli === "help") { + console.log(usage()); + return; + } + const result = await run({ + repoRoot: cli.repoRoot, + model: cli.model, + write: cli.mode === "write", + packageFilter: cli.packageFilter, + concurrency: cli.concurrency, + }); + printSummary(result, cli.mode); + if (cli.mode === "check" && result.changed.length > 0) { + process.exit(1); + } + } catch (error) { + console.error(error instanceof Error ? error.message : String(error)); + process.exit(1); + } +} + +if (import.meta.main) { + await main(); +} + +export { applyRewrite, collectEntries, run, type RunResult, unreleasedSection, validateRewrite };