- Modified scanInlineBody to strip the payload prefix when present at the start of inline content.
- Added test cases covering backslash-delimited payloads in insert, delete, and replace operations.
- Changed output format to group results under `# /` headers to reduce token usage for shared path prefixes.
- Clamped the `limit` parameter to 1-200 (default 200) instead of the previous 1000.
- Updated tests to assert against raw file lists instead of parsed text output.
- Updated the payload continuation syntax to use backslash (`\`) instead of plus (`+`) as the explicit prefix for multi-line payloads and blank lines.
- Modified grammar, format constants, parser comments, and all documentation and test cases to reflect the new syntax.
- This is a breaking change for existing hashline patches using the `+` prefix.
- Removed the `!` delete sigil and all associated parsing, validation, and tokenization logic. The delete operation is no longer a supported edit kind.
- Updated grammar, format constants, and error messages to reference only insert and replace operations.
- Simplified the executor's overlap validation to handle only replace operations.
- Parser now buffers markdown-style `#` lines and skips them when they directly precede a hashline operation.
- It now preserves comment lines that are not immediately before an operation while still handling blank separators as regular raw input.
- Added focused parser tests plus prompt and changelog updates describing the new comment-skipping behavior.
- Simplified `renderSection` output assembly in hashline execution to stop injecting a separate headline.
- Removed conditional headline generation that prefixed create/update/status text before the header.
- Returned tool results containing only header, preview, and warnings content blocks.
- Preflighted write policies for all sections before any commit in multi-section batches.
- Rejected duplicate canonical targets (e.g., `a.ts` and `./a.ts`) before writes begin.
- Fixed `after_anchor` normalization mutating cached edits across repeated patch applications.
- Fixed `detectLineEnding` to use first-occurrence style instead of majority vote.
Two TS errors in CI:
1. `agent-session.ts:1317` — `error TS1345: An expression of type 'void'
cannot be tested for truthiness`. The listener type is
`(event: AgentSessionEvent) => void`, so the returned value can't be
directly tested. Same shape in `agent.ts:1079`.
2. `test/session/emit-listener-isolation.test.ts:19` — the test fixture
for `AgentEvent.tool_execution_start` was missing the required `args`
field.
Cast the return to `unknown` and check `instanceof Promise` instead of
duck-typing `.then` — type-safe and matches what async functions actually
return. Add `args: {}` to the test fixture.
createAgentSession() removed the hidden `resolve` tool from the registry
whenever no active tool advertised `deferrable: true`. Plan mode dispatches
its plan-approval `resolve { action: "apply", extra: { title } }` call
through a standing handler installed by InteractiveMode (no deferrable tool
involved), so read-only plan-mode toolsets (e.g. `read`, `search`, `find`,
`web_search`) silently activated plan mode without `resolve`. The agent had
no callable tool to submit the finalized plan and got stuck on the post-turn
tool-decision reminder.
Keep `resolve` registered whenever `plan.enabled` is true so the standing
handler always has a callable tool. The hidden flag still prevents `resolve`
from appearing in the active tool set until plan mode (or a deferrable tool's
preview action) opts in.
Fixes#1428
Both `AgentSession.#emit` (session/agent-session.ts) and `Agent.#emit`
(packages/agent/src/agent.ts) iterated listeners with no error isolation.
A synchronous throw in any subscriber aborted the for-loop, so later
subscribers (TUI rendering, ACP bridge, task executor progress,
hindsight) silently missed events. Many listeners — see
`modes/controllers/event-controller.ts:141` and
`modes/controllers/input-controller.ts:576` — are registered as
`async (event) => { await this.handleEvent(event); }`; the returned
Promise was dropped, so any rejection became an unhandled rejection.
Wrap each listener invocation in try/catch and attach a `.catch` to any
returned thenable. Errors are logged via `logger.warn` (already imported
in agent-session.ts) and `console.error` (agent.ts has no logger
dependency, keep it that way).
Test: new `test/session/emit-listener-isolation.test.ts` registers two
listeners on both classes; first listener throws (or returns a rejecting
Promise); asserts the second listener still receives the event AND no
`unhandledRejection` fires. 4 cases (sync+async × Agent+AgentSession).
All fail on current main; all pass with the fix.
`getPackageDir()` walked up from `import.meta.dir` and fell back to
`getProjectDir()` (the user's `cwd`) when no `package.json` was located.
Inside `bun --compile` binaries `import.meta.dir` resolves to
`/$bunfs/root`, so the walk hit the filesystem root and `omp` ended up
reading the host project's `CHANGELOG.md` as its own — both in startup
"What's New" display and `/changelog`. Worse, parsing succeeded on any
`## [x.y.z]` heading, so `lastChangelogVersion` was persisted into
`~/.omp/agent/config.yml` based on the host project's file.
- Made `getPackageDir()` return `string | undefined`; removed the `cwd`
fallback so package-asset lookup never bleeds into the host project.
- Extracted the walk-up into a pure `walkUpForPackageDir(startDir)` so
the resolution contract is unit-testable from arbitrary directories.
- Made `getChangelogPath()` propagate `undefined` and taught
`parseChangelog` to accept it, returning `[]`. Existing callers
(`main.ts` startup, `/changelog` TUI handler, `/changelog` slash
command) already gate on empty entries, so the compiled-binary path
now skips changelog display cleanly without mutating settings.
- Added `test/issue-1423-repro.test.ts` covering the resolver contract,
the `parseChangelog(undefined)` path, the `PI_PACKAGE_DIR` override,
and the negative assertion that a host `## [99.0.0]` heading never
surfaces as an omp entry.
Fixes#1423
- Added a dedicated @oh-my-pi/hashline package with parser, patcher, filesystem, snapshots, and release metadata.
- Migrated coding-agent hashline and stream entrypoints to @oh-my-pi/hashline and removed old hashline module exports.
- Changed multi-section hashline execution to validate section hashes and flush diagnostics only at the final commit.
- Added session fileSnapshotStore support and rewired edit/read/search/write tools to use it instead of fileReadCache.
- Added `unfoldUntilLines`/`unfoldLimitLines` options to progressively reveal nested elidable spans breadth-first instead of collapsing everything behind the outermost elision.
- Added `minTotalLines` setting to skip summarization for short files, returning verbatim content instead.
- Added `:` selector support to `search` paths for constraining matches to specific line ranges.
- Extracted `parseLineRanges`/`parseLineRangeChunk`/`isLineInRanges` from `read.ts` into shared `path-utils.ts`.
- Removed inline_body from grammar; op sigils (↑, ↓, :) now accept no trailing content.
- Executor emits INLINE_PAYLOAD_ACCEPTED_WARNING when legacy inline form is encountered but still applies the edit leniently.
- Updated prompt docs and all tests to use bare op + `+`-prefixed continuation rows.
- Changed two test expectations from exact string match (toBe) to substring match (toContain) for the '(no output)' text.
- This allows tests to pass when the output contains additional content beyond the expected string.
- Measured bash wall-clock duration for direct, terminal-bridge, and interactive execution paths.
- Recorded wall time in result notices and details, then stripped the duplicated literal notice during shell rendering.
- Updated the renderer to include wall time in the status label and added tests for the new wall-time behavior.
- Changed unprefixed continuation lines from a hard error to accepted implicit payload with a warning.
- Demoted inner `LINE:TEXT` ops whose anchors fall inside a pending `A-B:` block to payload continuation lines instead of raising an overlap error.
- Added `IMPLICIT_CONTINUATION_WARNING` and `PAYLOAD_LINE_PREFIX_DEMOTED_WARNING` constants for both new lenient paths.
- Changed multiline payload syntax so continuation lines must start with `+`; that prefix is stripped before writing.
- Raw unprefixed lines after an op now throw an error instead of being silently accepted as payload.
- Removed `PAYLOAD_LINE_PREFIX_DEMOTED_WARNING` and the nested-replace demotion path; inner `N:` ops inside a pending `A-B:` now raise an overlap error.
- Raw blank lines between ops are ignored; use `+` alone for an empty payload line.
- Handled an inner `replace` op that appears inside a pending multiline `A-B:` block by appending its body to the outer payload and preserving `LINE:`/`A-B:` body content as continuation.
- Retained same-range replace-pair coalescing while adding a warning when nested replace anchors are demoted from op markers to payload lines.
- Added hashline tests for nested payload demotion behavior, expected warnings, and non-demoted out-of-range `N:` replacements.
- Single-line pure-insert duplicates are ambiguous (e.g., `N↓}` may be an anchor echo or an intentional delimiter), so single-line absorb logic was removed.
- Multi-line context echo absorption is unchanged; still gated on `autoDropPureInsertDuplicates`.
- Updated tests to expect literal output for previously auto-dropped single-line cases.
- Changed identical `A-B:` duplicate ops to last-wins coalesce with a warning, fixing spurious anchor-conflict errors when models emit before/after pairs.
- Non-identical overlap shapes (different ranges, replace+delete, delete+delete) still throw.
- Added new file hash line to edit tool result output after successful apply.
- In `resolveApproval`, yolo mode now returns the user policy directly (`allow`/`prompt`/`deny`) and ignores tool `override` prompts.
- Updated approval-mode and approval unit tests to match the new behavior for critical bash patterns under yolo and auto-approve.
- Updated docs and settings metadata to describe yolo as user-policy-driven rather than override-driven.
- Removed `href`, `hrefr`, and `hline` Handlebars helpers along with shared hashline anchor state; unused by any template.
- Changed blank lines between ops from silent separators to literal payload lines appended to the open op.
- Added overlapping-delete validation to reject before/after-block patch patterns.
- Simplified hashline prompt doc, removing template-helper examples and tightening rules.
- Adjusted handoff test Promise resolver typings to use non-undefined string values.
- Updated the mocked generateHandoff promise to resolve with a string handoff value.
- Resolved the pending handoff promise with "handoff" in test cleanup to satisfy the contract.
- Short-circuited `agent_end` when `#checkCompaction` deferred handoff, skipping rewind/todo passes and `agent.continue()` race.
- Aborted retry/compaction paths in `AgentSession.dispose()` before draining post-prompt tasks so `/exit` and Ctrl+C no longer hang.
- Added handoff-deadlock regression tests in `agent-session-handoff.test.ts` to prevent reordering races.
- Added non-structural single-line prefix and suffix duplicate checks for `A-B:` replacement ranges.
- Gated the new boundary absorber behind `autoDropPureInsertDuplicates` and integrated it with existing structural and multi-line hashline absorption logic.
- Updated hashline schema documentation, prompts, and tests, and recorded the behavior change in the changelog.
- Added `ToolTier`, `ToolApproval`, and `ToolApprovalDecision` types and exported approval APIs.
- Updated approval-mode options from `auto|prompt|custom` to `always-ask|write|yolo` and defaulted mode to `yolo`.
- Changed approval resolution to apply per-tool decisions first, then mode-tier limits, with legacy-mode migration.
- Assigned read/write/exec `approval` and approval-detail prompts across built-in, custom, extension, and MCP tools.
- Decouple the per-tool approval gate from extension presence. ExtensionRunner
and the ExtensionToolWrapper that hosts the gate are now constructed
unconditionally in createAgentSession. Previously the runner was only built
when extensionsResult.extensions.length > 0, so the entire approval system
silently disappeared for sessions with no extensions loaded — any
tools.approvalMode: prompt|custom setting was a no-op without feedback.
Today this hole was masked by createAutoresearchExtension always being
pushed inline; the unconditional construction makes the safety invariant
explicit, and a new regression test in approval-mode.test.ts pins it.
- Extend CRITICAL_BASH_PATTERNS to cover remote-fetch-then-execute shapes
that the original `bash <(curl …)` regex missed:
- `source <(curl …)` / `. <(curl …)` (anchored at command boundary so
`find . -name foo` doesn't false-positive)
- `eval "$(curl …)"` / `eval $(curl …)` / `eval `curl …``
Also adds `chmod -R` symbolic-mode forms (`u+x`, `u+rwx,o+w …`) targeting
filesystem root, and `tee` / `tee -a` writes to /etc/{passwd,shadow,sudoers}
(the standard way to write root-owned files without redirect). Benign
forms (`source ./local.sh`, `chmod -R u+x ./build`, `tee /var/log/app.log`,
`eval "$VAR"`) are pinned negative in the test suite.
- Extend formatApprovalPrompt with payload previews for the destructive tools
that previously rendered as bare `Allow tool: <name>`: eval (language +
first cell's code), task (agent + first task's id + assignment), ast_edit
(first op's pattern / replacement / paths), browser (action + tab + url +
code), and write content (alongside path). For `task` in particular this
closes the gap that docs/approval-mode.md's "parent's approval covers the
subagent" claim was waving at — the prompt now actually shows what's being
delegated.
- Tighten isMcpToolName: drop the fallback `|| toolName.includes("__")` so
an extension tool legally named `my__feature` or `pkg__util__do` is no
longer falsely labelled `Origin: MCP server tool` in the approval prompt.
Strict `mcp__` prefix only.
- Revert the cargo-cult `{ autoApprove: true } as AgentToolContext` insertions
in agent-session-python-cleanup.test.ts and sdk-move-cwd.test.ts. The tests
create sessions without passing settings, so the wrapper falls through to
approvalMode "auto" automatically; the explicit flag was unnecessary and
the `as AgentToolContext` cast hid that autoApprove lives on
CustomToolContext, not AgentToolContext.
- Document in commands/launch.ts the dual --auto-approve declaration (oclif
Flags for --help, manual parseArgs for runtime) so a future rename catches
both call sites.
- Promote the subagent caveat in docs/approval-mode.md to a callout near the
top: anything `task` is asked to do runs unattended once the parent task
call is approved.
Verification:
- bun test packages/coding-agent/test/tools/approval.test.ts → 75 pass / 0 fail
(was 57; +18 cases covering new remote-exec patterns, chmod symbolic, tee
/etc, isMcp negative, and eval/task/ast_edit/browser/write payload previews)
- bun test packages/coding-agent/test/tools/approval-mode.test.ts → 7 pass /
0 fail (was 7; +1 case asserting extensionRunner is always constructed)
- bun tsc --noEmit -p packages/coding-agent → clean
- bun x biome check . → clean
- Windows EBUSY tempdir-cleanup noise in agent-session-python-cleanup and
sdk-move-cwd is pre-existing on this branch (already documented in the
PR body) and absent on Linux CI.
- approval: user 'tool: deny' now wins over critical-pattern override
(the override only tightens allow->prompt; it must never re-arm a denied tool).
- approval: rename hindsight policy keys to match registered tool names
(recall/retain/reflect, not hindsight_recall/hindsight_retain).
- approval: head+tail truncation for bash/ssh command prompts so a
destructive suffix buried after a long benign preamble stays visible.
- task/executor: force tools.approvalMode='auto' in createSubagentSettings
so subagents (which have no UI) cannot deadlock on per-tool prompts;
the parent's approval of the task call is the authorization.
- docs/approval-mode: rewrite so every example surfaces tools.approvalMode
and explains that tools.approval is ignored outside 'custom' mode.
New global setting under /settings -> Interaction that controls the tool
approval flow:
auto (default) Skip every approval prompt — yolo. Matches --auto-approve.
prompt Built-in per-tool defaults only. Destructive tools (bash,
edit, write, eval, ssh) require confirmation; read-only
tools auto-allow; tools.approval.<tool> overrides ignored.
custom tools.approval.<tool> config wins. Built-in defaults only
fall back for tools the user hasn't configured. Critical
safety patterns (rm -rf /, fork bombs, curl|bash) still
prompt even when the tool is user-allowed.
The CLI --auto-approve / --yolo flag always wins regardless of the setting,
preserving the automation/CI path.
Wires through ExtensionToolWrapper.execute(): the wrapper reads
tools.approvalMode from settings, derives userPolicies only for custom mode,
and feeds the existing requiresApproval() resolver. Resolution order inside
requiresApproval already places user config above built-in defaults, so
'config wins' falls out naturally in custom mode.
Adds test/tools/approval-mode.test.ts covering all three modes, the CLI
override, the built-in fallback in custom mode, and the critical-pattern
override that fires even when bash is user-allowed.
The approval gate added in 0efa60b7d requires either a UI runner or an
autoApprove context flag. The python-cleanup tests call EvalTool.execute
directly, bypassing the agent loop that normally supplies context.
Pass { autoApprove: true } as AgentToolContext at three direct call sites.
This matches the in-loop behaviour for tests that opt into approval-free
execution and unblocks CI for #1378.
Re-introduces the per-tool approval system from luzidd's commit 39124f3 (which
is no longer reachable from main) and improves it before re-landing.
What's restored:
- ApprovalPolicy (allow/deny/prompt) plus DEFAULT_APPROVAL_POLICIES.
- ACTION_EXCEPTIONS registry (LSP read-only, bash critical patterns).
- getApprovalPolicy() six-level resolution order.
- ExtensionToolWrapper.execute() gate before extension handlers.
- --auto-approve / --yolo CLI flag and tools.approval.<tool> user config.
- docs/approval-mode.md user guide.
What's improved over the original:
- Replaced unchecked 'as any' casts with typed unknown narrowing helpers.
- Validate userConfig values: invalid strings, numbers, etc. fall through to
the built-in default instead of being silently honoured (typo no longer
locks a tool out or grants implicit approval).
- Expanded CRITICAL_BASH_PATTERNS: chmod -R /, chown -R /, bash <(curl ...),
writes to /etc/passwd|shadow|sudoers, shutdown/reboot/halt/init 0,
kill -9 1, nc -e / nc -c reverse shells. Pattern shapes require a
command-position boundary so 'npm run reboot-tests' and 'echo "shutdown the
queue"' don't false-positive.
- Added DEBUG_READONLY_ACTIONS exception so DAP inspection actions (threads,
stack_trace, variables, scopes, read_memory, …) auto-allow while
execution-side actions (launch, attach, continue, evaluate, write_memory,
set_breakpoint, …) still prompt.
- formatApprovalPrompt: labels mcp__<server>__<tool> calls as MCP server
tools, surfaces ssh host + command, recognises the modern § hashline header
for edit, and truncates >240-char fields so a heredoc-sized body cannot
blow out the confirmation dialog.
- Test suite grown from 40 to 57 cases — new coverage for invalid user
config, the extended critical-bash patterns, benign-keyword negatives,
debug exceptions, MCP/ssh prompt formatting, and command truncation.
Verification:
- bun test packages/coding-agent/test/tools/approval.test.ts -> 57 pass
- bun x biome check . -> clean
- bun run check:ts across all 9 workspaces -> clean
- Updated the auth-gateway OpenAI responses caching test to use shared E2E helper utilities and the common gateway URL constant.
- Initialized and reset in-memory settings in the nested live rendering test fixture to isolate test state between runs.
- Rejected negative values in addition to non-numeric ones, falling back to per-server config or default 30s.
- Emitted a logger warning when an invalid env value is ignored.
- Added tests covering negative and non-numeric rejection cases.
- Extended `buildWellKnownUrls` and `#resolveRegistrationEndpoint` to try `/.well-known//` as a third candidate after origin-root and path-prefixed forms.
- Fixed single-segment path handling so `/my-service` is treated as the gateway prefix rather than dropped.
- Fixed missing `await` on `#tryWellKnownForRegistration` that caused path-prefixed fallback to return an unresolved Promise.
- Added tests for single-segment prefix discovery and RFC 8414 path-ful issuer fallback.
- Dropped the `fileType: natives.FileType.File` restriction so glob searches can return directories as well as files.
- Updated the find tool prompt to document directory results and trailing-slash output.
- Added tests verifying directory matches are included and emitted with a trailing `/`.
Threaded cache freshness/authoritativeness through #loadCachedStandardProviderModels so dropProviderModels only fires when the cached Vertex project-catalog row is both fresh and authoritative. A stale or non-authoritative snapshot (e.g. after ADC discovery failure rewrote the row with authoritative=0) now keeps the bundled Gemini fallback in place, which would otherwise be the last working catalog in API-key-only environments.
Refs #1412
Added Google Vertex OpenAI-compatible model discovery with ADC auth and treated authoritative Vertex project catalogs as replacements for bundled Gemini fallbacks in the model registry.
Fixes#1412