mirror of
https://github.com/stablyai/orca.git
synced 2026-10-02 08:02:02 +00:00
* feat(agents): add first-class Muse Code harness Add Muse as a supervised Orca agent across desktop, mobile, session history, source control, local hooks, SSH, WSL, and native Windows. Preserve user settings, support Muse 1.3 hook environment allowlists, and recognize versioned foreground processes. Include question, waiting, completion, resume, and readiness coverage. Co-authored-by: homesh-dev <300847526+homesh-dev@users.noreply.github.com> Co-authored-by: jeffhuen <32542276+jeffhuen@users.noreply.github.com> Co-authored-by: John Cusack <johncusackccm@gmail.com> Co-authored-by: Adrien De oliveira <75085839+adriendeoliveira@users.noreply.github.com> * test(agents): cover Muse remote hook registration * test(agents): cover Muse hook and source-control contracts * test(agents): exclude Muse hook metadata from script mode check * test(agents): keep Muse skill picker coverage stable * test(ai-vault): include Muse in every-agent fixture * test(mobile): repin Muse agent icon closure * fix(muse): detect questions and approvals from structured Muse signals Muse 1.3 fires no hook for request_user_input, so a pending question left the pane "working". Its internal reminder subagents also post hooks with their own session ids (even after Stop), which surfaced "tool failed" rows and flipped finished panes back to working. - Read pending questions from Muse's session log (user_input_prompt_requested/settled) via the existing transcript poll, now generalized from Codex subagents to Muse on main and relay. - Drop child-session hooks (SubagentStart ids, or turn_id === session_id). - Treat Notification permission_prompt as the approval wait; PermissionRequest also fires for auto-approved calls, so it only caches the approval card. - Ignore Notification copy as the prompt; poll replays are not new prompts or turn boundaries. - Allowlist USERPROFILE so Windows cmd AutoRun doesn't fail every hook. * perf(muse): parse only question events from the session log Most Muse session-log lines are large model/tool records. Filter raw lines by the user_input_prompt_ marker before JSON.parse via an optional readJsonlCursor line filter. * fix(muse): unwrap batched log records and scope questions to the live turn Review follow-ups: question events inside retained_frame batches were skipped, and a question left open by a crash or interrupt stayed pending for the pane's life. Share the history scanner's retained_frame unwrapper, and only report a pending question whose run_id matches the hook turn_id. * refactor(muse): drop type assertion in retained_frame unwrap * fix(agent-hooks): satisfy exhaustive-switch lint in transcript poll policy --------- Co-authored-by: Adrien De oliveira <75085839+adriendeoliveira@users.noreply.github.com>
191 lines
7.1 KiB
TypeScript
191 lines
7.1 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
DEFAULT_TERMINAL_INACTIVE_PANE_OPACITY,
|
|
getDefaultNotificationSettings,
|
|
getDefaultPrimarySelectionMiddleClickPaste,
|
|
getDefaultTerminalRightClickToPaste,
|
|
getDefaultSettings
|
|
} from './constants'
|
|
|
|
describe('getDefaultSettings', () => {
|
|
it('uses platform-consistent separators for the default workspace directory', () => {
|
|
expect(getDefaultSettings('/Users/alice').workspaceDir).toBe('/Users/alice/orca/workspaces')
|
|
expect(getDefaultSettings('C:\\Users\\alice').workspaceDir).toBe(
|
|
'C:\\Users\\alice\\orca\\workspaces'
|
|
)
|
|
})
|
|
|
|
it('enables gitignored file decorations by default', () => {
|
|
expect(getDefaultSettings('/tmp').showGitIgnoredFiles).toBe(true)
|
|
})
|
|
|
|
it('uses list view for Source Control changes by default', () => {
|
|
expect(getDefaultSettings('/tmp').sourceControlViewMode).toBe('list')
|
|
})
|
|
|
|
it('keeps Source Control changes first by default', () => {
|
|
expect(getDefaultSettings('/tmp').sourceControlGroupOrder).toBe('changes-first')
|
|
})
|
|
|
|
it('defaults mobile pairing to discovered network addresses', () => {
|
|
expect(getDefaultSettings('/tmp').mobilePairingCustomAddress).toBeNull()
|
|
expect(getDefaultSettings('/tmp').mobilePairingCustomAddresses).toEqual([])
|
|
})
|
|
|
|
it('keeps first-work branch auto-renaming on by default for new settings', () => {
|
|
expect(getDefaultSettings('/tmp').autoRenameBranchFromWork).toBe(true)
|
|
expect(getDefaultSettings('/tmp').autoRenameBranchFromWorkDefaultedOn).toBe(true)
|
|
})
|
|
|
|
it('uses a block terminal cursor by default for new settings', () => {
|
|
expect(getDefaultSettings('/tmp').terminalCursorStyle).toBe('block')
|
|
expect(getDefaultSettings('/tmp').terminalCursorStyleDefaultedToBlock).toBe(true)
|
|
})
|
|
|
|
it('allows OSC 52 clipboard writes by default for new settings', () => {
|
|
expect(getDefaultSettings('/tmp').terminalAllowOsc52Clipboard).toBe(true)
|
|
expect(getDefaultSettings('/tmp').terminalAllowOsc52ClipboardDefaultedOnForAllUsers).toBe(true)
|
|
})
|
|
|
|
it('enables separate light terminal theme by default', () => {
|
|
expect(getDefaultSettings('/tmp').terminalUseSeparateLightTheme).toBe(true)
|
|
})
|
|
|
|
it('keeps inactive terminal panes readable by default', () => {
|
|
expect(getDefaultSettings('/tmp').terminalInactivePaneOpacity).toBe(
|
|
DEFAULT_TERMINAL_INACTIVE_PANE_OPACITY
|
|
)
|
|
})
|
|
|
|
it('asks before closing terminals with running processes by default', () => {
|
|
expect(getDefaultSettings('/tmp').skipCloseTerminalWithRunningProcessConfirm).toBe(false)
|
|
})
|
|
|
|
it('uses system language by default', () => {
|
|
expect(getDefaultSettings('/tmp').uiLanguage).toBe('system')
|
|
})
|
|
|
|
it('defaults the menu bar icon on so the value round-trips across platforms', () => {
|
|
expect(getDefaultSettings('/tmp').showMenuBarIcon).toBe(true)
|
|
})
|
|
|
|
it('shows terminal link actions by default', () => {
|
|
expect(getDefaultSettings('/tmp').terminalLinkActionPopoverEnabled).toBe(true)
|
|
})
|
|
|
|
it('confirms before closing pinned tabs by default', () => {
|
|
expect(getDefaultSettings('/tmp').confirmClosePinnedTab).toBe(true)
|
|
})
|
|
|
|
it('keeps file-editor word wrapping enabled by default', () => {
|
|
expect(getDefaultSettings('/tmp').editorWordWrap).toBe(true)
|
|
})
|
|
|
|
it('keeps rich Markdown spellcheck enabled by default', () => {
|
|
expect(getDefaultSettings('/tmp').richMarkdownSpellcheckEnabled).toBe(true)
|
|
})
|
|
|
|
it('enables Source Control AI by default without pinning a separate agent', () => {
|
|
expect(getDefaultSettings('/tmp').commitMessageAi).toMatchObject({
|
|
enabled: true,
|
|
agentId: null,
|
|
selectedModelByAgent: {}
|
|
})
|
|
expect(getDefaultSettings('/tmp').sourceControlAi).toMatchObject({
|
|
enabled: true,
|
|
agentId: null,
|
|
selectedModelByAgent: {},
|
|
instructionsByOperation: {
|
|
commitMessage: '',
|
|
pullRequest: '',
|
|
branchName: ''
|
|
}
|
|
})
|
|
})
|
|
|
|
it('keeps compact worktree cards disabled by default', () => {
|
|
expect(getDefaultSettings('/tmp').compactWorktreeCards).toBe(false)
|
|
})
|
|
|
|
it('keeps per-workspace environments disabled by default', () => {
|
|
expect(getDefaultSettings('/tmp').experimentalEphemeralVms).toBe(false)
|
|
})
|
|
|
|
it('keeps the agent dashboard popout disabled by default', () => {
|
|
expect(getDefaultSettings('/tmp').experimentalAgentDashboardPopout).toBeUndefined()
|
|
expect(getDefaultSettings('/tmp').experimentalAgentDashboardShowIdle).toBeUndefined()
|
|
})
|
|
|
|
it('routes fresh Codex profiles through the real-home rollout by default', () => {})
|
|
|
|
it('defaults local Windows projects to the host runtime', () => {
|
|
expect(getDefaultSettings('/tmp').localWindowsRuntimeDefault).toEqual({
|
|
kind: 'windows-host'
|
|
})
|
|
})
|
|
|
|
it('suppresses notifications for the focused worktree by default for new users', () => {
|
|
expect(getDefaultNotificationSettings().suppressWhenFocused).toBe(true)
|
|
expect(getDefaultSettings('/tmp').notifications.suppressWhenFocused).toBe(true)
|
|
})
|
|
|
|
it('defaults agent launch args to yolo mode where the CLI supports it', () => {
|
|
const settings = getDefaultSettings('/tmp')
|
|
|
|
expect(settings.agentDefaultArgs).toMatchObject({
|
|
claude: '--dangerously-skip-permissions',
|
|
codex: '--dangerously-bypass-approvals-and-sandbox',
|
|
gemini: '--yolo',
|
|
cursor: '--yolo',
|
|
muse: '--yolo',
|
|
copilot: '--yolo',
|
|
grok: '--permission-mode bypassPermissions'
|
|
})
|
|
expect(settings.agentDefaultArgs).not.toHaveProperty('opencode')
|
|
expect(settings.agentDefaultArgs).not.toHaveProperty('kilo')
|
|
expect(settings.agentDefaultEnv).toMatchObject({
|
|
goose: { GOOSE_MODE: 'auto' }
|
|
})
|
|
expect(settings.agentYoloDefaultsMigrated).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('getDefaultPrimarySelectionMiddleClickPaste', () => {
|
|
it('enables primary selection paste on Linux by default', () => {
|
|
expect(getDefaultPrimarySelectionMiddleClickPaste('linux')).toBe(true)
|
|
})
|
|
|
|
it('enables primary selection paste on macOS by default', () => {
|
|
expect(getDefaultPrimarySelectionMiddleClickPaste('darwin')).toBe(true)
|
|
})
|
|
|
|
it('leaves primary selection paste opt-in on Windows', () => {
|
|
expect(getDefaultPrimarySelectionMiddleClickPaste('win32')).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('getDefaultTerminalRightClickToPaste', () => {
|
|
it('defaults on only for Windows', () => {
|
|
expect(getDefaultTerminalRightClickToPaste('win32')).toBe(true)
|
|
expect(getDefaultTerminalRightClickToPaste('darwin')).toBe(false)
|
|
expect(getDefaultTerminalRightClickToPaste('linux')).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('MiniMax defaults', () => {
|
|
it('starts MiniMax with empty group id and the canonical default model', () => {
|
|
const settings = getDefaultSettings('/tmp')
|
|
// Why: the fetcher reads these defaults on first launch. An empty
|
|
// group id is the signal that the fetcher must pull the value from
|
|
// the cookie itself, and "general" matches the model name the
|
|
// MiniMax usage endpoint exposes by default.
|
|
expect(settings.minimaxGroupId).toBe('')
|
|
expect(settings.minimaxUsageModels).toBe('general')
|
|
})
|
|
|
|
it('defaults the MiniMax endpoint to overseas', () => {
|
|
const settings = getDefaultSettings('/tmp')
|
|
expect(settings.minimaxEndpoint).toBe('overseas')
|
|
})
|
|
})
|