mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
* Add Qoder session history and search with real CLI coverage * Allow the real Qoder marker file to end with a newline * Keep Qoder tool output out of history previews and search * Keep Qoder search pages readable by older clients * Verify persisted Qoder history after a real generated and resumed task * Negotiate Qoder filters before searching an older execution host * Combine search client imports for the CI plugin gate * Keep the relay search oracle aligned with legacy agent filtering * Register supervised Qoder China and Qwen lifecycle integration * Cover Qoder China mobile assets and mixed-host resume gates * Verify Qoder provider tags against the older released wire parser * Verify China and Qwen keep independent Windows hook scripts * Verify Qoder registrations against the installed older Windows release * test(qoder): align search capability contracts and pin old-host fencing * fix(qoder): rank exact picker identities and command aliases first * test(qoder): preserve the regional CLI shared icon expectation Keep the full bundled-asset and no-remote-image checks, with an explicit shared-logo basename for Qoder China. The map also works with older catalog type unions. * fix(qoder): align China catalog entry with fallback order --------- Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
150 lines
5.3 KiB
TypeScript
150 lines
5.3 KiB
TypeScript
import { realpathSync } from 'node:fs'
|
|
import { homedir } from 'node:os'
|
|
import { resolve } from 'node:path'
|
|
import { markQoderWorkspaceTrusted } from './qoder/workspace-trust'
|
|
import {
|
|
type AgentTrustPreset,
|
|
markAntigravityWorkspaceTrusted,
|
|
markCodexProjectTrusted,
|
|
markCopilotFolderTrusted,
|
|
markCursorWorkspaceTrusted
|
|
} from './agent-trust-presets'
|
|
import { awaitAgentTrustWriteWithinDeadline } from './agent-trust-write-deadline'
|
|
import {
|
|
type ClaudeTrustConfigTarget,
|
|
grantClaudeWorkspaceTrust
|
|
} from './claude/claude-folder-trust-file'
|
|
import { isTooBroadToPreTrust } from '../shared/home-or-filesystem-root'
|
|
|
|
/**
|
|
* What the host that runs the agent knows about where that agent reads trust. Relay-safe:
|
|
* main describes this machine or a WSL guest, the SSH relay describes its own host.
|
|
*/
|
|
export type WorkspaceTrustHost = {
|
|
/** Homes the agent may read trust under; with none known, an agent that inherits trust writes nothing. */
|
|
homes: readonly (string | null | undefined)[]
|
|
/** The home the launched agent resolves `~` to, where the per-user trust files live. */
|
|
agentHome: string
|
|
/** The config Claude reads on this host, or null when this host cannot tell. */
|
|
claudeConfig: () => ClaudeTrustConfigTarget | null
|
|
/** Every config.toml the launched Codex may read, in the hook installer's lock order. */
|
|
codexConfigFiles: () => readonly string[]
|
|
deadlineMs: number
|
|
}
|
|
|
|
/** The home an agent launched with `launchEnv` on this host resolves `~` to. */
|
|
export function launchedAgentHome(
|
|
launchEnv: Record<string, string | undefined> | undefined
|
|
): string {
|
|
return (process.platform === 'win32' ? launchEnv?.USERPROFILE : launchEnv?.HOME) || homedir()
|
|
}
|
|
|
|
/**
|
|
* Whether trust on a home or a disk root would also trust the folders below it, per each agent's
|
|
* own lookup; such trust is never pre-written. Claude walks up parent folders (to the repo root,
|
|
* else the disk root); Copilot and Qoder accept any trusted ancestor. Codex matches its start
|
|
* folder or that folder's repo root, Antigravity the exact folder, and Cursor never inherits from
|
|
* a home, a folder above one or a shallow path.
|
|
*/
|
|
export const AGENT_TRUST_INHERITS_FROM_A_HOME: Record<AgentTrustPreset, boolean> = {
|
|
claude: true,
|
|
codex: false,
|
|
cursor: false,
|
|
copilot: true,
|
|
qoder: true,
|
|
'qoder-cn': true,
|
|
antigravity: false
|
|
}
|
|
|
|
/**
|
|
* Agents whose lookup keys on the folder they start in, so Orca trusts that folder, not the
|
|
* workspace root, even outside any workspace. Codex checks its start folder, then that folder's
|
|
* repo root; a non-git workspace root above the start folder is neither.
|
|
*/
|
|
export const AGENT_TRUST_KEYED_BY_START_FOLDER: Record<AgentTrustPreset, boolean> = {
|
|
claude: false,
|
|
codex: true,
|
|
cursor: false,
|
|
copilot: false,
|
|
qoder: false,
|
|
'qoder-cn': false,
|
|
antigravity: false
|
|
}
|
|
|
|
// Why resolve() too: Claude stores it, and it collapses `..` even where realpath fails.
|
|
function withResolvedForm(path: string): string[] {
|
|
try {
|
|
return [path, resolve(path), realpathSync.native(path)]
|
|
} catch {
|
|
return [path, resolve(path)]
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Whether trust stored for `storedPath` would cover a home: it is a root, a home or a folder
|
|
* above one. Both sides are compared given and resolved, since the writers store the realpath.
|
|
*/
|
|
function wouldTrustAHome(storedPath: string, homes: readonly string[]): boolean {
|
|
const homeForms = homes.flatMap(withResolvedForm)
|
|
return withResolvedForm(storedPath).some((form) => isTooBroadToPreTrust(form, homeForms))
|
|
}
|
|
|
|
async function writePreset(
|
|
preset: AgentTrustPreset,
|
|
storedPath: string,
|
|
host: WorkspaceTrustHost
|
|
): Promise<void> {
|
|
switch (preset) {
|
|
case 'claude': {
|
|
const target = host.claudeConfig()
|
|
if (target) {
|
|
await grantClaudeWorkspaceTrust(target, storedPath)
|
|
}
|
|
return
|
|
}
|
|
case 'codex':
|
|
return markCodexProjectTrusted(storedPath, host.codexConfigFiles())
|
|
case 'cursor':
|
|
return markCursorWorkspaceTrusted(storedPath, host.agentHome)
|
|
case 'copilot':
|
|
return markCopilotFolderTrusted(storedPath, host.agentHome)
|
|
case 'qoder-cn':
|
|
return markQoderWorkspaceTrusted(storedPath, host.agentHome, '.qoder-cn')
|
|
case 'qoder':
|
|
return markQoderWorkspaceTrusted(storedPath, host.agentHome)
|
|
case 'antigravity':
|
|
return markAntigravityWorkspaceTrusted(storedPath, host.agentHome)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The one place a preset's trust is written, on the host that runs the agent. For an agent that
|
|
* inherits trust from a home, refuses when the path the writer would store covers a home. Never
|
|
* throws or waits past the host's deadline: any failure or miss means the agent asks.
|
|
*/
|
|
export async function applyWorkspaceTrustOnThisHost(
|
|
preset: AgentTrustPreset,
|
|
workspacePath: string,
|
|
describeHost: () => WorkspaceTrustHost
|
|
): Promise<void> {
|
|
try {
|
|
const host = describeHost()
|
|
if (AGENT_TRUST_INHERITS_FROM_A_HOME[preset]) {
|
|
const homes = host.homes.filter((home): home is string => Boolean(home))
|
|
if (homes.length === 0 || wouldTrustAHome(workspacePath, homes)) {
|
|
return
|
|
}
|
|
}
|
|
await awaitAgentTrustWriteWithinDeadline(writePreset(preset, workspacePath, host), {
|
|
preset,
|
|
workspacePath,
|
|
deadlineMs: host.deadlineMs
|
|
})
|
|
} catch (error) {
|
|
console.warn(
|
|
`[agent-trust] ${preset} trust for ${workspacePath} failed; the agent will ask`,
|
|
error
|
|
)
|
|
}
|
|
}
|