Commit Graph

15 Commits

Author SHA1 Message Date
Christian Stewart a45a62860b fix(coding-agent): strip continued ids after extension reparse
Reuse raw argv during the extension-aware parse so explicit session ids are removed without dropping a following user prompt.

Signed-off-by: Christian Stewart <christian@aperture.us>
2026-07-11 16:19:05 -07:00
Christian Stewart 21446db15a fix(coding-agent): resolve continued ids before extension flags
Identify explicit session ids from the token immediately following --continue so extension-owned values remain untouched while session resolution targets the requested id.

Signed-off-by: Christian Stewart <christian@aperture.us>
2026-07-11 16:19:05 -07:00
Christian Stewart 39290eade8 fix(coding-agent): deferred continue normalization past extension flags
Skipped UUID-shaped positional normalization while startup parsing still contains unrecognized extension flags, then normalized the extension-aware parse.

Signed-off-by: Christian Stewart <christian@aperture.us>
2026-07-11 16:19:05 -07:00
Christian Stewart 8ff4590d31 fix(coding-agent): preserved continue target after flag reparse
Normalized UUID-shaped --continue targets on the extension-aware argument parse so the session id cannot leak back into the initial prompt.

Signed-off-by: Christian Stewart <christian@aperture.us>
2026-07-11 16:19:05 -07:00
Ogrodev 73b1f895d1 Merge upstream/main into feat/profiles-and-alias 2026-06-08 15:54:57 -03:00
can1357 9d457f73d9 test: migrated test imports to package subpath exports
- Replaced relative `../src` imports with `@oh-my-pi/pi-ai` and `@oh-my-pi/pi-agent-core` subpaths.
2026-06-08 19:03:55 +02:00
Ogrodev 7c6f77de0b fix(coding-agent): harden profile bootstrap and aliases 2026-06-04 23:55:15 -03:00
can1357 1fbc2cbd79 fix(coding-agent): let extension flags shadow same-named built-ins in parseArgs
Follow-up to #1503. When an extension registered a flag whose name collides
with a value-taking built-in — e.g. plan-mode's boolean `--plan` vs the
built-in `--plan <plan-model>` selector — the extension-aware reparse still
took the built-in branch. `omp --extension plan-mode --plan "review the diff"`
consumed "review the diff" as the plan-model value, leaving parsed.messages
empty and overwriting result.plan with the prompt text. recoverFlagValue only
patched the extension flag value, not the corrupted parsed object that
applyExtensionFlags returns as initialArgs.

Fix at the source: parseArgs now checks the registered extension-flag set
BEFORE the built-in branches, so a registered flag is parsed with the
extension's semantics (boolean toggle / string value) and surfaces in
unknownFlags without consuming the following token or touching the built-in
field. This makes recoverFlagValue dead, so applyExtensionFlags is simplified
to read resolved values straight from unknownFlags.

Tests: parseArgs-level shadowing guard (boolean --plan keeps the message and
leaves result.plan unset); applyExtensionFlags message/built-in-field
preservation for colliding boolean (--plan) and string (--model) flags;
non-colliding flag-looking-value rule retained. Verified the new guards fail
without the shadowing fix.
2026-05-31 06:36:33 +02:00
can1357 b48b825344 fix(coding-agent): process @file before session creation; drop built-in flag-name list
Two review fixes for the extension-flag/initial-prompt work:

