mirror of
https://github.com/stablyai/orca.git
synced 2026-10-08 00:02:38 +00:00
fix(opencode): keep overlay manifest cleanup inside owned directories (#24763)
* fix: wait for OpenCode worker composer before first dispatch Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors. Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> * feat(opencode): probe execution-host CLI capabilities * fix(opencode): select plugin default for execution host loader * fix(opencode): limit prompt prefill capability to verified release * feat(opencode): probe launch capabilities on the execution host * fix(opencode): select plugin loader for the launched host binary * fix(opencode): match WSL probe cwd and declared guest environment * fix(opencode): preserve launch environment deletion boundaries * wip(opencode): authorize native startup prompt intent at execution owner * fix(opencode): atomically replace status plugin entrypoints * fix(opencode): retain plugin permissions across restrictive umasks * test(opencode): resolve permission fixture from primary cwd * feat(opencode): install startup prompt plugin independently of status hooks * fix(opencode): wait for admitted startup intent and preserve failed-launch briefs * fix(opencode): confine overlay manifest cleanup to owned directories Co-authored-by: Adnan Khan <adnank11427@gmail.com> * fix: wait for OpenCode worker composer before first dispatch Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors. Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> * feat(opencode): probe execution-host CLI capabilities * fix(opencode): select plugin default for execution host loader * fix(opencode): limit prompt prefill capability to verified release * feat(opencode): probe launch capabilities on the execution host * fix(opencode): select plugin loader for the launched host binary * fix(opencode): match WSL probe cwd and declared guest environment * fix(opencode): preserve launch environment deletion boundaries * wip(opencode): authorize native startup prompt intent at execution owner * fix(opencode): atomically replace status plugin entrypoints * fix(opencode): retain plugin permissions across restrictive umasks * test(opencode): resolve permission fixture from primary cwd * feat(opencode): install startup prompt plugin independently of status hooks * fix(opencode): wait for admitted startup intent and preserve failed-launch briefs * fix(opencode): unsubscribe hook settings during async host shutdown * STRICT launch CI contract correction * CAPS launch CI contract correction * INTENT launch CI contract correction * test: initialize Claude prompt state in output retention fixture * Wait for OpenCode location hydration in intent startup * Bind OpenCode startup readiness to the current location in intent startup --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com> Co-authored-by: Adnan Khan <adnank11427@gmail.com> Co-authored-by: Orca startup hydration review <agents@stably.ai>
This commit is contained in:
co-authored by
Brennan Benson
Adnan Khan
Ahmed Nagy
Orca startup hydration review
parent
d9173ffbdb
commit
8c617301f7
@@ -0,0 +1,98 @@
|
||||
import {
|
||||
mkdirSync,
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
readdirSync,
|
||||
rmSync,
|
||||
symlinkSync,
|
||||
writeFileSync
|
||||
} from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { basename, dirname, join } from 'node:path'
|
||||
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
|
||||
import { setAppEnvironment } from '../../shared/app-environment'
|
||||
import { safeRemoveTree } from '../pty/overlay-mirror'
|
||||
import { OpenCodeHookService } from './hook-service'
|
||||
import { OPENCODE_OVERLAY_MANIFEST_FILE } from './opencode-overlay-manifest'
|
||||
|
||||
let root: string
|
||||
let source: string
|
||||
let service: OpenCodeHookService
|
||||
|
||||
beforeEach(() => {
|
||||
root = mkdtempSync(join(tmpdir(), 'orca-overlay-confinement-'))
|
||||
source = join(root, 'source')
|
||||
mkdirSync(join(source, 'plugins'), { recursive: true })
|
||||
writeFileSync(join(source, 'plugins', 'user.js'), 'export default {}')
|
||||
vi.stubEnv('XDG_CONFIG_HOME', join(root, 'xdg'))
|
||||
setAppEnvironment({
|
||||
getPath: () => join(root, 'profile'),
|
||||
getAppPath: () => process.cwd(),
|
||||
getVersion: () => '0.0.0-test',
|
||||
isPackaged: () => false,
|
||||
onWillQuit: () => {},
|
||||
exit: () => {},
|
||||
getAppMetrics: () => []
|
||||
})
|
||||
service = new OpenCodeHookService({
|
||||
pluginFileName: 'orca-test.js',
|
||||
legacyHooksDir: 'legacy-test',
|
||||
overlayDir: 'overlay-test',
|
||||
pluginSource: () => 'export default {}'
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs()
|
||||
rmSync(root, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
it.each(['overlay-root', 'source-overlay', 'plugins'] as const)(
|
||||
'preserves outside files when the owned %s is replaced by a directory link',
|
||||
(boundary) => {
|
||||
const overlay = service.buildPtyEnv('pane-1', source).OPENCODE_CONFIG_DIR
|
||||
if (!overlay) {
|
||||
throw new Error('Expected an isolated overlay')
|
||||
}
|
||||
const outside = join(root, 'outside')
|
||||
const externalOverlay = boundary === 'overlay-root' ? join(outside, basename(overlay)) : outside
|
||||
mkdirSync(join(externalOverlay, 'plugins'), { recursive: true })
|
||||
const sentinel = join(externalOverlay, 'plugins', 'keep.js')
|
||||
writeFileSync(sentinel, 'export const keep = true')
|
||||
writeFileSync(
|
||||
join(externalOverlay, OPENCODE_OVERLAY_MANIFEST_FILE),
|
||||
JSON.stringify({ topLevelEntries: [], pluginEntries: ['keep.js'] })
|
||||
)
|
||||
const replaced =
|
||||
boundary === 'overlay-root'
|
||||
? dirname(overlay)
|
||||
: boundary === 'source-overlay'
|
||||
? overlay
|
||||
: join(overlay, 'plugins')
|
||||
const target = boundary === 'plugins' ? join(outside, 'plugins') : outside
|
||||
if (boundary === 'plugins') {
|
||||
writeFileSync(
|
||||
join(overlay, OPENCODE_OVERLAY_MANIFEST_FILE),
|
||||
JSON.stringify({ topLevelEntries: [], pluginEntries: ['keep.js'] })
|
||||
)
|
||||
}
|
||||
safeRemoveTree(replaced)
|
||||
symlinkSync(target, replaced, process.platform === 'win32' ? 'junction' : 'dir')
|
||||
const before = readdirSync(outside, { recursive: true }).toSorted()
|
||||
|
||||
const result = service.buildPtyEnv('pane-2', source)
|
||||
expect(readFileSync(sentinel, 'utf8')).toBe('export const keep = true')
|
||||
expect(readdirSync(outside, { recursive: true }).toSorted()).toEqual(before)
|
||||
expect(result).toEqual({ OPENCODE_CONFIG_DIR: source })
|
||||
}
|
||||
)
|
||||
|
||||
it('still removes a stale mirrored entry from a real owned overlay', () => {
|
||||
const overlay = service.buildPtyEnv('pane-1', source).OPENCODE_CONFIG_DIR
|
||||
if (!overlay) {
|
||||
throw new Error('Expected an isolated overlay')
|
||||
}
|
||||
rmSync(join(source, 'plugins', 'user.js'))
|
||||
expect(service.buildPtyEnv('pane-2', source)).toEqual({ OPENCODE_CONFIG_DIR: overlay })
|
||||
expect(readdirSync(join(overlay, 'plugins'))).toEqual(['orca-test.js'])
|
||||
})
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
writeFileSync
|
||||
} from 'node:fs'
|
||||
import { createHash } from 'node:crypto'
|
||||
import { isSafeDescendCandidate, mirrorEntry, safeRemoveTree } from '../pty/overlay-mirror'
|
||||
import { isSafeDescendCandidate, mirrorEntry, safeRemoveOverlay } from '../pty/overlay-mirror'
|
||||
import {
|
||||
getOpenCode2PluginSource,
|
||||
getOpenCodeFamilyPluginSource,
|
||||
@@ -141,7 +141,13 @@ export class OpenCodeHookService {
|
||||
}
|
||||
const overlayDir = this.getSourceOverlayDir(existingConfigDir)
|
||||
try {
|
||||
mkdirSync(overlayDir, { recursive: true })
|
||||
// Owned directories must stay real; a replaced parent redirects both cleanup and writes.
|
||||
for (const directory of [this.getOverlayRoot(), overlayDir, join(overlayDir, 'plugins')]) {
|
||||
mkdirSync(directory, { recursive: true })
|
||||
if (!isSafeDescendCandidate(lstatSync(directory))) {
|
||||
return { OPENCODE_CONFIG_DIR: existingConfigDir }
|
||||
}
|
||||
}
|
||||
if (existsSync(existingConfigDir)) {
|
||||
this.mirrorUserConfig(existingConfigDir, overlayDir)
|
||||
}
|
||||
@@ -230,7 +236,7 @@ export class OpenCodeHookService {
|
||||
|
||||
private clearManifestEntries(overlayDir: string, manifest: OpenCodeOverlayManifest): void {
|
||||
for (const entryName of manifest.topLevelEntries) {
|
||||
safeRemoveTree(join(overlayDir, entryName))
|
||||
safeRemoveOverlay(join(overlayDir, entryName), overlayDir)
|
||||
}
|
||||
|
||||
const overlayPluginsDir = join(overlayDir, 'plugins')
|
||||
@@ -238,7 +244,7 @@ export class OpenCodeHookService {
|
||||
if (entryName === this.pluginFileName) {
|
||||
continue
|
||||
}
|
||||
safeRemoveTree(join(overlayPluginsDir, entryName))
|
||||
safeRemoveOverlay(join(overlayPluginsDir, entryName), overlayPluginsDir)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { expect, it } from 'vitest'
|
||||
import {
|
||||
readOpenCodeOverlayManifest,
|
||||
OPENCODE_OVERLAY_MANIFEST_FILE
|
||||
} from './opencode-overlay-manifest'
|
||||
|
||||
it('accepts only string manifest entries and tolerates invalid persisted shapes', () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'orca-overlay-manifest-'))
|
||||
try {
|
||||
const path = join(root, OPENCODE_OVERLAY_MANIFEST_FILE)
|
||||
for (const text of ['null', '1', '{']) {
|
||||
writeFileSync(path, text)
|
||||
expect(readOpenCodeOverlayManifest(root)).toEqual({ topLevelEntries: [], pluginEntries: [] })
|
||||
}
|
||||
writeFileSync(
|
||||
path,
|
||||
JSON.stringify({
|
||||
topLevelEntries: ['valid', 1, null],
|
||||
pluginEntries: [{ bad: true }, 'plugin.js']
|
||||
})
|
||||
)
|
||||
expect(readOpenCodeOverlayManifest(root)).toEqual({
|
||||
topLevelEntries: ['valid'],
|
||||
pluginEntries: ['plugin.js']
|
||||
})
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user