mirror of
https://github.com/stablyai/orca.git
synced 2026-11-01 09:37:33 +00:00
* fix(native-chat): identify the owner when resume create is refused * test(native-chat): consolidate transcript refusal cases * test(native-chat): keep ownership coverage at the launch boundary * test(native-chat): update adoption fixture for early ownership admission * test(native-chat): align resume admission assertions with host ownership * test(native-chat): keep RPC coverage focused on ownership proof * test(native-chat): assert blank request presence before optional source * fix(native-chat): admit the actual CLI source during chat creation * fix(native-chat): satisfy source admission lint checks * test(native-chat): provide the waiting hook prompt * test(native-chat): isolate source admission fixtures from host homes * test(native-chat): canonicalize adoption fixture directories * test(native-chat): preserve early ownership admission in source stack * fix(native-chat): preserve resume refusal behavior and honest transcript feedback * fix(native-chat): retain import read evidence in typed result * fix(native-chat): preserve admitted source and prepared execution home * fix(chat): retain source evidence for dispatched adoption * fix(chat): keep source capture within module limits * test(chat): allow host evidence in create routing * fix(native-chat): admit an unsaved transcript under a linked account folder A missing transcript stayed unresolved while its account root resolved through symlinks, so containment failed and a not-yet-saved conversation showed a load error instead of the unsaved notice. * refactor(native-chat): move the newest-item import window into its own module Keeps journal-legacy-import.ts under the line limit after the source identity observer landed on top of the streaming import. * fix(native-chat): restore the refusal error import lost in the main merge * fix(native-chat): admit Pi transcripts against the root Session History walks A PI_CODING_AGENT_DIR that already ends in sessions, or any custom folder, made admission expect <dir>/sessions while the lookup searched <dir>, so a found conversation was refused. * refactor(native-chat): admit and look up CLI transcripts in one store per account Each agent's import described where an account keeps its conversations twice, once for the path check and once for the lookup, and the two could disagree (the linked-folder and Pi custom-root refusals). The import now names one conversation store per account; the create path resolves it once and both the hook-path check and the fallback lookup judge that same store. OpenCode's store is its exact database.
96 lines
3.7 KiB
TypeScript
96 lines
3.7 KiB
TypeScript
import type { StructuredAgentId } from './agent-session-provider-handle'
|
|
import type { AgentSessionMutationEnvelope } from './agent-session-wire'
|
|
import type { z } from 'zod'
|
|
import type { SendBody } from './rpc-contract/structured-agent-session-message-params'
|
|
import {
|
|
createStructuredAgentSessionOperationId,
|
|
structuredAgentSessionCreateFingerprint
|
|
} from './structured-agent-session-mutation'
|
|
|
|
/**
|
|
* The conversation a create adopts instead of starting a fresh one.
|
|
*
|
|
* Deliberately carries an identity and nothing else. The transcript file and the account home it
|
|
* lives under are derived by the executing host, never sent: `agentSession.create` is reachable by
|
|
* paired mobile clients, and a client-supplied path would let one choose which file the host reads
|
|
* into a journal and which credential directory the provider child launches against.
|
|
*/
|
|
export type StructuredAgentSessionResumeSource = {
|
|
/** claude: the session id. codex: the thread id. */
|
|
providerSessionId: string
|
|
/** Host-owned pane evidence; send only with the CLI-source create capability. */
|
|
cliSource?: { workspaceId: string; paneKey: string }
|
|
}
|
|
|
|
export type StructuredAgentSessionCreateParams = {
|
|
envelope: AgentSessionMutationEnvelope
|
|
worktree: string
|
|
agent: StructuredAgentId
|
|
resumeFrom?: StructuredAgentSessionResumeSource
|
|
/** Sent only to a host advertising `AGENT_SESSION_CREATE_TAB_ID_RUNTIME_CAPABILITY`. */
|
|
tabId?: string
|
|
/** Sent only to a host advertising `AGENT_SESSION_CREATE_MESSAGE_RUNTIME_CAPABILITY`. */
|
|
firstMessage?: StructuredAgentSessionFirstMessage
|
|
options?: Readonly<Record<string, string>>
|
|
}
|
|
|
|
export type StructuredAgentSessionFirstMessage = {
|
|
clientMessageId: string
|
|
body: z.infer<typeof SendBody>
|
|
}
|
|
|
|
/** Provider-prefixed so a session id names its lane on sight, and underscore-only
|
|
* so the id stays a single token everywhere it is embedded (tab ids, log keys). */
|
|
export function createStructuredAgentSessionId(
|
|
agent: StructuredAgentId,
|
|
randomUuid: () => string
|
|
): string {
|
|
return `${agent}_${randomUuid().replaceAll('-', '_')}`
|
|
}
|
|
|
|
/** Whether a caller-minted id keeps the shape `createStructuredAgentSessionId` gives every id:
|
|
* named for its agent, then one token. The token alone is checked, so a hyphenated agent name
|
|
* is not refused at the wire. */
|
|
export function isStructuredAgentSessionIdFor(agent: string, sessionId: string): boolean {
|
|
const prefix = `${agent}_`
|
|
return sessionId.startsWith(prefix) && /^[A-Za-z0-9_]+$/.test(sessionId.slice(prefix.length))
|
|
}
|
|
|
|
/**
|
|
* The durable `agentSession.create` envelope every client replays on an ambiguous
|
|
* transport failure. The fingerprint must be computed over the same fields the host
|
|
* recomputes, so both clients build it here rather than each assembling their own.
|
|
*/
|
|
export function structuredAgentSessionCreateParams(args: {
|
|
sessionId: string
|
|
worktree: string
|
|
agent: StructuredAgentId
|
|
resumeFrom?: StructuredAgentSessionResumeSource
|
|
tabId?: string
|
|
firstMessage?: StructuredAgentSessionFirstMessage
|
|
options?: Readonly<Record<string, string>>
|
|
randomUuid: () => string
|
|
now?: number
|
|
}): StructuredAgentSessionCreateParams {
|
|
const fields = {
|
|
worktree: args.worktree,
|
|
agent: args.agent,
|
|
...(args.resumeFrom ? { resumeFrom: args.resumeFrom } : {}),
|
|
...(args.tabId ? { tabId: args.tabId } : {}),
|
|
...(args.firstMessage ? { firstMessage: args.firstMessage } : {}),
|
|
...(args.options ? { options: args.options } : {})
|
|
}
|
|
return {
|
|
envelope: {
|
|
sessionId: args.sessionId,
|
|
clientOperationId: createStructuredAgentSessionOperationId(args.randomUuid, args.now),
|
|
expectedRuntimeFence: null,
|
|
payloadFingerprint: structuredAgentSessionCreateFingerprint({
|
|
sessionId: args.sessionId,
|
|
...fields
|
|
})
|
|
},
|
|
...fields
|
|
}
|
|
}
|