1. @file ordering — `processFileArguments` runs `process.exit(1)` on a
   missing/unreadable file. It had been moved after `createSession`, which
   writes the terminal breadcrumb eagerly (SessionManager.create →
   #newSessionSync), so `omp @missing.md "x"` left a junk session/breadcrumb
   behind before exiting.

   Resolve extension-registered CLI flags BEFORE creating the session: load the
   session's extensions up front (new `loadSessionExtensions` helper, the single
   source of createAgentSession's discovery-branch logic), build an
   ExtensionFlagSink straight from the loaded extensions + runtime, re-parse
   argv, then process @file args — all before any session exists. The loaded
   result is handed back to createAgentSession via `preloadedExtensions` (now
   checked before `disableExtensionDiscovery`, so it can't double-load) and the
   same EventBus is shared, so no extra work. This keeps the P1#1 fix
   (`--flag @value` is the flag's value, not a file) while failing fast with no
   session side effects.

2. "Can we avoid the big list of names?" — removed the hand-maintained
   `BUILTIN_FLAG_NAMES` set (and its stale "rejected at registration" doc).
   `applyExtensionFlags` now always falls back to recovering a flag's value from
   argv when parseArgs didn't surface it; the recovery scan mirrors parseArgs's
   consumption rules (flag-looking space-form values stay their own flag) and is
   a no-op for flags that were absent or already surfaced, so no list of
   built-in names is needed.

Adds `ExtensionRunner.aggregateFlags` (static) so getFlags and the CLI's
pre-session sink share one implementation.

Tests: pre-session flag resolution via the exact main.ts sink pattern;
list-free recovery of an arbitrary colliding built-in (`--model`); and the
flag-looking-value rule. Verified typecheck + extension/runner/acp suites.
2026-05-31 06:23:15 +02:00
Erik Svilich 38d2341300 fix(coding-agent): preserve built-in-colliding extension flags instead of rejecting
The previous collision guard threw in registerFlag, which broke loading the
bundled plan-mode example extension (it registers `--plan`, also a built-in)
even when `--plan` was never passed — making a documented extension unusable.

Registering a built-in-named flag is a supported pattern: `--plan` is both the
built-in plan-model selector and plan-mode's boolean mode toggle, and the value
must reach both. So instead of rejecting, preserve delivery: remove the guard,
and in applyExtensionFlags recover a colliding flag's value from argv
(resolveCollidingFlag) when parseArgs routed it to the built-in branch and it
never reached unknownFlags. Non-colliding flags are unchanged (peer-* etc.).

Verified the real bundled plan-mode.ts loads with --plan registered and
delivered; replaced the reject-test with a loads-without-throwing regression
plus colliding-flag delivery coverage.
2026-05-31 04:45:51 +02:00
Erik Svilich d6f887d152 fix(coding-agent): close remaining extension-flag edge cases (GPT-5.5 review)
Three issues from an adversarial review, all rooted in the startup argv parse
running before extensions load:

1. Flag-looking string values (`--name --print`): the extension-aware reparse
   consumed the following token as the value, disagreeing with the startup
   parse that treated `--print` as the built-in flag — so the reparse could
   silently flip command shape. Extension string flags now consume a following
   token only in `--flag=value` form or when it is not flag-looking; pass a
   flag-looking value as `--flag=value`. Keeps both parses consistent.

2. `@file` string values (`--target @notes.md`): file args were processed from
   the startup parse, which misreads the value as a file and reads it into the
   prompt. processFileArguments now runs on the extension-aware parse
   (initialArgs.fileArgs); pipedInput stays early for mode detection.

3. Built-in collisions: an extension flag named like a built-in (e.g. `model`)
   was consumed by the built-in branch and never delivered to the runner.
   registerFlag now rejects names in BUILTIN_FLAG_NAMES with a clear error
   (isolated per-extension by loadExtension's try/catch).

Adds tests for all three plus the documented startup-parse misclassification.
2026-05-31 04:45:51 +02:00
Erik Svilich 295c52a307 fix(coding-agent): drop unconsumed --flag=value values for non-consuming flags
Addresses review: a boolean flag in equals form still leaked its value. parseArgs
splices `--headless=true` into `--headless`, `true` so value-consuming flags can
pick the value up via `args[++i]`; a boolean flag sets itself without consuming
it, leaving `true` to fall through as a positional message — and since
applyExtensionFlags feeds this parse into buildInitialMessage, `omp
--headless=true "do the task"` sent `true` as the prompt.

Track the spliced value's index and, if no branch advanced past it (i.e. the
matched flag did not consume a value), drop it after the dispatch. Closes the
whole equals-form class — boolean extension flags and built-in non-consuming
flags (`--no-tools=true`, `--print=1`) alike — at the single parsing site.

Adds tests for boolean extension + built-in flags in equals form.
2026-05-31 04:45:51 +02:00
Erik Svilich b674e3a663 fix(coding-agent): unify extension-flag parsing through parseArgs
Addresses review: a string extension flag in equals form (--spawn-peer=reviewer)
was still leaking its value into the initial prompt. Root cause was a second,
hand-rolled argv parser in applyExtensionFlagValues that recognized only
`--flag` and `--flag value`, not `--flag=value`; it looked up the literal name
`spawn-peer=reviewer`, set nothing, and (because the reparse was gated on
"were values set") skipped the reparse entirely, so the extension-unaware
startup parse won — leaving `reviewer` as the first message. The extension
itself also never received the value.

Replace the duplicate parser with a single source of truth: extract
applyExtensionFlags() into cli/extension-flags.ts, which re-parses argv through
the same parseArgs() the startup pass uses (now seeded with the registered
flags) and pushes the resulting values onto the runner. parseArgs already
normalizes `--flag`, `--flag value`, and `--flag=value` identically, so no flag
form can be handled by one parser and missed by the other. The reparse is now
gated on registered-flag presence, not on values having been set.

Wires parseArgs's previously-unused `unknownFlags` output to the runner, and
removes the now-redundant parseArgs import from main.ts. Adds unit tests for
applyExtensionFlags across all flag forms (including equals form) plus the
no-runner / no-flags / no-args-passed gate cases.
2026-05-31 04:45:51 +02:00
Erik Svilich 15f6f52e08 fix(coding-agent): make parseArgs non-mutating to fix double-splice on reparse
The `--option=value` handling splices the value into the argv to reuse the
`args[++i]` path, mutating the caller's array. The post-extension reparse in
runRootCommand then ran on that already-mutated argv, so

    omp --model=sonnet --spawn-peer reviewer "review"

re-spliced `sonnet` and leaked it into the initial prompt before "review".

parseArgs now copies its input and never mutates the caller's array, so
launch, acp, and the reparse are all safe. Drops the now-redundant
`[...rawArgs]` copy at the reparse site, and adds regression coverage for the
--option=value + extension-flag combo plus input non-mutation.
2026-05-31 04:45:51 +02:00
Erik Svilich c3f3f9163d fix(coding-agent): strip extension flags from the initial prompt
The root command parses argv twice — once at startup before extensions
load (so their flag set is unknown) and once after the extension runner
is ready. buildInitialMessage was reading the first, extension-unaware
parse, so a string-valued extension flag's value leaked into the prompt:

    omp --spawn-peer reviewer "review the diff"

sent "reviewer" as the first message instead of "review the diff" — the
--spawn-peer token is dropped (it starts with "-"), but its bare value
is mis-read as the first positional message.

Build the initial message from args re-parsed with the extension flag map
(session.extensionRunner.getFlags()) whenever any extension flag was
applied, so the flag and its value are consumed before the prompt is
assembled. Generic across any flag-registering extension; no behavior
change when no extension flags are present.

Adds regression coverage for the parse/build pipeline: string + boolean
extension flags are consumed correctly, and the pre-fix leak the second
parse corrects is pinned.
2026-05-31 04:45:50 +02:00