mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 16:03:20 +00:00
Merge remote-tracking branch 'origin/main' into nwparker/faster-CI
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import { chmodSync, mkdirSync, writeFileSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
function escapeWindowsBatchValue(value) {
|
||||
// Why: cmd.exe expands %NAME% even inside quotes, so literal path percent signs must be doubled.
|
||||
return value.replaceAll('%', '%%')
|
||||
}
|
||||
|
||||
export function prepareDevCliTerminalWrappers({
|
||||
repoRoot,
|
||||
userDataPath,
|
||||
electronExecutable,
|
||||
platform = process.platform
|
||||
}) {
|
||||
const binDir = path.join(repoRoot, 'out', 'bin')
|
||||
const userDataBinDir = path.join(userDataPath, 'cli', 'bin')
|
||||
const cliPath = path.join(repoRoot, 'out', 'cli', 'index.js')
|
||||
mkdirSync(binDir, { recursive: true })
|
||||
mkdirSync(userDataBinDir, { recursive: true })
|
||||
|
||||
if (platform === 'win32') {
|
||||
const wrapperContent = `@echo off\r\nset "ORCA_USER_DATA_PATH=${escapeWindowsBatchValue(userDataPath)}"\r\nset "ORCA_DEV_CLI_INVOCATION=1"\r\nset "ORCA_APP_EXECUTABLE=${escapeWindowsBatchValue(electronExecutable)}"\r\nset "ORCA_APP_EXECUTABLE_NEEDS_APP_ROOT=1"\r\nnode "${escapeWindowsBatchValue(cliPath)}" %*\r\n`
|
||||
for (const targetDir of [binDir, userDataBinDir]) {
|
||||
for (const commandName of ['orca-dev.cmd', 'orca.cmd']) {
|
||||
writeFileSync(path.join(targetDir, commandName), wrapperContent, 'utf8')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const wrapperContent = `#!/usr/bin/env bash\nexport ORCA_USER_DATA_PATH=${JSON.stringify(userDataPath)}\nexport ORCA_DEV_CLI_INVOCATION=1\nexport ORCA_APP_EXECUTABLE=${JSON.stringify(electronExecutable)}\nexport ORCA_APP_EXECUTABLE_NEEDS_APP_ROOT=1\nexec node ${JSON.stringify(cliPath)} "$@"\n`
|
||||
for (const targetDir of [binDir, userDataBinDir]) {
|
||||
for (const commandName of ['orca-dev', 'orca']) {
|
||||
const wrapperPath = path.join(targetDir, commandName)
|
||||
writeFileSync(wrapperPath, wrapperContent, 'utf8')
|
||||
chmodSync(wrapperPath, 0o755)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { binDir, userDataBinDir }
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { mkdtempSync, readFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import path from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { prepareDevCliTerminalWrappers } from './dev-cli-terminal-wrapper.mjs'
|
||||
|
||||
describe('dev CLI terminal wrappers', () => {
|
||||
it('writes profile-scoped Windows wrappers for worker terminals', () => {
|
||||
const root = mkdtempSync(path.join(tmpdir(), 'orca-dev-terminal-wrapper-'))
|
||||
const userDataPath = path.join(root, 'profile')
|
||||
prepareDevCliTerminalWrappers({
|
||||
repoRoot: root,
|
||||
userDataPath,
|
||||
electronExecutable: path.join(root, 'electron.exe'),
|
||||
platform: 'win32'
|
||||
})
|
||||
|
||||
const wrapper = readFileSync(path.join(userDataPath, 'cli', 'bin', 'orca-dev.cmd'), 'utf8')
|
||||
expect(wrapper).toContain(`set "ORCA_USER_DATA_PATH=${userDataPath}"`)
|
||||
expect(wrapper).toContain('set "ORCA_DEV_CLI_INVOCATION=1"')
|
||||
expect(wrapper).toContain(`node "${path.join(root, 'out', 'cli', 'index.js')}" %*`)
|
||||
expect(readFileSync(path.join(userDataPath, 'cli', 'bin', 'orca.cmd'), 'utf8')).toBe(wrapper)
|
||||
expect(readFileSync(path.join(root, 'out', 'bin', 'orca-dev.cmd'), 'utf8')).toBe(wrapper)
|
||||
expect(readFileSync(path.join(root, 'out', 'bin', 'orca.cmd'), 'utf8')).toBe(wrapper)
|
||||
})
|
||||
|
||||
it('escapes literal percent signs in every Windows batch path', () => {
|
||||
const root = path.join(mkdtempSync(path.join(tmpdir(), 'orca-dev-terminal-wrapper-')), '%repo%')
|
||||
const userDataPath = path.join(root, '%profile%')
|
||||
const electronExecutable = path.join(root, '%electron%', 'electron.exe')
|
||||
prepareDevCliTerminalWrappers({
|
||||
repoRoot: root,
|
||||
userDataPath,
|
||||
electronExecutable,
|
||||
platform: 'win32'
|
||||
})
|
||||
|
||||
const wrapper = readFileSync(path.join(userDataPath, 'cli', 'bin', 'orca-dev.cmd'), 'utf8')
|
||||
expect(wrapper).toContain(`set "ORCA_USER_DATA_PATH=${userDataPath.replaceAll('%', '%%')}"`)
|
||||
expect(wrapper).toContain(
|
||||
`set "ORCA_APP_EXECUTABLE=${electronExecutable.replaceAll('%', '%%')}"`
|
||||
)
|
||||
expect(wrapper).toContain(
|
||||
`node "${path.join(root, 'out', 'cli', 'index.js').replaceAll('%', '%%')}" %*`
|
||||
)
|
||||
expect(readFileSync(path.join(root, 'out', 'bin', 'orca-dev.cmd'), 'utf8')).toBe(wrapper)
|
||||
expect(readFileSync(path.join(root, 'out', 'bin', 'orca.cmd'), 'utf8')).toBe(wrapper)
|
||||
})
|
||||
|
||||
it('writes executable-style POSIX wrappers with the same profile identity', () => {
|
||||
const root = mkdtempSync(path.join(tmpdir(), 'orca-dev-terminal-wrapper-'))
|
||||
const userDataPath = path.join(root, 'profile')
|
||||
prepareDevCliTerminalWrappers({
|
||||
repoRoot: root,
|
||||
userDataPath,
|
||||
electronExecutable: path.join(root, 'electron'),
|
||||
platform: 'linux'
|
||||
})
|
||||
|
||||
const wrapper = readFileSync(path.join(userDataPath, 'cli', 'bin', 'orca-dev'), 'utf8')
|
||||
expect(wrapper).toContain(`export ORCA_USER_DATA_PATH=${JSON.stringify(userDataPath)}`)
|
||||
expect(wrapper).toContain('export ORCA_DEV_CLI_INVOCATION=1')
|
||||
expect(wrapper).toContain(
|
||||
`exec node ${JSON.stringify(path.join(root, 'out', 'cli', 'index.js'))}`
|
||||
)
|
||||
expect(readFileSync(path.join(userDataPath, 'cli', 'bin', 'orca'), 'utf8')).toBe(wrapper)
|
||||
expect(readFileSync(path.join(root, 'out', 'bin', 'orca-dev'), 'utf8')).toBe(wrapper)
|
||||
expect(readFileSync(path.join(root, 'out', 'bin', 'orca'), 'utf8')).toBe(wrapper)
|
||||
})
|
||||
})
|
||||
@@ -25,6 +25,7 @@ describe('orca-dev package bin', () => {
|
||||
`fs.writeFileSync(${JSON.stringify(outputPath)}, JSON.stringify({`,
|
||||
' argv: process.argv.slice(2),',
|
||||
' userDataPath: process.env.ORCA_USER_DATA_PATH,',
|
||||
' devCliInvocation: process.env.ORCA_DEV_CLI_INVOCATION,',
|
||||
' appExecutable: process.env.ORCA_APP_EXECUTABLE',
|
||||
'}));'
|
||||
].join('\n'),
|
||||
@@ -47,6 +48,7 @@ describe('orca-dev package bin', () => {
|
||||
expect(JSON.parse(readFileSync(outputPath, 'utf8'))).toEqual({
|
||||
argv: ['--help'],
|
||||
userDataPath: path.join(root, 'user-data'),
|
||||
devCliInvocation: '1',
|
||||
appExecutable: path.join(root, 'Electron')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import { accessSync, constants, existsSync, realpathSync, statSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { prepareDevCliTerminalWrappers } from './dev-cli-terminal-wrapper.mjs'
|
||||
|
||||
const scriptPath = realpathSync(import.meta.filename)
|
||||
const scriptDir = path.dirname(scriptPath)
|
||||
@@ -16,6 +17,8 @@ if (!existsSync(cliEntry)) {
|
||||
}
|
||||
|
||||
process.env.ORCA_USER_DATA_PATH = process.env.ORCA_DEV_USER_DATA_PATH ?? getDefaultDevUserDataPath()
|
||||
// Why: custom dev profiles do not necessarily contain "orca-dev" in their path; carry explicit provenance into the CLI.
|
||||
process.env.ORCA_DEV_CLI_INVOCATION = '1'
|
||||
|
||||
const electronExecutable = getElectronExecutable()
|
||||
if (!process.env.ORCA_APP_EXECUTABLE && isRunnableFile(electronExecutable)) {
|
||||
@@ -23,6 +26,13 @@ if (!process.env.ORCA_APP_EXECUTABLE && isRunnableFile(electronExecutable)) {
|
||||
process.env.ORCA_APP_EXECUTABLE_NEEDS_APP_ROOT = '1'
|
||||
}
|
||||
|
||||
// Why: headless `orca-dev serve` skips the Electron dev runner that normally installs terminal CLI shims.
|
||||
prepareDevCliTerminalWrappers({
|
||||
repoRoot,
|
||||
userDataPath: process.env.ORCA_USER_DATA_PATH,
|
||||
electronExecutable: process.env.ORCA_APP_EXECUTABLE ?? electronExecutable
|
||||
})
|
||||
|
||||
const result = spawnSync(process.execPath, [cliEntry, ...process.argv.slice(2)], {
|
||||
stdio: 'inherit',
|
||||
env: process.env
|
||||
|
||||
@@ -29,10 +29,14 @@ describe('orchestration skill guidance', () => {
|
||||
const skill = readSkill()
|
||||
const toolBoundary = getSection(skill, 'Tool Boundary')
|
||||
|
||||
expect(toolBoundary).toContain(
|
||||
'must create Orca runtime state with `orca orchestration task-create` and `orca orchestration dispatch --inject`'
|
||||
expect(toolBoundary).toContain('must create or bind a Run')
|
||||
expect(toolBoundary).toContain('create the Task with `orca orchestration task-create`')
|
||||
expect(toolBoundary).toContain('preferred `orca orchestration worker-start` composition')
|
||||
expect(toolBoundary).toContain('low-level `orca orchestration dispatch --inject` path')
|
||||
expect(toolBoundary).not.toContain('or `orca orchestration run`')
|
||||
expect(skill).toContain(
|
||||
'`coordinator-start`, `coordinator-stop`, `run`, and `run-stop` are retired scheduler commands'
|
||||
)
|
||||
expect(toolBoundary).toContain('or `orca orchestration run`')
|
||||
expect(toolBoundary).toContain(
|
||||
'Do not substitute non-Orca subagent tools, generic agent-spawn APIs, or chat-only parallel worker features'
|
||||
)
|
||||
@@ -47,6 +51,20 @@ describe('orchestration skill guidance', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('teaches the hard cutover without reviving a legacy executor', () => {
|
||||
const skill = readSkill()
|
||||
const migration = getSection(skill, 'Contract Migration')
|
||||
|
||||
expect(migration).toContain('hard cutover')
|
||||
expect(migration).toContain('effectsApplied')
|
||||
expect(migration).toContain('skills get orchestration --full')
|
||||
expect(migration).toContain('Do not retry the rejected command unchanged')
|
||||
expect(migration).toContain('no longer supervised')
|
||||
expect(migration).toContain('task-list --run run_legacy_local')
|
||||
expect(migration).toContain('Read-only inspection never consumes legacy mail')
|
||||
expect(migration).toContain('does not run a legacy scheduler, translate old writes, or drain')
|
||||
})
|
||||
|
||||
it('treats long-running worker waits as liveness checkpoints, not failures', () => {
|
||||
const skill = readSkill()
|
||||
|
||||
@@ -213,13 +231,13 @@ describe('orchestration skill guidance', () => {
|
||||
const messaging = getSection(skill, 'Messaging')
|
||||
const workerTerminals = getSection(skill, 'Worker Terminals')
|
||||
const agentFirstExample = workerTerminals.match(
|
||||
/```bash\norca worktree create --name <task-name> --agent codex --json\n[\s\S]*?```/
|
||||
/```bash\norca worktree create --name <task-name> --agent codex --setup run --json\n[\s\S]*?```/
|
||||
)?.[0]
|
||||
|
||||
expect(workerTerminals).toContain('For an allowed new worktree, use agent-first:')
|
||||
expect(workerTerminals).toContain('fallback shell + agent pair')
|
||||
expect(workerTerminals).toContain(
|
||||
'Repo setup or default-terminal settings may still add tabs or splits'
|
||||
'repo setup and default-terminal settings may add intentional tabs or splits'
|
||||
)
|
||||
expect(workerTerminals).toContain('without configured default tabs')
|
||||
expect(workerTerminals).toContain(
|
||||
@@ -229,12 +247,11 @@ describe('orchestration skill guidance', () => {
|
||||
expect(workerTerminals).not.toContain('ends with **one** agent tab')
|
||||
expect(agentFirstExample).toBeDefined()
|
||||
expect(agentFirstExample).not.toContain('orca terminal list')
|
||||
expect(agentFirstExample).toContain('agentTerminalHandle')
|
||||
expect(agentFirstExample).toContain('startupTerminal.handle')
|
||||
expect(messaging).toContain(
|
||||
'Use `startupTerminal.handle` from the create response when present'
|
||||
)
|
||||
expect(messaging).toContain('continue with the replacement only')
|
||||
expect(messaging).toContain('it does not remotely wake another terminal')
|
||||
expect(messaging).toContain('Prefer `agentTerminalHandle` from the create response')
|
||||
expect(messaging).toContain('Continue with the replacement handle only')
|
||||
expect(messaging).toContain('never writes to terminal input or remotely wakes another terminal')
|
||||
expect(messaging).toContain('Use `orchestration dispatch --inject` to deliver a tracked task')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { execFileSync, spawn } from 'node:child_process'
|
||||
import { createHash } from 'node:crypto'
|
||||
import {
|
||||
chmodSync,
|
||||
cpSync,
|
||||
existsSync,
|
||||
lstatSync,
|
||||
@@ -17,6 +16,7 @@ import {
|
||||
import net from 'node:net'
|
||||
import { createRequire } from 'node:module'
|
||||
import path from 'node:path'
|
||||
import { prepareDevCliTerminalWrappers } from './dev-cli-terminal-wrapper.mjs'
|
||||
|
||||
// Why: Electron-based hosts (e.g. Claude Code, VS Code) set
|
||||
// ELECTRON_RUN_AS_NODE=1 in their terminal environment. If this leaks into
|
||||
@@ -314,35 +314,12 @@ function getDevUserDataPath() {
|
||||
}
|
||||
|
||||
function prepareDevCliWrapper() {
|
||||
const binDir = path.join(repoRoot, 'out', 'bin')
|
||||
mkdirSync(binDir, { recursive: true })
|
||||
const userDataPath = getDevUserDataPath()
|
||||
const userDataBinDir = path.join(userDataPath, 'cli', 'bin')
|
||||
const cliPath = path.join(repoRoot, 'out', 'cli', 'index.js')
|
||||
const electronBin = getElectronExecutable()
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
writeFileSync(
|
||||
path.join(binDir, 'orca-dev.cmd'),
|
||||
`@echo off\r\nset "ORCA_USER_DATA_PATH=${userDataPath}"\r\nset "ORCA_APP_EXECUTABLE=${electronBin}"\r\nset "ORCA_APP_EXECUTABLE_NEEDS_APP_ROOT=1"\r\nnode "${cliPath}" %*\r\n`,
|
||||
'utf8'
|
||||
)
|
||||
} else {
|
||||
const wrapperContent = `#!/usr/bin/env bash\nexport ORCA_USER_DATA_PATH=${JSON.stringify(userDataPath)}\nexport ORCA_APP_EXECUTABLE=${JSON.stringify(electronBin)}\nexport ORCA_APP_EXECUTABLE_NEEDS_APP_ROOT=1\nexec node ${JSON.stringify(cliPath)} "$@"\n`
|
||||
const wrapperPath = path.join(binDir, 'orca-dev')
|
||||
writeFileSync(wrapperPath, wrapperContent, 'utf8')
|
||||
chmodSync(wrapperPath, 0o755)
|
||||
|
||||
mkdirSync(userDataBinDir, { recursive: true })
|
||||
for (const commandName of ['orca-dev', 'orca']) {
|
||||
const userDataWrapperPath = path.join(userDataBinDir, commandName)
|
||||
// Why: dev Orca terminals prepend this directory to PATH; refreshing the
|
||||
// `orca` alias prevents stale global/userData wrappers from hijacking
|
||||
// Orca-owned commands such as `orca claude-teams`.
|
||||
writeFileSync(userDataWrapperPath, wrapperContent, 'utf8')
|
||||
chmodSync(userDataWrapperPath, 0o755)
|
||||
}
|
||||
}
|
||||
const { binDir } = prepareDevCliTerminalWrappers({
|
||||
repoRoot,
|
||||
userDataPath,
|
||||
electronExecutable: getElectronExecutable()
|
||||
})
|
||||
|
||||
process.env.PATH = `${binDir}${path.delimiter}${process.env.PATH ?? ''}`
|
||||
console.log(`[orca-dev] Prepared wrapper in ${binDir}`)
|
||||
|
||||
Reference in New Issue
Block a user