mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
fix(omp): preserve child status ownership provenance
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
# OMP runtime session provenance
|
||||
|
||||
The status extension uses `ctx.agentKind === 'sub'`, when provided by OMP, to
|
||||
reject child callbacks before they can claim a pane. The value must describe the
|
||||
current runtime session, not the ancestry of its transcript. A child transcript
|
||||
resumed directly as the pane's main session must still report status.
|
||||
OMP computes whether a runtime session is a task child, but the released
|
||||
`ExtensionContext` does not expose that value. The status extension therefore uses
|
||||
the session manager's parent header and nested task transcript path only when a
|
||||
root owner is already known. A nested transcript with no known owner remains
|
||||
eligible because it may have been resumed directly as the pane's main session.
|
||||
|
||||
This optional extension-context field requires an OMP runtime change. The reviewed
|
||||
OMP source at e7546987ca526eac8f605fac19ef9805b8f01898 already computes `agentKind`
|
||||
in its SDK, but does not expose it through ExtensionRunner.createContext. A local
|
||||
runtime proposal forwards that value to extension callbacks. It is not released
|
||||
or installed by this Orca change.
|
||||
The remaining child-first case is inherently ambiguous to Orca: task children and
|
||||
resumed child transcripts have the same public session-manager shape. A complete
|
||||
child-first fence requires OMP to expose its computed `agentKind` through
|
||||
`ExtensionRunner.createContext`; until then the conservative fallback avoids
|
||||
silencing valid resumed sessions.
|
||||
|
||||
Older runtimes retain the manager-identity guard. That guard assumes the main
|
||||
session reaches Orca's callback before any child. An earlier user extension can
|
||||
|
||||
@@ -2,11 +2,27 @@
|
||||
export function getOmpSessionOwnerHandlerSourceLines(): string[] {
|
||||
return [
|
||||
' // SessionManager survives reload/new/resume; task children own a different instance.',
|
||||
' function sessionProvenance(ctx): { manager: unknown; id?: string; file?: string; parent?: string } | undefined {',
|
||||
' const manager = ctx?.sessionManager',
|
||||
" if (!manager || typeof manager !== 'object') return undefined",
|
||||
' const id = typeof manager.getSessionId === "function" ? manager.getSessionId() : undefined',
|
||||
' const file = typeof manager.getSessionFile === "function" ? manager.getSessionFile() : undefined',
|
||||
' const header = typeof manager.getHeader === "function" ? manager.getHeader() : undefined',
|
||||
' return { manager, id, file, parent: typeof header?.parentSession === "string" ? header.parentSession : undefined }',
|
||||
' }',
|
||||
'',
|
||||
' function isNestedTaskTranscript(parentFile, candidateFile): boolean {',
|
||||
' if (typeof parentFile !== "string" || typeof candidateFile !== "string") return false',
|
||||
' const root = parentFile.endsWith(".jsonl") ? parentFile.slice(0, -6) : parentFile',
|
||||
' return candidateFile.startsWith(`${root}/`) && candidateFile !== parentFile',
|
||||
' }',
|
||||
'',
|
||||
' function ownsSessionStatus(ctx): boolean {',
|
||||
' if (!isOmpRuntime()) return true',
|
||||
' // Newer OMP builds may expose this computed runtime provenance directly.',
|
||||
' if (ctx?.agentKind === "sub") return false',
|
||||
' const manager = ctx?.sessionManager',
|
||||
" if (!manager || typeof manager !== 'object') return true",
|
||||
' const current = sessionProvenance(ctx)',
|
||||
' if (!current) return true',
|
||||
' // Keep ownership through module reload and shutdown while child sessions drain.',
|
||||
" const key = Symbol.for('orca.omp.status-session-owners')",
|
||||
' let owners = Reflect.get(globalThis, key)',
|
||||
@@ -16,8 +32,13 @@ export function getOmpSessionOwnerHandlerSourceLines(): string[] {
|
||||
' }',
|
||||
' const pane = JSON.stringify([process.env.ORCA_PANE_KEY, process.env.ORCA_AGENT_LAUNCH_TOKEN])',
|
||||
' const owner = owners.get(pane)',
|
||||
' if (owner) return owner === manager',
|
||||
' owners.set(pane, manager)',
|
||||
' if (owner) {',
|
||||
' if (owner.manager === current.manager) return true',
|
||||
' if (current.parent === owner.file || current.parent === owner.id) return false',
|
||||
' if (isNestedTaskTranscript(owner.file, current.file)) return false',
|
||||
' return false',
|
||||
' }',
|
||||
' owners.set(pane, current)',
|
||||
' return true',
|
||||
' }',
|
||||
'',
|
||||
|
||||
@@ -89,6 +89,32 @@ describe('OMP session status ownership', () => {
|
||||
await settle()
|
||||
expect(JSON.parse(harness.fetchMock.mock.calls[0][1].body).payload.session_id).toBe('separate')
|
||||
})
|
||||
|
||||
it('uses OMP parent metadata and nested task paths when the root already owns the pane', async () => {
|
||||
const harness = createAgentStatusExtensionHarness({ kind: 'omp' })
|
||||
const rootFile = '/sessions/root.jsonl'
|
||||
const root = {
|
||||
sessionManager: {
|
||||
getSessionId: () => 'root',
|
||||
getSessionFile: () => rootFile,
|
||||
getHeader: () => ({ parentSession: undefined })
|
||||
}
|
||||
}
|
||||
await harness.callHook('session_start', {}, root)
|
||||
await settle()
|
||||
harness.fetchMock.mockClear()
|
||||
harness.reload()
|
||||
const child = {
|
||||
sessionManager: {
|
||||
getSessionId: () => 'child',
|
||||
getSessionFile: () => '/sessions/root/child.jsonl',
|
||||
getHeader: () => ({ parentSession: rootFile })
|
||||
}
|
||||
}
|
||||
await harness.callHook('agent_start', {}, child)
|
||||
await settle()
|
||||
expect(harness.fetchMock).not.toHaveBeenCalled()
|
||||
})
|
||||
it('keeps reporting for legacy callbacks without a session manager', async () => {
|
||||
const harness = createAgentStatusExtensionHarness({ kind: 'omp' })
|
||||
await harness.callHook('agent_start')
|
||||
@@ -134,7 +160,6 @@ describe('OMP runtime session provenance', () => {
|
||||
it('allows a former child transcript resumed as the runtime main session', async () => {
|
||||
const harness = createAgentStatusExtensionHarness({ kind: 'omp' })
|
||||
const root = {
|
||||
agentKind: 'main',
|
||||
hasUI: false,
|
||||
sessionManager: {
|
||||
getSessionId: () => 'resumed-child',
|
||||
|
||||
Reference in New Issue
Block a user