mirror of
https://github.com/stablyai/orca.git
synced 2026-10-08 08:02:32 +00:00
Pi (pi.dev) and OMP are goal/mission agents whose normalizePiCompatibleEvent maps milestone agent_end -> hook state 'done' while they are still working. observeHookStatus gated the quiet window on `workingStatusObserved`, so these intermediate 'done' events (workingStatusObserved === false) fell through to an immediate "agent finished" notification while the TUI kept spinning, and a follow-up working event could not cancel it. Route Pi/OMP 'done' through the existing quiet window via a new doneShouldUseQuietWindow() predicate (delegating to a shared isPiCompatibleAgentType on the canonical PiAgentKind), so resumed work cancels the premature notification. Codex and other turn-end-only producers keep their immediate dispatch. Also harden the process-exit backstop: skip the agent-evidence teardown while a quiet-window 'done' is pending, otherwise a poll that finds the agent gone would clear hasAgentRunEvidence and the timer would silently drop the real completion. Co-authored-by: Orca <help@stably.ai>
74 lines
3.2 KiB
TypeScript
74 lines
3.2 KiB
TypeScript
import { TUI_AGENT_CONFIG } from './tui-agent-config'
|
|
import { getCommandTokenPathBasename, getFirstCommandToken } from './command-token-scanner'
|
|
|
|
/**
|
|
* Pi-compatible agent kinds. Both Pi and OMP (omp.sh) consume the same
|
|
* `PI_CODING_AGENT_DIR` env contract and the same extension API, but each
|
|
* defaults its on-disk config dir to a different `~/.<kind>/agent` path.
|
|
* Orca's managed extension installer needs to know which agent is being
|
|
* launched so it targets the user's actual source dir for THAT agent, with no
|
|
* cross-agent fallback
|
|
* (otherwise switching agents in the same workspace silently shadows the
|
|
* other agent's user extensions).
|
|
*/
|
|
export type PiAgentKind = 'pi' | 'omp'
|
|
|
|
/**
|
|
* True when `agentType` names a Pi-compatible (goal/mission) kind. These agents
|
|
* emit milestone `agent_end` events between steps while still working, so they
|
|
* are treated differently from agents that only signal completion at turn end.
|
|
*/
|
|
export function isPiCompatibleAgentType(
|
|
agentType: string | null | undefined
|
|
): agentType is PiAgentKind {
|
|
return agentType === 'pi' || agentType === 'omp'
|
|
}
|
|
|
|
const OMP_LAUNCH_CMD = TUI_AGENT_CONFIG.omp.launchCmd
|
|
|
|
// Why: regex carved to avoid matching `pi` inside `pip`, `mpi`, `api`,
|
|
// `python`, or `omp` inside `comp`, `omp.sh` (acceptable - that's literally
|
|
// the binary), `omp-foo`, etc. The leading boundary excludes alnum/underscore
|
|
// AND `-`/`.`/`/`/`\\` so that `~/bin/pi` or `./omp` still match but
|
|
// `mpi`/`pomp` do not. Trailing boundary allows whitespace, end-of-string,
|
|
// shell separators, or argv-style flags (`pi -v`, `omp --help`).
|
|
const BOUNDARY_BEFORE = `(?:^|[\\s;&|('"\`])`
|
|
const BOUNDARY_AFTER = `(?:$|[\\s;&|)'"\`])`
|
|
const PATH_PREFIX = `(?:[^\\s;&|('"\`]*[\\\\/])?`
|
|
|
|
function makeLaunchCmdRegex(launchCmd: string): RegExp {
|
|
// Why: launchCmd may be a multi-token string ("hermes --tui"); only the
|
|
// first token is the binary name. Use that for matching.
|
|
const binary = getCommandTokenPathBasename(getFirstCommandToken(launchCmd))
|
|
const escaped = binary.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
return new RegExp(
|
|
`${BOUNDARY_BEFORE}${PATH_PREFIX}${escaped}(?:\\.cmd|\\.exe|\\.sh)?${BOUNDARY_AFTER}`,
|
|
'i'
|
|
)
|
|
}
|
|
|
|
const OMP_REGEX = makeLaunchCmdRegex(OMP_LAUNCH_CMD)
|
|
|
|
/**
|
|
* Identify the Pi-compatible agent kind a launch command targets.
|
|
*
|
|
* Returns 'omp' when the command launches OMP (`omp` / `omp.sh`), otherwise
|
|
* defaults to 'pi'. Defaulting to 'pi' preserves prior behavior for the
|
|
* non-launch case (e.g. bare shells that may later invoke `pi`) where Orca
|
|
* prepared Pi integration by default.
|
|
*
|
|
* NEVER cross-fall-back: a missing source dir for the resolved kind means
|
|
* "create that kind's extension dir only" - the other agent's dir MUST NOT
|
|
* be substituted.
|
|
*/
|
|
export function detectPiAgentKindFromCommand(command: string | undefined): PiAgentKind {
|
|
if (typeof command === 'string' && OMP_REGEX.test(command)) {
|
|
return 'omp'
|
|
}
|
|
// Why: PI launches and the no-command (bare-shell) fallback both resolve to
|
|
// 'pi'. A bare shell that later invokes `pi` keeps the historical default;
|
|
// if it later invokes `omp`, the status extension re-routes at runtime based
|
|
// on the executable name so attribution still lands on OMP.
|
|
return 'pi'
|
|
}
|