mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
wip(omp): prove fresh settings overlay without redirecting storage
This commit is contained in:
@@ -275,6 +275,8 @@ describe('PiTitlebarExtensionService', () => {
|
||||
const content = 'agent.db credentials'
|
||||
|
||||
expect(env.PI_CODING_AGENT_DIR).toBeUndefined()
|
||||
expect(readFileSync(env.ORCA_OMP_FRESH_CONFIG, 'utf8')).toBe('autoResume: false\n')
|
||||
expect(env.ORCA_OMP_FRESH_CONFIG.startsWith(userDataDir)).toBe(true)
|
||||
expect(env.ORCA_OMP_SOURCE_AGENT_DIR).toBe(piHome)
|
||||
expect(env.ORCA_OMP_STATUS_EXTENSION).toBe(join(piHome, 'extensions', 'orca-agent-status.ts'))
|
||||
expect(existsSync(sourcePath)).toBe(false)
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
OMP_FRESH_CONFIG_FILENAME,
|
||||
OMP_FRESH_CONFIG_SOURCE,
|
||||
ORCA_OMP_FRESH_CONFIG_ENV
|
||||
} from '../../shared/omp-fresh-launch'
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
||||
import { homedir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
@@ -183,7 +188,19 @@ export class PiTitlebarExtensionService {
|
||||
// The caller resolves the effective launch environment. Reading the
|
||||
// daemon's ambient PI_CONFIG_DIR here can select the host profile for a
|
||||
// guest/WSL launch whose environment has not been hydrated yet.
|
||||
const sourceAgentDir = existingAgentDir || getDefaultPiAgentDir(kind, options?.configDirName)
|
||||
const freshConfigEnv: Record<string, string> = {}
|
||||
if (kind === 'omp') {
|
||||
const configDir = join(
|
||||
getAppEnvironment().getPath('userData'),
|
||||
OMP_MANAGED_STATUS_EXTENSION_DIR
|
||||
)
|
||||
mkdirSync(configDir, { recursive: true })
|
||||
const configPath = join(configDir, OMP_FRESH_CONFIG_FILENAME)
|
||||
writeFileSync(configPath, OMP_FRESH_CONFIG_SOURCE)
|
||||
freshConfigEnv[ORCA_OMP_FRESH_CONFIG_ENV] = configPath
|
||||
}
|
||||
const sourceAgentDir =
|
||||
existingAgentDir || getDefaultPiAgentDir(kind, options?.configDirName)
|
||||
if (kind !== 'prime-agent') {
|
||||
try {
|
||||
this.safeRemoveOverlay(this.getPtyOverlayDir(ptyId, kind), kind)
|
||||
@@ -203,7 +220,9 @@ export class PiTitlebarExtensionService {
|
||||
if (kind === 'omp') {
|
||||
const statusSource = withOrcaManagedExtensionMarker(getPiAgentStatusExtensionSource(kind))
|
||||
const statusExtensionPath = this.writeOmpFallbackStatusExtension(statusSource)
|
||||
return statusExtensionPath ? { ORCA_OMP_STATUS_EXTENSION: statusExtensionPath } : {}
|
||||
return statusExtensionPath
|
||||
? { ...freshConfigEnv, ORCA_OMP_STATUS_EXTENSION: statusExtensionPath }
|
||||
: freshConfigEnv
|
||||
}
|
||||
return {}
|
||||
}
|
||||
@@ -213,7 +232,7 @@ export class PiTitlebarExtensionService {
|
||||
}
|
||||
|
||||
const installed = this.installManagedExtensions(sourceAgentDir, kind)
|
||||
const env: Record<string, string> = {}
|
||||
const env: Record<string, string> = { ...freshConfigEnv }
|
||||
if (kind === 'omp') {
|
||||
env.ORCA_OMP_SOURCE_AGENT_DIR = installed.sourceAgentDir
|
||||
if (installed.statusExtensionPath) {
|
||||
|
||||
@@ -63,6 +63,7 @@ describe('addOrcaWslInteropEnv', () => {
|
||||
ORCA_USER_DATA_PATH: 'C:\\Users\\jin\\AppData\\Roaming\\Orca',
|
||||
ORCA_CLI_COMMAND: 'orca-ide',
|
||||
ORCA_CODEX_LAUNCH_PREFLIGHT: 'C:\\Program Files\\Orca\\resources\\bin\\orca.exe',
|
||||
ORCA_OMP_FRESH_CONFIG: 'C:\\Orca\\fresh-session.yml',
|
||||
ORCA_OMP_STATUS_EXTENSION: 'C:\\Users\\jin\\.omp\\agent\\extensions\\orca-agent-status.ts',
|
||||
ORCA_PRIME_AGENT_STATUS_EXTENSION: 'C:\\stale\\orca-agent-status.ts',
|
||||
ORCA_PANE_KEY: 'tab-1:leaf-1',
|
||||
@@ -87,6 +88,7 @@ describe('addOrcaWslInteropEnv', () => {
|
||||
expect(env.WSLENV).toContain('ORCA_CLI_COMMAND/u')
|
||||
expect(env.WSLENV).toContain('ORCA_CODEX_LAUNCH_PREFLIGHT/p')
|
||||
expect(env.WSLENV).toContain('ORCA_OMP_STATUS_EXTENSION/p')
|
||||
expect(env.WSLENV).toContain('ORCA_OMP_FRESH_CONFIG/p')
|
||||
expect(env.WSLENV).not.toContain('ORCA_PRIME_AGENT_STATUS_EXTENSION')
|
||||
expect(env.WSLENV).toContain('ORCA_PANE_KEY/u')
|
||||
expect(env.WSLENV).toContain('ORCA_TAB_ID/u')
|
||||
|
||||
@@ -99,8 +99,8 @@ export function addOrcaWslInteropEnv(env: Record<string, string>): void {
|
||||
'ORCA_WSL_HOOK_INSTANCE/u',
|
||||
'ORCA_OMP_SOURCE_AGENT_DIR/p',
|
||||
'ORCA_OMP_STATUS_EXTENSION/p',
|
||||
// A protocol name, never a path; in-guest agents read it to pick an image encoder.
|
||||
`${ORCA_IMAGE_PROTOCOL_ENV}/u`,
|
||||
'ORCA_OMP_FRESH_CONFIG/p',
|
||||
...worktreeSetupWslenvEntries(env)
|
||||
]
|
||||
applyWslenvPassthrough(env, passthroughEntries)
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { withFreshOmpLaunch } from './omp-fresh-launch'
|
||||
import { buildAgentStartupPlan } from './tui-agent-startup'
|
||||
|
||||
describe('OMP fresh launch intent', () => {
|
||||
it.each(['omp', 'omp launch', 'omp --model provider/model', 'omp --config user.yml'])(
|
||||
'%s adds the final overlay',
|
||||
(command) => {
|
||||
expect(withFreshOmpLaunch(command, 'posix')).toBe(
|
||||
`${command} --config "$ORCA_OMP_FRESH_CONFIG"`
|
||||
)
|
||||
}
|
||||
)
|
||||
it.each([
|
||||
'omp --resume id',
|
||||
'omp -r id',
|
||||
'omp --continue',
|
||||
'omp -c',
|
||||
'omp --session-dir /custom',
|
||||
'omp --no-session',
|
||||
'omp --fork id',
|
||||
'omp models',
|
||||
'omp config',
|
||||
'omp wt',
|
||||
'omp --help',
|
||||
'omp --unknown foo',
|
||||
'omp --model',
|
||||
'omp -- hello',
|
||||
'echo omp',
|
||||
'omp && echo hi'
|
||||
])('preserves %s', (command) => {
|
||||
expect(withFreshOmpLaunch(command, 'posix')).toBe(command)
|
||||
})
|
||||
it('quotes the host config path for each Windows shell', () => {
|
||||
expect(withFreshOmpLaunch('omp', 'powershell')).toBe(
|
||||
'omp --config "$env:ORCA_OMP_FRESH_CONFIG"'
|
||||
)
|
||||
expect(withFreshOmpLaunch('omp', 'cmd')).toBe('omp --config "%ORCA_OMP_FRESH_CONFIG%"')
|
||||
})
|
||||
it('keeps fresh intent out of saved resume command and environment', () => {
|
||||
const plan = buildAgentStartupPlan({
|
||||
agent: 'omp',
|
||||
prompt: 'new task',
|
||||
cmdOverrides: {},
|
||||
platform: 'linux'
|
||||
})
|
||||
expect(plan?.launchCommand).toContain('--config "$ORCA_OMP_FRESH_CONFIG"')
|
||||
expect(JSON.stringify(plan?.launchConfig)).not.toContain('ORCA_OMP_FRESH_CONFIG')
|
||||
expect(plan?.env).toBeUndefined()
|
||||
})
|
||||
it('does not require a new environment field from an older SSH relay', () => {
|
||||
const plan = buildAgentStartupPlan({
|
||||
agent: 'omp',
|
||||
prompt: 'new task',
|
||||
cmdOverrides: {},
|
||||
platform: 'linux',
|
||||
isRemote: true
|
||||
})
|
||||
expect(plan?.launchCommand).not.toContain('ORCA_OMP_FRESH_CONFIG')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,55 @@
|
||||
import { tokenizeStartupCommand, type AgentStartupShell } from './tui-agent-startup-shell'
|
||||
|
||||
export const ORCA_OMP_FRESH_CONFIG_ENV = 'ORCA_OMP_FRESH_CONFIG'
|
||||
export const OMP_FRESH_CONFIG_FILENAME = 'fresh-session.yml'
|
||||
export const OMP_FRESH_CONFIG_SOURCE = 'autoResume: false\n'
|
||||
|
||||
// Unknown flags may consume values or select a subcommand; leave those commands intact.
|
||||
const VALUE_FLAGS = new Set([
|
||||
'--model',
|
||||
'--provider',
|
||||
'--thinking',
|
||||
'--config',
|
||||
'--profile',
|
||||
'--extension',
|
||||
'-e',
|
||||
'--system-prompt',
|
||||
'--append-system-prompt',
|
||||
'--tools',
|
||||
'--skill',
|
||||
'--theme',
|
||||
'--api-key'
|
||||
])
|
||||
const SWITCH_FLAGS = new Set(['--no-extensions', '--no-skills', '--no-prompt-templates'])
|
||||
|
||||
/** Apply fresh intent to one launch command, never the saved resume configuration. */
|
||||
export function withFreshOmpLaunch(command: string, shell: AgentStartupShell): string {
|
||||
const parsed = tokenizeStartupCommand(command, shell)
|
||||
if (!parsed.ok) {
|
||||
return command
|
||||
}
|
||||
const executable = parsed.tokens[0]?.split(/[\\/]/).at(-1)?.toLowerCase()
|
||||
if (!['omp', 'omp.exe', 'omp.cmd', 'omp.bat', 'omp.sh', 'omp.js'].includes(executable ?? '')) {
|
||||
return command
|
||||
}
|
||||
let index = parsed.tokens[1] === 'launch' ? 2 : 1
|
||||
for (; index < parsed.tokens.length; index++) {
|
||||
const token = parsed.tokens[index]
|
||||
const equals = token.indexOf('=')
|
||||
const flag = equals === -1 ? token : token.slice(0, equals)
|
||||
if (VALUE_FLAGS.has(flag)) {
|
||||
if (equals === -1 && ++index >= parsed.tokens.length) {
|
||||
return command
|
||||
}
|
||||
} else if (!SWITCH_FLAGS.has(token)) {
|
||||
return command
|
||||
}
|
||||
}
|
||||
const path =
|
||||
shell === 'cmd'
|
||||
? `"%${ORCA_OMP_FRESH_CONFIG_ENV}%"`
|
||||
: shell === 'powershell'
|
||||
? `"$env:${ORCA_OMP_FRESH_CONFIG_ENV}"`
|
||||
: `"$${ORCA_OMP_FRESH_CONFIG_ENV}"`
|
||||
return `${command} --config ${path}`
|
||||
}
|
||||
@@ -667,7 +667,7 @@ describe('tui agent startup plans', () => {
|
||||
expect(plan?.env).toEqual({ ORCA_OMP_PREFILL: 'fix the omp regression' })
|
||||
expect(plan?.expectedProcess).toBe('omp')
|
||||
expect(plan?.launchCommand).toBe(
|
||||
`omp; command test -n "$fish_pid" && set --erase -g ORCA_OMP_PREFILL; command test -z "$fish_pid" && unset ORCA_OMP_PREFILL; true`
|
||||
`omp --config "$ORCA_OMP_FRESH_CONFIG"; command test -n "$fish_pid" && set --erase -g ORCA_OMP_PREFILL; command test -z "$fish_pid" && unset ORCA_OMP_PREFILL; true`
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { withFreshOmpLaunch } from './omp-fresh-launch'
|
||||
import { isShellProcess } from './agent-detection'
|
||||
import type { SleepingAgentLaunchConfig } from './agent-session-resume'
|
||||
import {
|
||||
@@ -70,6 +71,10 @@ export function buildAgentStartupPlan(args: {
|
||||
if (!baseCommand.ok) {
|
||||
return null
|
||||
}
|
||||
const launchCommand =
|
||||
agent === 'omp' && !args.isRemote
|
||||
? withFreshOmpLaunch(baseCommand.command, shell)
|
||||
: baseCommand.command
|
||||
const launchConfig = buildSleepingAgentLaunchConfig({
|
||||
...args,
|
||||
// Why: picker flags are a one-time launch choice; a resumed provider
|
||||
@@ -83,7 +88,7 @@ export function buildAgentStartupPlan(args: {
|
||||
}
|
||||
return {
|
||||
agent,
|
||||
launchCommand: baseCommand.command,
|
||||
launchCommand,
|
||||
expectedProcess: config.expectedProcess,
|
||||
followupPrompt: null,
|
||||
launchConfig,
|
||||
@@ -98,7 +103,7 @@ export function buildAgentStartupPlan(args: {
|
||||
const promptSeparator = config.argvPromptSeparator ? ` ${config.argvPromptSeparator}` : ''
|
||||
return {
|
||||
agent,
|
||||
launchCommand: `${baseCommand.command}${promptSeparator} ${quotedPrompt}`,
|
||||
launchCommand: `${launchCommand}${promptSeparator} ${quotedPrompt}`,
|
||||
expectedProcess: config.expectedProcess,
|
||||
followupPrompt: null,
|
||||
launchConfig,
|
||||
@@ -111,7 +116,7 @@ export function buildAgentStartupPlan(args: {
|
||||
if (config.promptInjectionMode === 'flag-prompt') {
|
||||
return {
|
||||
agent,
|
||||
launchCommand: `${baseCommand.command} --prompt ${quotedPrompt}`,
|
||||
launchCommand: `${launchCommand} --prompt ${quotedPrompt}`,
|
||||
expectedProcess: config.expectedProcess,
|
||||
followupPrompt: null,
|
||||
launchConfig,
|
||||
@@ -149,7 +154,7 @@ export function buildAgentStartupPlan(args: {
|
||||
if (config.promptInjectionMode === 'flag-prompt-interactive') {
|
||||
return {
|
||||
agent,
|
||||
launchCommand: `${baseCommand.command} --prompt-interactive ${quotedPrompt}`,
|
||||
launchCommand: `${launchCommand} --prompt-interactive ${quotedPrompt}`,
|
||||
expectedProcess: config.expectedProcess,
|
||||
followupPrompt: null,
|
||||
launchConfig,
|
||||
@@ -161,7 +166,7 @@ export function buildAgentStartupPlan(args: {
|
||||
if (config.promptInjectionMode === 'flag-interactive') {
|
||||
return {
|
||||
agent,
|
||||
launchCommand: `${baseCommand.command} -i ${quotedPrompt}`,
|
||||
launchCommand: `${launchCommand} -i ${quotedPrompt}`,
|
||||
expectedProcess: config.expectedProcess,
|
||||
followupPrompt: null,
|
||||
launchConfig,
|
||||
@@ -172,7 +177,7 @@ export function buildAgentStartupPlan(args: {
|
||||
|
||||
return {
|
||||
agent,
|
||||
launchCommand: baseCommand.command,
|
||||
launchCommand,
|
||||
expectedProcess: config.expectedProcess,
|
||||
followupPrompt: trimmedPrompt,
|
||||
launchConfig,
|
||||
@@ -222,6 +227,10 @@ export function buildAgentDraftLaunchPlan(args: {
|
||||
if (!baseCommand.ok) {
|
||||
return null
|
||||
}
|
||||
const launchCommand =
|
||||
agent === 'omp' && !args.isRemote
|
||||
? withFreshOmpLaunch(baseCommand.command, shell)
|
||||
: baseCommand.command
|
||||
const launchConfig = buildSleepingAgentLaunchConfig({
|
||||
...args,
|
||||
// Why: see the new-session path above — resume must not replay picker flags.
|
||||
@@ -232,7 +241,7 @@ export function buildAgentDraftLaunchPlan(args: {
|
||||
const quoted = quoteStartupArg(trimmed, shell)
|
||||
plan = {
|
||||
agent,
|
||||
launchCommand: `${baseCommand.command} ${config.draftPromptFlag} ${quoted}`,
|
||||
launchCommand: `${launchCommand} ${config.draftPromptFlag} ${quoted}`,
|
||||
expectedProcess: config.expectedProcess,
|
||||
launchConfig,
|
||||
...appliedSessionOptionProps(baseCommand.appliedSessionOptions),
|
||||
@@ -244,7 +253,7 @@ export function buildAgentDraftLaunchPlan(args: {
|
||||
const clearVar = clearEnvCommand(config.draftPromptEnvVar, shell)
|
||||
plan = {
|
||||
agent,
|
||||
launchCommand: `${baseCommand.command}${commandSeparator(shell)}${clearVar}`,
|
||||
launchCommand: `${launchCommand}${commandSeparator(shell)}${clearVar}`,
|
||||
expectedProcess: config.expectedProcess,
|
||||
launchConfig,
|
||||
...appliedSessionOptionProps(baseCommand.appliedSessionOptions),
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// Bun, with a read-only OMP source checkout as argv[2]. No model requests.
|
||||
import assert from 'node:assert/strict'
|
||||
import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join, resolve } from 'node:path'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import { OMP_FRESH_CONFIG_SOURCE } from '../../src/shared/omp-fresh-launch.ts'
|
||||
|
||||
assert.ok(process.argv[2], 'Pass a read-only OMP checkout path')
|
||||
const scratch = await mkdtemp(join(tmpdir(), 'orca-omp-fresh-proof-'))
|
||||
process.env.HOME = join(scratch, 'home')
|
||||
process.env.USERPROFILE = process.env.HOME
|
||||
process.env.XDG_CONFIG_HOME = join(scratch, 'xdg-config')
|
||||
process.env.XDG_DATA_HOME = join(scratch, 'xdg-data')
|
||||
process.env.XDG_STATE_HOME = join(scratch, 'xdg-state')
|
||||
process.env.OMP_CODING_AGENT_DIR = join(scratch, 'agent')
|
||||
delete process.env.PI_CONFIG_FILES
|
||||
const source = (path) =>
|
||||
pathToFileURL(join(resolve(process.argv[2]), 'packages/coding-agent/src', path)).href
|
||||
const managers = []
|
||||
try {
|
||||
await mkdir(process.env.HOME, { recursive: true })
|
||||
const { SessionManager } = await import(source('session/session-manager.ts'))
|
||||
const { Settings, resetSettingsForTest } = await import(source('config/settings.ts'))
|
||||
const { createSessionManager } = await import(source('main.ts'))
|
||||
const cwd = join(scratch, 'project')
|
||||
await mkdir(cwd)
|
||||
const previous = SessionManager.create(cwd)
|
||||
managers.push(previous)
|
||||
previous.appendMessage({ role: 'user', content: 'previous task', timestamp: Date.now() })
|
||||
await previous.ensureOnDisk()
|
||||
await previous.flush()
|
||||
const config = join(scratch, 'fresh.yml')
|
||||
const userConfig = join(scratch, 'user.yml')
|
||||
await writeFile(config, OMP_FRESH_CONFIG_SOURCE)
|
||||
await writeFile(userConfig, 'autoResume: true\n')
|
||||
const settings = await Settings.init({ cwd, configFiles: [userConfig] })
|
||||
const resumed = await createSessionManager({}, cwd, settings)
|
||||
managers.push(resumed)
|
||||
assert.equal(resumed.getSessionId(), previous.getSessionId())
|
||||
resetSettingsForTest()
|
||||
const freshSettings = await Settings.init({ cwd, configFiles: [userConfig, config] })
|
||||
assert.equal(freshSettings.get('autoResume'), false)
|
||||
assert.equal(settings.get('autoResume'), true)
|
||||
const defaultSelection = await createSessionManager({}, cwd, freshSettings)
|
||||
assert.equal(defaultSelection, undefined, 'SDK creates a fresh session after undefined selection')
|
||||
const fresh = SessionManager.create(cwd)
|
||||
managers.push(fresh)
|
||||
assert.notEqual(fresh.getSessionId(), previous.getSessionId())
|
||||
assert.equal(fresh.getSessionDir(), previous.getSessionDir())
|
||||
const explicit = await createSessionManager(
|
||||
{ resume: previous.getSessionFile() },
|
||||
cwd,
|
||||
freshSettings
|
||||
)
|
||||
managers.push(explicit)
|
||||
assert.equal(explicit.getSessionId(), previous.getSessionId())
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
platform: process.platform,
|
||||
autoResumeReproduced: true,
|
||||
freshSelected: true,
|
||||
storageDirectoryPreserved: true,
|
||||
explicitResumePreserved: true,
|
||||
modelCalls: 0,
|
||||
scope:
|
||||
'Actual OMP Settings overlays, persistent SessionManager and createSessionManager; no rendered UI'
|
||||
})
|
||||
)
|
||||
} finally {
|
||||
for (const manager of managers) {
|
||||
await manager?.close()
|
||||
}
|
||||
await rm(scratch, { recursive: true, force: true })
|
||||
}
|
||||
Reference in New Issue
Block a user