mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
* feat(native-chat): Codex chats list and run the skills Codex loaded A Codex chat's skill list now comes from Codex itself (`skills/list`), live while the chat runs and refreshed when Codex says its skills changed, and from a short-lived Codex check while the chat is at rest. Plugin skills keep Codex's `plugin:skill` names, disabled skills drop out, and each skill file is its own row. A skill picked from the list reaches Codex as that exact skill, beside the unchanged `$name` text, on hosts that take it. * test(native-chat): account for Codex's skill list call; send without a trailing argument * fix: align skill probes with chat launches and preserve picked mentions * fix: distinguish skill choices in phone accessibility labels * fix: clear picked skill files when queued text changes * fix(native-chat): refresh resting commands when launch settings change * fix: keep WSL agent refresh on its execution host * fix: preserve skill selection at read and send boundaries * fix: narrow agent identity for selected skill notice * fix: use checked agent predicate for skill notice label * test: retain locale in composer translation mocks
109 lines
4.3 KiB
TypeScript
109 lines
4.3 KiB
TypeScript
// What `agentSession.subscribeAvailability` publishes: one health snapshot of this host's agents,
|
|
// shared by every reader and kept without them. Each agent's location rule and installed-release
|
|
// check run once, in the server's home rather than a workspace; a launch checks again in its own.
|
|
|
|
import { homedir } from 'node:os'
|
|
import type { AgentSessionAgentAvailability } from '../../shared/agent-session-agent-availability'
|
|
import type { AgentSessionExecutionLocation } from '../../shared/agent-session-record'
|
|
import { LOCAL_EXECUTION_HOST_ID } from '../../shared/execution-host'
|
|
import type { GlobalSettings } from '../../shared/global-settings-types'
|
|
import { onAgentsRefreshedOnHost } from '../preflight/workspace-agent-detection'
|
|
import { observeStructuredAgentLaunch } from './structured-agent-launch-support'
|
|
import { STRUCTURED_AGENT_RUNTIME_METADATA } from './structured-agent-runtime-metadata'
|
|
import type { StructuredAgentRuntimeMetadata } from './structured-agent-runtime-registration-types'
|
|
import { recaptureHostShellEnvironment } from './structured-agent-shell-environment'
|
|
import type { RuntimeStore } from './runtime-store-contract'
|
|
|
|
export type StructuredAgentAvailabilityReader = (
|
|
agents: readonly AgentSessionAgentAvailability[]
|
|
) => void
|
|
|
|
// Why: these pick the profile-loading shell the check's environment is captured from.
|
|
const SHELL_SETTINGS = ['terminalWindowsShell', 'terminalWindowsPowerShellImplementation'] as const
|
|
/** Every setting the snapshot reads, so a change to any of them can change it. */
|
|
const HEALTH_SETTINGS = [
|
|
'agentCmdOverrides',
|
|
'agentDefaultEnv',
|
|
'nativeChatInheritShellEnvironment',
|
|
'nativeChatShellEnvironmentVariables',
|
|
...SHELL_SETTINGS
|
|
] as const satisfies readonly (keyof GlobalSettings)[]
|
|
|
|
// Why native: health is this machine's; a workspace's WSL or remote route is the client's rule.
|
|
const HOST_LOCATION: AgentSessionExecutionLocation = {
|
|
executionHostId: LOCAL_EXECUTION_HOST_ID,
|
|
wslDistro: null,
|
|
workspaceId: '',
|
|
workspaceKind: 'folder'
|
|
}
|
|
|
|
type HealthRuntime = Parameters<typeof observeStructuredAgentLaunch>[2]
|
|
|
|
async function observeHostAgent(
|
|
{ definition: { agent }, supportsLocation }: StructuredAgentRuntimeMetadata,
|
|
runtime: HealthRuntime
|
|
): Promise<AgentSessionAgentAvailability> {
|
|
if (!supportsLocation(HOST_LOCATION)) {
|
|
return { agent, state: 'refused', reason: 'agent' }
|
|
}
|
|
// A failed probe or an unrunnable Command setting says nothing about the installed release.
|
|
const seen = await observeStructuredAgentLaunch(
|
|
agent,
|
|
async () => homedir(),
|
|
runtime,
|
|
false
|
|
).catch(() => 'inconclusive' as const)
|
|
if (seen === 'inconclusive') {
|
|
return { agent, state: 'unknown' }
|
|
}
|
|
return seen === 'supported'
|
|
? { agent, state: 'available' }
|
|
: { agent, state: 'refused', reason: 'agent' }
|
|
}
|
|
|
|
/** The runtime's snapshot: taken after a fresh shell capture, initially and on launch settings or
|
|
* agent Refresh. No reader gates it, so a later one reads it at once. */
|
|
export function installStructuredAgentAvailability(
|
|
store: Pick<RuntimeStore, 'onSettingsChanged'> | null,
|
|
runtime: HealthRuntime
|
|
) {
|
|
const readers = new Set<StructuredAgentAvailabilityReader>()
|
|
let latest: readonly AgentSessionAgentAvailability[] | undefined
|
|
let pass = 0
|
|
const derive = (capture = true): void => {
|
|
const mine = ++pass
|
|
// Explicit Refresh already captured; first use and settings changes need fresh facts.
|
|
void (capture ? recaptureHostShellEnvironment() : Promise.resolve())
|
|
.catch(() => undefined)
|
|
.then(() =>
|
|
Promise.all(
|
|
STRUCTURED_AGENT_RUNTIME_METADATA.map((agent) => observeHostAgent(agent, runtime))
|
|
)
|
|
)
|
|
.then((agents) => {
|
|
// A newer pass supersedes this one, so nothing is owed after it.
|
|
if (mine === pass) {
|
|
latest = agents
|
|
readers.forEach((read) => read(agents))
|
|
}
|
|
})
|
|
}
|
|
store?.onSettingsChanged?.((updates) => {
|
|
if (HEALTH_SETTINGS.some((key) => key in updates)) {
|
|
derive()
|
|
}
|
|
})
|
|
onAgentsRefreshedOnHost(() => derive(false))
|
|
derive()
|
|
return {
|
|
/** Reads the latest snapshot and every later one; returns the release. */
|
|
subscribe(read: StructuredAgentAvailabilityReader): () => void {
|
|
readers.add(read)
|
|
if (latest) {
|
|
read(latest)
|
|
}
|
|
return () => readers.delete(read)
|
|
}
|
|
}
|
|
}
|