Files
orca/src/main/execution-host-workspace-trust.ts
T
NeilandOrca Integration Recovery 843607b1bc Register supervised Qoder China and Qwen Code (#24616)
* 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>
2026-10-02 22:13:26 -07:00

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
)
}
}