diff --git a/README.md b/README.md index 3e9fe972b..9b88aa6ab 100644 --- a/README.md +++ b/README.md @@ -393,6 +393,8 @@ Twenty-five backends. Pin one, or let `auto` walk the chain in order. | `mojeek` | no key (browser) | | `public` | no key (all of the above, consolidated) | +Exa also accepts a stored API key through `/login exa`; explicit keyless selection uses the public MCP fallback. + ### Specialised handlers The agent gets structured content, not stripped HTML. diff --git a/docs/environment-variables.md b/docs/environment-variables.md index f4cac96ab..7f0046b28 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -244,7 +244,7 @@ OAuth host chain: `KIMI_CODE_OAUTH_HOST` → `KIMI_OAUTH_HOST` → `https://auth | Variable | Used by | | --------------------------------------------------- | ------------------------------------------------------------- | -| `EXA_API_KEY` | Exa search provider and Exa MCP tools | +| `EXA_API_KEY` | Exa search/MCP; alternatively use `/login exa` | | `BRAVE_API_KEY` | Brave search provider | | `PERPLEXITY_API_KEY` | Perplexity search provider API-key mode | | `PERPLEXITY_COOKIES` | Perplexity cookie-auth search mode | diff --git a/docs/extensions.md b/docs/extensions.md index 75dc941c1..33c0af111 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -158,6 +158,7 @@ Handlers and tool `execute` receive `ctx` with: - `modelRegistry`, `model` - `models` (read-only model query — see below) - `getContextUsage()` +- `getAsyncJobSnapshot()` returns the current session's read-only async-job snapshot, or `null` when no session owns the context - `compact(...)` - `isIdle()`, `hasPendingMessages()`, `abort()` - `shutdown()` @@ -268,6 +269,23 @@ Cancelable pre-events: - `goal_updated` - `credential_disabled` +### MCP notifications + +- `mcp_notification` — fired for every JSON-RPC notification received from a connected MCP server, AFTER the manager's own handling of known list/update methods (`notifications/tools/list_changed`, `notifications/resources/list_changed`, `notifications/resources/updated`, `notifications/prompts/list_changed`). Unknown or server-custom methods are also delivered. Payload: `{ server: string; method: string; params: unknown }`. Multiple extensions may subscribe; a handler that throws does not prevent other handlers from firing. Notifications received before any listener attaches are buffered (bounded FIFO, cap 100, drop-oldest) and drained into the first subscriber — so startup-time frames aren't lost even if the extension binds after MCP discovery. + +Bridging a push-capable MCP into a session steer: + +```ts +pi.on("mcp_notification", event => { + if (event.server !== "peer-bus") return; + if (event.method !== "notifications/peer_message") return; + const params = event.params as { from: string; text: string }; + pi.sendUserMessage(`[from ${params.from}] ${params.text}`, { deliverAs: "steer" }); +}); +``` + +The runtime handles the JSON-RPC transport and its own list/update refresh first; the handler runs afterwards and can inject a mid-turn steer via `pi.sendMessage` / `pi.sendUserMessage`. + ### User command interception - `user_bash` (override with `{ result }`) diff --git a/docs/mcp-runtime-lifecycle.md b/docs/mcp-runtime-lifecycle.md index 7896bf8ac..45707571c 100644 --- a/docs/mcp-runtime-lifecycle.md +++ b/docs/mcp-runtime-lifecycle.md @@ -158,6 +158,19 @@ Both return structured tool output and convert remaining transport/tool errors i There is also a follow-up path for late connections: after waiting for a specific server, if status becomes `connected`, it re-runs `session.refreshMCPTools(...)` so newly available tools are rebound in-session. + +## Server-initiated notifications + +MCP servers may push JSON-RPC notification frames at any point after `initialize` completes. The transport surfaces them via `onNotification`; the manager fans them out in two paths: + +1. **Internal refresh** for known methods: + - `notifications/tools/list_changed` → `refreshServerTools` + - `notifications/resources/list_changed` → `refreshServerResources` + - `notifications/resources/updated` → `#onResourcesChanged` (only for currently subscribed URIs) + - `notifications/prompts/list_changed` → `refreshServerPrompts` +2. **Listener fanout**: every notification (including the known ones AND server-custom methods) is delivered to registered listeners AFTER the internal refresh runs. Registered via `MCPManager.addNotificationListener(listener)`, which returns an unsubscribe function. Multiple listeners are supported; each is invoked with independent error isolation — a synchronous throw in one listener does not prevent others from firing (thrown errors are logged at `debug`). + +`sdk.ts` registers one listener that bridges to the extension runner's `mcp_notification` event, so extensions receive every server-initiated frame with `{ server, method, params }`. The listener is captured with `postmortem` so it is released on session teardown. ## Health, reconnect, and partial failure behavior Current runtime behavior is connection-event driven: diff --git a/docs/rpc.md b/docs/rpc.md index d316700fb..da926f084 100644 --- a/docs/rpc.md +++ b/docs/rpc.md @@ -124,6 +124,7 @@ Important edge behavior from runtime: ### State - `{ id?, type: "get_state" }` +- `{ id?, type: "set_fast_mode", enabled: boolean }` - `{ id?, type: "get_available_commands" }` - `{ id?, type: "set_todos", phases: TodoPhase[] }` - `{ id?, type: "set_host_tools", tools: RpcHostToolDefinition[] }` @@ -231,6 +232,19 @@ Local-only slash commands may emit `command_output` frames before completing via ### `get_state` payload +`tokensPerSecond` is a number when output throughput is available and `null` +otherwise. `fastModeEnabled` reports the session setting, while +`fastModeActive` reports the actual computed active state. For Fireworks, +`providers.fireworksTier: priority` is a provider-level setting independent of +the `/fast` family setting, so `fastModeActive` may remain `true` for an +unsupported Fireworks model. + +For direct Anthropic, a provider rejection of `speed: "fast"` uses a sticky +fallback scoped by the resolved endpoint and exact model: `fastModeEnabled` may +remain `true` while `fastModeActive` is `false`. An explicit `set_fast_mode` +enable expresses retry intent and clears that fallback so the provider attempt +is re-armed. + ```json { "model": { "provider": "...", "id": "..." }, @@ -243,6 +257,9 @@ Local-only slash commands may emit `command_output` frames before completing via "sessionFile": "...", "sessionId": "...", "sessionName": "...", + "fastModeEnabled": false, + "tokensPerSecond": null, + "fastModeActive": false, "autoCompactionEnabled": true, "messageCount": 0, "queuedMessageCount": 0, @@ -275,6 +292,73 @@ Local-only slash commands may emit `command_output` frames before completing via } ``` +### `set_fast_mode` payload + +`set_fast_mode` changes whether fast mode is enabled for the session. The +request is: + +```json +{ "id": "req_fast_on", "type": "set_fast_mode", "enabled": true } +``` + +On success, `data` always contains both `enabled` and `active`. These are the +actual computed values: `enabled` reports the session setting, and `active` +reports the resulting active state, including any provider-level Fireworks +priority setting: + +For direct Anthropic, an explicit enable also re-arms a provider attempt after +the sticky rejection fallback, even when fast mode was already enabled. + +```json +{ + "id": "req_fast_on", + "type": "response", + "command": "set_fast_mode", + "success": true, + "data": { "enabled": true, "active": true } +} +``` + +Enabling fast mode on a model without a service-tier family fails with the +exact error below: + +```json +{ + "id": "req_fast_on", + "type": "response", + "command": "set_fast_mode", + "success": false, + "error": "Fast mode is unavailable for the current model." +} +``` + +Disabling fast mode is idempotent, including on an unsupported model. It +succeeds as an off/no-op result, but disabling `/fast` does not override +provider-level settings, so a successful disable does not guarantee +`active: false`. For example, with an unsupported +`fireworks/deepseek-v4-flash` model and `providers.fireworksTier: priority`, +the response reports the session setting as disabled while the provider +priority keeps the computed active state true: + +```json +{ + "id": "req_fast_off", + "type": "response", + "command": "set_fast_mode", + "success": true, + "data": { "enabled": false, "active": true } +} +``` + +The corresponding `get_state` result reports the same computed state: + +```json +{ + "fastModeEnabled": false, + "fastModeActive": true +} +``` + ### `set_todos` payload Replaces the in-memory todo state for the current session and returns the normalized phase list: diff --git a/docs/settings.md b/docs/settings.md index e02ff0d78..7f5c2bb10 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -385,6 +385,7 @@ thinkingBudgets: | `thinkingBudgets.high` | number | `16384` | Token budget for `high`. | | `thinkingBudgets.xhigh` | number | `32768` | Token budget for `xhigh`. | | `thinkingBudgets.max` | number | `32768` | Token budget for `max`. | +| `providers.autoThinkingMaxEffort` | enum | `xhigh` | Highest effort `defaultThinkingLevel: auto` may resolve. `xhigh` keeps the classifier one tier below the top, so only `ultrathink` reaches `max`; `max` lets the classifier bill the top tier on models that expose it. The local on-device classifier stays capped at `xhigh` either way. This governs what `auto` *resolves*: a model whose ladder offers nothing under the ceiling gets no auto level at all, and one that also sets `thinking.requiresEffort` still receives its lowest supported effort from the transport — on a `["max"]` ladder that is `max`, because the model accepts nothing else. | ### Sampling diff --git a/docs/tools/browser.md b/docs/tools/browser.md index a5c0e1a59..2189c2e75 100644 --- a/docs/tools/browser.md +++ b/docs/tools/browser.md @@ -55,7 +55,7 @@ | `viewport` | `{ width: number; height: number; scale?: number }` | No | Requested viewport. For headless launch this becomes the initial viewport; for a page it is applied with `page.setViewport()`. `scale` maps to Puppeteer `deviceScaleFactor`. | | `wait_until` | `"load" \| "domcontentloaded" \| "networkidle0" \| "networkidle2"` | No | Navigation wait condition. Defaults to `"load"` where omitted, including `open` navigation and later `tab.goto(...)`. | | `dialogs` | `"accept" \| "dismiss"` | No | Installs a page `dialog` handler that auto-accepts or auto-dismisses dialogs. Omitted means no handler. | -| `app` | `{ path?: string; cdp_url?: string; args?: string[]; target?: string }` | No | Selects browser kind. With no `app`, the cmux backend is used when a cmux socket is available (`CMUX_SOCKET_PATH`, gated by the `browser.cmux` setting / `PI_BROWSER_CMUX` override); otherwise the session `browser.headless` setting applies. `app.path` is resolved against the session cwd and used as the executable path for spawn/attach reuse. `app.cdp_url` connects to an existing CDP endpoint. `args` are appended only when spawning `app.path`. `target` is only used for attached/spawned-app page selection. | +| `app` | `{ path?: string; cdp_url?: string; args?: string[]; target?: string }` | No | Selects browser kind. With no `app`, a configured `browser.cdpUrl` setting attaches to that endpoint; otherwise the cmux backend is used when a cmux socket is available (`CMUX_SOCKET_PATH`, gated by the `browser.cmux` setting / `PI_BROWSER_CMUX` override); otherwise the session `browser.headless` setting applies. `app.path` is resolved against the session cwd and used as the executable path for spawn/attach reuse. `app.cdp_url` connects to an existing CDP endpoint. `args` are appended only when spawning `app.path`. `target` is only used for attached/spawned-app page selection. | ### `action: "close"` @@ -92,6 +92,7 @@ The tool returns one result per call; no streaming partial output is emitted fro 2. `open` resolves browser kind with `resolveBrowserKind()`: - `app.cdp_url` → `{ kind: "connected" }` after trimming trailing slashes. - `app.path` → `{ kind: "spawned" }` after resolving against session cwd. + - otherwise, a non-empty `browser.cdpUrl` setting → `{ kind: "connected" }` after trimming whitespace and trailing slashes. - otherwise, `resolveCmuxKind()` → `{ kind: "cmux", socketPath, password?, surface? }` when `CMUX_SOCKET_PATH` is set and cmux is enabled (`browser.cmux` setting, overridable by `PI_BROWSER_CMUX`). - otherwise → `{ kind: "headless", headless: session.settings.get("browser.headless") }`. 3. `open` rejects reusing the same tab name across different browser kinds (`sameBrowserKind()`); callers must close first. @@ -163,7 +164,7 @@ The tool returns one result per call; no streaming partial output is emitted fro - **Browser kind** - **Headless**: launches local Chromium with Puppeteer, applies stealth patches, and creates a fresh page per tab. - **Spawned app (`app.path`)**: reuses an existing CDP-enabled process for that executable when possible; otherwise kills same-path processes, spawns the executable with remote debugging enabled, then attaches. No stealth patches are injected. - - **Connected browser (`app.cdp_url`)**: attaches to an already-running CDP endpoint. No process ownership; close only disconnects. + - **Connected browser (`app.cdp_url`, or the `browser.cdpUrl` setting when the call carries no `app`)**: attaches to an already-running CDP endpoint. No process ownership; close only disconnects. - **Cmux surface (`browser.cmux`)**: with no `app` and a cmux socket available (`CMUX_SOCKET_PATH`, enabled by the `browser.cmux` setting / `PI_BROWSER_CMUX` override), drives a cmux WKWebView surface over a unix-socket JSON-RPC client instead of Puppeteer. No Bun worker and no stealth patches; `open` opens a split (owning that surface), `run` executes via `runCmuxCode()`, and `close` issues `surface.close` for surfaces it owns (leaving the workspace's last surface open). - **Target selection for attached/spawned browsers** - With `app.target`, `pickElectronTarget()` returns the first page whose URL or title contains the case-insensitive substring. diff --git a/docs/tools/web_search.md b/docs/tools/web_search.md index f54eaa1bb..f14e74e6e 100644 --- a/docs/tools/web_search.md +++ b/docs/tools/web_search.md @@ -147,7 +147,7 @@ Streaming: none. `WebSearchTool.execute()` forwards its `AbortSignal` into `exec - `limit` and `num_search_results` are collapsed together before dispatch. - Output may include parsed free-text `answer`, `sources`, `requestId`. - **Exa** — `packages/coding-agent/src/web/search/providers/exa.ts` - - Availability: env or `agent.db` credential for `exa` admits Exa to the auto chain; settings must not explicitly disable `exa.enabled` or `exa.enableSearch`. Explicit selection (listing `exa` in `providers.webSearchOrder`, or a forced `provider: exa`) reaches Exa even without a credential and falls back to public MCP. + - Availability: `EXA_API_KEY` or a stored credential for `exa` (including one added through `/login exa`) admits Exa to the auto chain; settings must not explicitly disable `exa.enabled` or `exa.enableSearch`. Explicit selection (listing `exa` in `providers.webSearchOrder`, or a forced `provider: exa`) reaches Exa even without a credential and falls back to public MCP. - Querying: POST `https://api.exa.ai/search` with the resolved Exa API key, otherwise JSON-RPC `tools/call` against `https://mcp.exa.ai/mcp` for remote MCP tool `web_search_exa`. - `limit` and `num_search_results` are collapsed together before dispatch. - Output: synthesized `answer` from up to 3 result summaries, `sources`, `requestId`. @@ -279,4 +279,4 @@ Streaming: none. `WebSearchTool.execute()` forwards its `AbortSignal` into `exec - `recency` is implemented by Brave, Perplexity, Tavily, SearXNG, Kagi, TinyFish, Firecrawl, xAI, DuckDuckGo, Bing, Yahoo, Startpage, Google, and Mojeek (Ecosia ignores it; Public Web passes it through). The model-facing prompt does not name specific providers. - `packages/coding-agent/src/config/settings-schema.ts` uses the shared `SEARCH_PROVIDER_PREFERENCES` / `SEARCH_PROVIDER_OPTIONS` metadata, so the settings selector and setup wizard expose `auto` plus every provider in the auto chain. - The credential-free scrapers close the auto chain, cheap plain-fetch engines first (`duckduckgo`, `bing`, `yahoo`, `startpage`) and browser-backed ones after (`google`, `ecosia`, `mojeek`); `public` is listed last and never auto-selected. -- Exa uses `authStorage.getApiKey("exa")`, then `EXA_API_KEY`, then unauthenticated `https://mcp.exa.ai/mcp` fallback. +- `/login exa` stores the pasted key in AuthStorage; Exa resolves credentials in order from `authStorage.getApiKey("exa")`, then `EXA_API_KEY`, then the unauthenticated `https://mcp.exa.ai/mcp` fallback. diff --git a/packages/agent/CHANGELOG.md b/packages/agent/CHANGELOG.md index b4487a72a..e97272a65 100644 --- a/packages/agent/CHANGELOG.md +++ b/packages/agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Provider-native compaction failures now surface their transport error instead of silently switching to generic summarization; streaming V2 still falls back to native V1 when available. + ## [17.1.7] - 2026-07-27 ### Changed diff --git a/packages/agent/src/compaction/compaction-v2-streaming.ts b/packages/agent/src/compaction/compaction-v2-streaming.ts index e14f6b422..997e43f00 100644 --- a/packages/agent/src/compaction/compaction-v2-streaming.ts +++ b/packages/agent/src/compaction/compaction-v2-streaming.ts @@ -8,7 +8,7 @@ */ import type { Api, CodexCompactionContext, FetchImpl, Model, ProviderSessionState } from "@oh-my-pi/pi-ai"; -import { isTransientStatus, ProviderHttpError } from "@oh-my-pi/pi-ai/error"; +import * as AIError from "@oh-my-pi/pi-ai/error"; import { applyCodexResponsesLiteShape } from "@oh-my-pi/pi-ai/providers/openai-codex/request-transformer"; import { createOpenAICodexCompactionRequestContext, @@ -21,6 +21,7 @@ import { parseAzureDeploymentNameMap, resolveOpenAIRequestSetup, } from "@oh-my-pi/pi-ai/providers/openai-shared"; +import { captureOpenAIHttpError } from "@oh-my-pi/pi-ai/utils/openai-http"; import { CODEX_BASE_URL, getCodexAccountId, @@ -334,18 +335,19 @@ async function attemptCompactionV2Streaming( }); if (!response.ok) { - const errorText = await response.text().catch(() => ""); + const cause = await captureOpenAIHttpError(response); logger.warn("V2 remote compaction failed", { endpoint, status: response.status, statusText: response.statusText, - errorText, + errorText: cause.captured.bodyText ?? "", }); - throw new ProviderHttpError( + throw new AIError.ProviderHttpError( `V2 remote compaction failed (${response.status} ${response.statusText})`, response.status, { headers: response.headers, + cause, }, ); } @@ -560,6 +562,10 @@ function formatCompactionV2Failure(event: Record, type: string) } function isRetryableCompactionError(error: Error): boolean { + // The gateway's synthetic auth_unavailable is an HTTP 503, but the + // captured response cause classifies it as auth. Let provider fallback run + // immediately instead of spending the transient retry budget. + if (AIError.is(AIError.classify(error), AIError.Flag.AuthFailed)) return false; if ( error.name === "AbortError" || error.name === "TimeoutError" || @@ -567,8 +573,8 @@ function isRetryableCompactionError(error: Error): boolean { ) { return true; } - if (error instanceof ProviderHttpError) { - return isTransientStatus(error.status); + if (error instanceof AIError.ProviderHttpError) { + return AIError.isTransientStatus(error.status); } const message = error.message.toLowerCase(); return ( diff --git a/packages/agent/src/compaction/compaction.ts b/packages/agent/src/compaction/compaction.ts index cb1317b7e..e524f251e 100644 --- a/packages/agent/src/compaction/compaction.ts +++ b/packages/agent/src/compaction/compaction.ts @@ -22,7 +22,7 @@ import { type Usage, withAuth, } from "@oh-my-pi/pi-ai"; -import { ProviderHttpError } from "@oh-my-pi/pi-ai/error"; +import * as AIError from "@oh-my-pi/pi-ai/error"; import { createOpenAICodexCompactionRequestContext } from "@oh-my-pi/pi-ai/providers/openai-codex-responses"; import { convertTools } from "@oh-my-pi/pi-ai/providers/openai-responses"; import { buildResponsesInput, resolveOpenAICompatPolicy } from "@oh-my-pi/pi-ai/providers/openai-shared"; @@ -43,6 +43,7 @@ import { V2_RETAINED_MESSAGE_TOKEN_BUDGET, } from "./compaction-v2-streaming"; import type { CompactionEntry, SessionEntry } from "./entries"; +import { NativeCompactionError } from "./errors"; import { isEstimateCacheable, readEstimateCache, writeEstimateCache } from "./message-cache"; import { type ConvertToLlm, createBranchSummaryMessage, createCustomMessage, defaultConvertToLlm } from "./messages"; import { @@ -202,6 +203,18 @@ export const DEFAULT_COMPACTION_SETTINGS: CompactionSettings = { v2RetainedMessageBudget: V2_RETAINED_MESSAGE_TOKEN_BUDGET, }; +/** Whether a compaction candidate preserves provider-native transport under the effective settings. */ +export function shouldUseProviderNativeCompaction( + model: Model, + settings: Pick, +): boolean { + if (settings.remoteEnabled === false) return false; + return ( + shouldUseOpenAiRemoteCompaction(model) || + (settings.remoteStreamingV2Enabled !== false && shouldUseCompactionV2Streaming(model)) + ); +} + // ============================================================================ // Token calculation // ============================================================================ @@ -725,7 +738,9 @@ function resolveCompactionEffort(model: Model, level: ThinkingLevel | undefined) */ function createSummarizationError(prefix: string, response: AssistantMessage): Error { const text = `${prefix}: ${response.errorMessage || "Unknown error"}`; - return response.errorStatus === undefined ? new Error(text) : new ProviderHttpError(text, response.errorStatus); + return response.errorStatus === undefined + ? new Error(text) + : new AIError.ProviderHttpError(text, response.errorStatus); } function shouldRetryHandoffWithAutoToolChoice(response: AssistantMessage): boolean { @@ -1332,6 +1347,17 @@ function buildCompactionV2Reasoning( return { effort: reasoning.wireEffort ?? reasoning.requestedEffort, summary: "auto" }; } +/** + * Keep any non-auth native protocol failure ahead of authentication failures. + * Downstream may retry compaction with another provider only when every native + * protocol failed authentication, so a later auth error must not hide an + * earlier transport or protocol failure. + */ +function selectNativeCompactionError(previousError: unknown, nextError: unknown): unknown { + if (previousError === undefined) return nextError; + return AIError.is(AIError.classify(previousError), AIError.Flag.AuthFailed) ? nextError : previousError; +} + /** * Generate summaries for compaction using prepared data. * Returns CompactionResult - SessionManager adds id/parentId when saving. @@ -1406,6 +1432,7 @@ export async function compact( ...recentMessages, ]; let usedRemoteCompaction = false; + let nativeCompactionError: unknown; if ( settings.remoteEnabled !== false && settings.remoteStreamingV2Enabled !== false && @@ -1467,7 +1494,8 @@ export async function compact( // swallowing it here would downgrade Esc into "fall back to local // summarization" and keep compaction running on an aborted signal. if (signal?.aborted) throw err; - logger.warn("OpenAI V2 remote compaction failed, falling back to V1/local summarization", { + nativeCompactionError = selectNativeCompactionError(nativeCompactionError, err); + logger.warn("OpenAI V2 remote compaction failed, falling back to V1 remote compaction", { error: err instanceof Error ? err.message : String(err), model: model.id, provider: model.provider, @@ -1517,7 +1545,8 @@ export async function compact( // swallowing it here would downgrade Esc into "fall back to local // summarization" and keep compaction running on an aborted signal. if (signal?.aborted) throw err; - logger.warn("OpenAI remote compaction failed, falling back to local summarization", { + nativeCompactionError = selectNativeCompactionError(nativeCompactionError, err); + logger.warn("OpenAI remote compaction failed", { error: err instanceof Error ? err.message : String(err), model: model.id, provider: model.provider, @@ -1526,6 +1555,10 @@ export async function compact( } } + if (!usedRemoteCompaction && nativeCompactionError !== undefined && !summaryOptions.remoteEndpoint) { + throw new NativeCompactionError(nativeCompactionError); + } + // Generate summaries (can be parallel if both needed) and merge into one let summary: string; diff --git a/packages/agent/src/compaction/errors.ts b/packages/agent/src/compaction/errors.ts index f60d0926e..ed34484dd 100644 --- a/packages/agent/src/compaction/errors.ts +++ b/packages/agent/src/compaction/errors.ts @@ -18,6 +18,22 @@ export class CompactionCancelledError extends Error { } } +/** + * A provider-native compaction request failed after every native protocol + * available for the selected model was exhausted. + * + * The cause stays attached so AI error classification can still recognize + * authentication failures. Non-auth failures remain distinguishable from + * ordinary summarization errors and must not fall through to another provider. + */ +export class NativeCompactionError extends Error { + readonly name = "NativeCompactionError" as const; + + constructor(cause: unknown) { + super(cause instanceof Error ? cause.message : String(cause), { cause }); + } +} + /** * Outcome of a compaction attempt, surfaced by `CommandController.executeCompaction` * so callers (e.g. the plan-mode approval flow) can distinguish a deliberate abort diff --git a/packages/agent/src/compaction/openai.ts b/packages/agent/src/compaction/openai.ts index df3b8d4f2..ea5e181bf 100644 --- a/packages/agent/src/compaction/openai.ts +++ b/packages/agent/src/compaction/openai.ts @@ -38,6 +38,7 @@ import { getOpenAIResponsesHistoryPayload, normalizeResponsesToolCallId, } from "@oh-my-pi/pi-ai/utils"; +import { captureOpenAIHttpError } from "@oh-my-pi/pi-ai/utils/openai-http"; import { CODEX_BASE_URL, getCodexAccountId, @@ -840,18 +841,19 @@ export async function requestOpenAiRemoteCompaction( }); if (!response.ok) { - const errorText = await response.text().catch(() => ""); + const cause = await captureOpenAIHttpError(response); logger.warn("OpenAI remote compaction failed", { endpoint, status: response.status, statusText: response.statusText, - errorText, + errorText: cause.captured.bodyText ?? "", }); throw new ProviderHttpError( `Remote compaction failed (${response.status} ${response.statusText})`, response.status, { headers: response.headers, + cause, }, ); } diff --git a/packages/agent/test/remote-compaction.test.ts b/packages/agent/test/remote-compaction.test.ts index 155080b75..fd3879d50 100644 --- a/packages/agent/test/remote-compaction.test.ts +++ b/packages/agent/test/remote-compaction.test.ts @@ -4,6 +4,7 @@ import { compact, createFileOps, DEFAULT_COMPACTION_SETTINGS, + NativeCompactionError, prepareCompaction, type SessionEntry, } from "@oh-my-pi/pi-agent-core/compaction"; @@ -20,6 +21,7 @@ import { trimRemoteCompactionInputToContextWindow, } from "@oh-my-pi/pi-agent-core/compaction/openai"; import * as ai from "@oh-my-pi/pi-ai"; +import * as AIError from "@oh-my-pi/pi-ai/error"; import { getOpenAICodexTransportDetails } from "@oh-my-pi/pi-ai/providers/openai-codex-responses"; import type { AssistantMessage, @@ -733,6 +735,36 @@ describe("requestCompactionV2Streaming", () => { expect(attempts).toBe(2); }); + + test("does not retry and preserves auth_unavailable from V2 HTTP failures", async () => { + const model = makeOpenAiModel({ + remoteCompaction: { + enabled: true, + v2StreamingEnabled: true, + v2Endpoint: "https://compact.example/v1/responses", + }, + }); + const request = buildCompactionV2Request( + model, + [{ type: "message", role: "user", content: [{ type: "input_text", text: "real user" }] }], + "instructions", + ); + const fetchMock = vi.fn(async () => + Response.json( + { error: { type: "auth_unavailable", message: "no auth available for codex" } }, + { status: 503, statusText: "Service Unavailable" }, + ), + ); + + const error = await requestCompactionV2Streaming(model, "test-key", request, undefined, { + fetch: fetchMock, + retryWait: async () => {}, + }).catch(cause => cause); + + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(error).toBeInstanceOf(AIError.ProviderHttpError); + expect(AIError.is(AIError.classify(error), AIError.Flag.AuthFailed)).toBe(true); + }); }); describe("Responses Lite remote compaction", () => { @@ -1687,6 +1719,78 @@ describe("compact() remote compaction failure handling", () => { expect(JSON.stringify(sameProviderActive?.messagesToSummarize ?? [])).not.toContain("ORIGINAL ALPHA port 4242"); }); + test("retains the V2 non-auth failure when the V1 fallback fails authentication", async () => { + const preparation = makePreparation(); + preparation.settings = { ...preparation.settings, remoteStreamingV2Enabled: true }; + const model = makeOpenAiModel({ + remoteCompaction: { enabled: true, v2StreamingEnabled: true }, + }); + const requestedUrls: string[] = []; + const fetchMock: FetchImpl = async input => { + const url = String(input); + requestedUrls.push(url); + return url.endsWith("/responses/compact") + ? new Response("authentication failed", { status: 401, statusText: "Unauthorized" }) + : new Response("V2 transport failed", { status: 400, statusText: "Bad Request" }); + }; + + const error = await compact(preparation, model, "test-key", undefined, undefined, { fetch: fetchMock }).catch( + cause => cause, + ); + + expect(requestedUrls.map(url => new URL(url).pathname)).toEqual(["/v1/responses", "/v1/responses/compact"]); + expect(error).toBeInstanceOf(NativeCompactionError); + expect(error).toMatchObject({ cause: { status: 400 } }); + expect(AIError.is(AIError.classify(error), AIError.Flag.AuthFailed)).toBe(false); + }); + + test("keeps native compaction auth-classified when every attempted protocol fails authentication", async () => { + const preparation = makePreparation(); + preparation.settings = { ...preparation.settings, remoteStreamingV2Enabled: true }; + const model = makeOpenAiModel({ + remoteCompaction: { enabled: true, v2StreamingEnabled: true }, + }); + const requestedUrls: string[] = []; + const fetchMock: FetchImpl = async input => { + requestedUrls.push(String(input)); + return new Response("authentication failed", { status: 401, statusText: "Unauthorized" }); + }; + + const error = await compact(preparation, model, "test-key", undefined, undefined, { fetch: fetchMock }).catch( + cause => cause, + ); + + expect(requestedUrls.map(url => new URL(url).pathname)).toEqual(["/v1/responses", "/v1/responses/compact"]); + expect(error).toBeInstanceOf(NativeCompactionError); + expect(error).toMatchObject({ cause: { status: 401 } }); + expect(AIError.is(AIError.classify(error), AIError.Flag.AuthFailed)).toBe(true); + }); + + test("V2 native failure falls back to V1 without generic summarization", async () => { + const completeSpy = vi.spyOn(ai, "completeSimple").mockResolvedValue(localSummaryMessage("local summary")); + const preparation = makePreparation(); + preparation.settings = { ...preparation.settings, remoteStreamingV2Enabled: true }; + const model = makeOpenAiModel({ + remoteCompaction: { enabled: true, v2StreamingEnabled: true }, + }); + const requestedUrls: string[] = []; + const fetchMock: FetchImpl = async input => { + const url = String(input); + requestedUrls.push(url); + if (url.endsWith("/responses/compact")) { + return Response.json({ output: [{ type: "compaction", encrypted_content: "enc-v1" }] }); + } + return new Response("V2 unavailable", { status: 502, statusText: "Bad Gateway" }); + }; + + const result = await compact(preparation, model, "test-key", undefined, undefined, { fetch: fetchMock }); + + expect(requestedUrls.some(url => url.endsWith("/responses"))).toBe(true); + expect(requestedUrls.some(url => url.endsWith("/responses/compact"))).toBe(true); + expect(result.shortSummary).toBe("Remote compaction"); + expect(completeSpy).not.toHaveBeenCalled(); + }); + test("user abort during the remote compact request rejects without falling back to local summarization", async () => { // Contract: Esc is a cancellation, not a remote failure. Before the fix // the AbortError was swallowed by the fallback catch and compaction kept @@ -1760,16 +1864,54 @@ describe("compact() remote compaction failure handling", () => { }); }); - test("remote compact server failure without abort still falls back to local summarization", async () => { + test("uses an explicit remote endpoint after provider-native compaction fails", async () => { + const completeSpy = vi.spyOn(ai, "completeSimple").mockResolvedValue(localSummaryMessage("local fallback")); + const preparation = makePreparation(); + preparation.settings = { + ...preparation.settings, + remoteEndpoint: "http://summary.test/v1/chat/completions", + remoteStreamingV2Enabled: true, + }; + const model = makeOpenAiModel({ + remoteCompaction: { enabled: true, v2StreamingEnabled: true }, + }); + const requestedUrls: string[] = []; + const fetchMock: FetchImpl = async input => { + const url = String(input); + requestedUrls.push(url); + if (url === preparation.settings.remoteEndpoint) { + const summary = + requestedUrls.filter(requested => requested === url).length === 1 + ? "configured remote history summary" + : "configured remote short summary"; + return Response.json({ choices: [{ message: { content: summary } }] }); + } + return new Response("native compaction unavailable", { status: 400, statusText: "Bad Request" }); + }; + + const result = await compact(preparation, model, "test-key", undefined, undefined, { fetch: fetchMock }); + + expect(requestedUrls.map(url => new URL(url).pathname)).toEqual([ + "/v1/responses", + "/v1/responses/compact", + "/v1/chat/completions", + "/v1/chat/completions", + ]); + expect(result.summary).toContain("configured remote history summary"); + expect(result.shortSummary).toBe("configured remote short summary"); + expect(completeSpy).not.toHaveBeenCalled(); + }); + + test("native compaction server failure rejects without generic summarization", async () => { const completeSpy = vi.spyOn(ai, "completeSimple").mockResolvedValue(localSummaryMessage("local summary")); const fetchMock: FetchImpl = async () => new Response("nope", { status: 500, statusText: "Internal Server Error" }); - const result = await compact(makePreparation(), makeOpenAiModel(), "test-key", undefined, undefined, { - fetch: fetchMock, - }); - - expect(result.summary).toContain("local summary"); - expect(completeSpy).toHaveBeenCalled(); + await expect( + compact(makePreparation(), makeOpenAiModel(), "test-key", undefined, undefined, { + fetch: fetchMock, + }), + ).rejects.toThrow("Remote compaction failed"); + expect(completeSpy).not.toHaveBeenCalled(); }); }); diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 1629eee20..90a84d279 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -7,12 +7,16 @@ - Added first-class parentTurnId support for nested Codex requests, allowing stream options and metadata helpers to accept and safely propagate the initiating turn's ID. - Added preservation of the Codex `encrypted_function_args` plaintext-collaboration marker on replayed function calls, keeping server-marked plaintext tool arguments from being reinterpreted as encrypted on subsequent turns. - Added exact OAuth credential-row resolution by durable credential id. The targeted path refreshes only that row and never ranks, rotates, or falls back to sibling accounts. +- Added interactive Exa API-key login through `/login exa`, opening the official API-key dashboard and saving pasted keys to the credential store ([#1798](https://github.com/can1357/oh-my-pi/issues/1798)). +- Cursor's modern exec wire protocol is now handled end to end. `agent.proto` models the frames current Cursor CLI builds emit — the seven Pi tools (`ExecServerMessage` 45-51), hooks, subagents, allowlist prechecks, MCP state, smart-mode classification, canvas diagnostics, conversation search, agent-store conflicts and git diff — and every one of them gets a typed answer. The Pi frames run their local equivalents (`read`/`bash`/`edit`/`write`/`grep`/`glob`); the rest answer with the error, not-found or empty-but-valid variant that is actually true of this client. Frames this build cannot name at all now raise `ExecClientControlMessage.throw` with `unknown_exec_variant`, and recognised frames with no truthful answer (`git_diff_request`, whose `GetDiffResponse` has no error variant) raise `exec_variant_unsupported`, instead of a silent ack that leaves the server waiting. +- `lsp` is advertised in the MCP tool catalog again. It was filtered out as a Cursor-native tool, but the native `diagnostics` frame covers one of roughly ten LSP actions, so the other nine were unreachable. ### Changed - Codex turn metadata now reserves the codex-rs `code_mode_tool_names` key, preventing caller-supplied client metadata extras from colliding with the core-owned field. - Codex SSE requests to the official endpoint now use zstd-compressed bodies by default to match the official client, which can be disabled with PI_CODEX_ZSTD=0. - API-key validation now preserves provider HTTP status and retry headers, allowing authentication, rate-limit, and server failures to retain their original error classifications. +- The Cursor Pi arg translation (`piReadPath`, `piJoinPath`, `piLsPath`, `piEscapeRegexLiteral`, `piLimit`) moved to `providers/cursor-pi-args`, re-exported from `providers/cursor/exec-modern` so existing imports are unaffected. The legacy pi shim shares these helpers and is compiled into the bundled virtual module registry, where a nested `providers//` specifier is unresolvable under bunfs — and importing them from the exec module would drag the whole protobuf graph in for two string functions. ### Fixed @@ -26,6 +30,31 @@ - Fixed direct Anthropic Claude Opus requests failing with HTTP 400 when the endpoint rejects strict tool fields. - Fixed usage-based credential ranking for Anthropic accounts where a missing long-window (7-day) metric was incorrectly treated as a short-window metric. - Fixed legacy Codex usage blocks continuing to gate all models after per-meter backoff was introduced, splitting the old shared scope into independent chat and spark blocks while maintaining backward compatibility with older clients and database schemas. +- Fixed Anthropic retry loops ignoring `maxRetryDelayMs` for long server `retry-after` hints, so over-budget delays surface immediately without losing response details or abort cleanup ([#7003](https://github.com/can1357/oh-my-pi/issues/7003)). +- Added interactive xAI API-key login with key validation through the xAI models endpoint. +- Fixed Google Gemini and Vertex tool declarations carrying numeric, boolean, object-valued, or mixed `enum` arrays that the Google Schema wire type cannot represent. Unsupported enums are omitted while valid string enums remain constrained. +- Umans usage provider: fetches `GET /v1/usage` and surfaces the rolling 5h request window + concurrency limits in `/usage`, `omp usage`, and the TUI status bar. +- Fixed ranged legacy Cursor reads reporting the returned window byte length as the full file size. +- Updated the Cursor client build advertisement to activate the modern exec-frame protocol handled by this provider. +- Fixed a windowed Cursor `read` reporting the window's line count as the file's. `total_lines` and `file_size` were derived from the payload, which is the whole file only for an unranged read — a 20-line page of a 100-line file answered `total_lines: 20`, which a paginating server reads as the end of the file. The count now comes from the read's own record of the file (`details.meta.truncation.totalLines`), falling back to counting the payload when the read returned the file whole. +- Fixed a `pi_grep` that hit the native backend's internal match ceiling answering as an unqualified success. `GrepTool` folds that cap into the flat `details.truncated` alone, setting neither `details.truncation` nor `perFileLimitReached` — the two fields the Pi result was built from — so the one truncation a caller can neither detect nor page around was the one it was never told about. The flat flag is now translated into a `PiTruncation`, and only when no specific cap already reported itself. +- Fixed a `pi_grep` frame's `context` and `limit` vanishing from the transcript. The bridge honors both by building a scoped `grep`, but neither is expressible in the model-facing schema, so the synthesized block recorded a plain pattern/path search — replaying a context-widened or capped search as an ordinary grep sitting beside output no ordinary grep produces. Both are now recorded on the block. +- Fixed a Cursor MCP resource listing shrinking to a count in the transcript. The full URI/name/mime catalog goes out on the wire, but the paired local result recorded `Listed N MCP resource(s)` — and rebuilt history is serialized from that result, so one reload later the model knew it had seen N resources and could name none of them. The paired result now lists what the answer carried. +- Fixed the `pi_read` range translation padding the slice it asks for. `piReadPath` composed a plain `:N+K` selector, which the local `read` tool expands by one leading and three trailing context line — so a frame naming offset 5/limit 20 received lines 4-27. Ranged Pi reads now compose `:raw:N+K`; the wire result is an opaque output string, so the line-number gutter `raw` also drops carries nothing the contract needs. +- Fixed four Cursor exec frames answering with a result whose oneof was never set. In proto3 that is not an empty result — the server reads it as "the tool ran and produced nothing", indistinguishable from real success. `listMcpResourcesExecResult`, `readMcpResourceExecResult`, `recordScreenResult` and `computerUseResult` now send `ListMcpResourcesSuccess{resources: []}`, `ReadMcpResourceNotFound{uri}`, `RecordScreenFailure` and `ComputerUseError` respectively. +- The MCP resource frames now answer from the host instead of a fixed verdict. `CursorExecHandlers` gained `listMcpResources`/`readMcpResource`, so a host holding live MCP connections advertises them; the empty catalog and `not_found` above remain the answer when no handler is supplied. A handler that throws surfaces as `ListMcpResourcesError`/`ReadMcpResourceError` rather than collapsing into "none exist", which the model cannot retry. A read carrying `download_path` forwards it and answers with `ReadMcpResourceSuccess.download_path` and no content, which is what that mode means. +- Fixed Cursor `connect_scm` calls losing their repository and settling on a fabricated verdict. The target rides in the `ConnectScmArgs.target` oneof, so reading a flat `github` property always saw `undefined`; and the authoritative `success`/`error`/`rejected` result only arrives on the completion frame, so answering at the announcement persisted a fixed failure for every call — including the ones the server went on to accept. The block now opens on the start frame and settles from the completion's decoded result. +- Fixed interleaved Cursor tool calls corrupting each other. The stream decoder tracked a single "current" block and settled it on any `toolCallCompleted`, ignoring the envelope's `call_id`: a completion for one call closed whichever block happened to be open and paired it with the wrong result, and `start A, start B` orphaned A entirely so its own completion settled B while A was never paired — which strips the whole interaction from every rebuilt transcript. Open blocks are now retained per envelope `call_id`, and end-of-stream closes all of them rather than only the last. +- Fixed a Cursor `search_conversations` call leaving no transcript block. The frame is answered from a fixed verdict, so nothing downstream pairs a result for it, and an unpaired call takes its whole interaction out of every rebuilt transcript. +- Fixed a Cursor `read_mcp_resource` call leaving no transcript block. The frame runs locally — and in download mode writes a workspace file — but synthesized no tool call and paired no result, so the read was invisible in the UI and absent from every rebuilt history; a resource download could mutate the workspace with nothing on record. The frame now synthesizes a `read_mcp_resource` block (not `read`: it is a remote MCP operation, and the name drives rendering and prune semantics) and pairs a result on success, not-found and error alike. Frames answered without a handler still synthesize nothing, since nothing ran. +- Fixed a Cursor `list_mcp_resources` call leaving no transcript block. The model consumed the catalog, but the frame synthesized no tool call and paired no result — its streamed `ListMcpResourcesToolCall` announcement was equally unrecognized — so the listing was invisible in the UI and absent from every rebuilt history. Frames a handler answered now synthesize a `list_mcp_resources` block and pair a result derived from the same answer that went on the wire; frames answered from the fixed no-handler catalog still synthesize nothing, since nothing ran. +- Fixed an unavailable `pi_edit`/`pi_write` answering with the error variant. Both results model refusal and failure as separate oneof cases, and a denial reported as `error` reads as "the tool ran and broke" — inviting a retry of an operation that was never permitted. A frame whose tool is not granted, or whose handler produced nothing, now answers with `PiEditExecRejected`/`PiWriteExecRejected`; execution failures keep the error variant. +- Fixed a Cursor MCP approval probe actually running the tool. A modern `mcpArgs` frame carrying `smart_mode_approval_only` asks only whether a call would be permitted, not for the call itself. The decoder dropped the flag, so the frame ran a side-effecting MCP tool the user had not been asked about, then ran it again when the real call followed. The flag is now carried through and the probe is answered from the host's policy without executing: approved only for a definite allow, refused for a deny, for a mode that demands a prompt the frame cannot raise, and for a tool the session does not have. No transcript block is synthesized either, since nothing ran. +- Fixed the Cursor stream's end-of-transport cleanup erasing the arguments of every block still open. Blocks whose args arrive whole (todo, connect-SCM, MCP) never feed the streamed partial-JSON buffer, and reparsing an absent buffer yields `{}`, so a truncated or disconnected turn rebuilt those calls with no arguments at all. Only blocks that actually streamed their args are reparsed now. +- Fixed a Cursor stream dying mid-turn stranding the call it left open. `connect_scm` and native todo blocks are stamped resolved the moment they open, so the agent loop synthesizes no placeholder and only their completion frame pairs a result — a transport that closed first left the card animating and the call unpaired, which takes the whole interaction out of every rebuilt transcript. The terminal-error path now closes open blocks and pairs those server-owned calls with an interrupted result; the flush ran only on clean completion before, which is not the path a dying stream takes. Exec-settled MCP blocks are left alone, since the dispatch that ran them owns their result. +- Fixed the Pi exec frames displaying a different operation than the one they run. The provider synthesized its transcript block from a second, hand-rolled translation of the frame args, so `pi_read`'s `offset`/`limit` were shown as a whole-file read, `pi_grep`'s `literal` pattern as an unescaped regex, and `pi_find`'s path/glob join differed from the executed one. Both sides now share a single translation. +- Fixed the streamed `pi_*_tool_call` announcements that modern builds send alongside each exec frame being unrecognized. The exec channel already synthesizes those blocks when it runs the tool; the duplicate was avoided only because the decoder recognized none of the variants, which would have started double-rendering as soon as any one was added. +- Fixed `pi_bash` results reaching Cursor clipped with no truncation notice. Two truncation records exist locally: `read`/`grep` set `details.truncation`, which carries an explicit `truncated` flag, while `bash` sets `details.meta.truncation`, whose record has no such flag — its presence is the signal. `piTruncation` read only the first shape and required the flag, so every real Bash truncation was dropped and the server was told the clipped output was complete. Both shapes now translate, and an explicit `truncated: false` still suppresses the field. ## [17.1.8] - 2026-07-28 diff --git a/packages/ai/src/auth-storage.ts b/packages/ai/src/auth-storage.ts index 8098b7ec8..df74da8c2 100644 --- a/packages/ai/src/auth-storage.ts +++ b/packages/ai/src/auth-storage.ts @@ -65,6 +65,7 @@ import { } from "./usage/openai-codex-reset"; import { opencodeGoUsageProvider } from "./usage/opencode-go"; import { syntheticUsageProvider } from "./usage/synthetic"; +import { umansUsageProvider } from "./usage/umans"; import { xaiOauthUsageProvider } from "./usage/xai-oauth"; import { zaiRankingStrategy, zaiUsageProvider } from "./usage/zai"; @@ -660,6 +661,7 @@ const DEFAULT_USAGE_PROVIDERS: UsageProvider[] = [ ollamaCloudUsageProvider, claudeUsageProvider, zaiUsageProvider, + umansUsageProvider, opencodeGoUsageProvider, githubCopilotUsageProvider, cursorUsageProvider, diff --git a/packages/ai/src/error/classes.ts b/packages/ai/src/error/classes.ts index 09b14f7f3..694b6c37c 100644 --- a/packages/ai/src/error/classes.ts +++ b/packages/ai/src/error/classes.ts @@ -1,4 +1,5 @@ import type { CapturedHttpErrorResponse } from "../utils/http-inspector"; +import { AbortError } from "./abort"; /** Prefix on errors raised when an Anthropic SSE stream envelope is malformed. */ export const STREAM_ENVELOPE_ERROR_PREFIX = "Anthropic stream envelope error:"; @@ -68,6 +69,22 @@ export class OpenAIHttpError extends ProviderHttpError { } } +/** Non-2xx response from the Anthropic API. */ +const DEFAULT_ANTHROPIC_ERROR_BODY_READ_TIMEOUT_MS = 5_000; +const MAX_ANTHROPIC_ERROR_BODY_BYTES = 64 * 1024; +const ANTHROPIC_ERROR_BODY_TRUNCATION_MARKER = "\n[Response body truncated after 64 KiB]"; +let anthropicErrorBodyReadTimeoutMs = DEFAULT_ANTHROPIC_ERROR_BODY_READ_TIMEOUT_MS; + +/** Test-only control for the otherwise bounded Anthropic error-body drain. */ +export const __anthropicApiErrorForTesting = { + setBodyReadTimeoutMs(timeoutMs: number | undefined): void { + if (timeoutMs !== undefined && (!Number.isFinite(timeoutMs) || timeoutMs < 0)) { + throw new RangeError("Anthropic error-body timeout must be a non-negative finite number."); + } + anthropicErrorBodyReadTimeoutMs = timeoutMs ?? DEFAULT_ANTHROPIC_ERROR_BODY_READ_TIMEOUT_MS; + }, +}; + /** Non-2xx response from the Anthropic API. */ export class AnthropicApiError extends ProviderHttpError { declare readonly headers: Headers; @@ -79,9 +96,87 @@ export class AnthropicApiError extends ProviderHttpError { this.requestId = headers.get("request-id"); } - static async fromResponse(response: Response): Promise { - const body = await response.text().catch(() => ""); - const detail = body.trim() || "status code (no body)"; + static async fromResponse(response: Response, signal?: AbortSignal): Promise { + // Avoid getReader() throwing when another consumer already owns the body. + const reader = response.body?.locked ? undefined : response.body?.getReader(); + + if (!reader) { + if (signal?.aborted) throw new AbortError("Request was aborted."); + const detail = "status code (no body)"; + return new AnthropicApiError(response.status, `${response.status} ${detail}`, response.headers); + } + + let aborted = false; + let timedOut = false; + let readerCancelled = false; + const cancelReader = () => { + if (readerCancelled) return; + readerCancelled = true; + void reader.cancel().catch(() => {}); + }; + const onAbort = () => { + if (aborted) return; + aborted = true; + cancelReader(); + }; + if (signal?.aborted) onAbort(); + else signal?.addEventListener("abort", onAbort, { once: true }); + + const deadline = performance.now() + anthropicErrorBodyReadTimeoutMs; + let timeout: Timer | undefined; + timeout = setTimeout(() => { + timedOut = true; + cancelReader(); + }, anthropicErrorBodyReadTimeoutMs); + + let capturedBytes = 0; + let truncated = false; + const bodyChunks: string[] = []; + try { + const decoder = new TextDecoder(); + let cleanEof = false; + while (!aborted && !timedOut) { + if (performance.now() >= deadline) { + timedOut = true; + cancelReader(); + break; + } + + let result: { readonly done: boolean; readonly value?: Uint8Array }; + try { + result = await reader.read(); + } catch { + break; + } + if (aborted || timedOut) break; + if (result.done) { + cleanEof = true; + break; + } + + const chunk = result.value; + if (!chunk) break; + const bytesToCapture = Math.min(MAX_ANTHROPIC_ERROR_BODY_BYTES - capturedBytes, chunk.byteLength); + if (bytesToCapture > 0) { + bodyChunks.push(decoder.decode(chunk.subarray(0, bytesToCapture), { stream: true })); + capturedBytes += bytesToCapture; + } + if (bytesToCapture < chunk.byteLength) { + truncated = true; + cancelReader(); + break; + } + } + if (aborted || signal?.aborted) throw new AbortError("Request was aborted."); + if (cleanEof) bodyChunks.push(decoder.decode()); + if (truncated) bodyChunks.push(ANTHROPIC_ERROR_BODY_TRUNCATION_MARKER); + } finally { + if (timeout !== undefined) clearTimeout(timeout); + signal?.removeEventListener("abort", onAbort); + reader.releaseLock(); + } + + const detail = bodyChunks.join("").trim() || "status code (no body)"; return new AnthropicApiError(response.status, `${response.status} ${detail}`, response.headers); } } diff --git a/packages/ai/src/providers/anthropic-client.ts b/packages/ai/src/providers/anthropic-client.ts index 01825e823..ea2fe6d38 100644 --- a/packages/ai/src/providers/anthropic-client.ts +++ b/packages/ai/src/providers/anthropic-client.ts @@ -43,6 +43,12 @@ export interface AnthropicRequestOptions { timeout?: number; /** Per-request retry budget override. */ maxRetries?: number; + /** + * Maximum delay in milliseconds to wait for a server-directed retry. If the + * server's `retry-after` hint exceeds this value, the retry is declined and + * the original error is surfaced. Non-positive values disable the cap. Defaults to 60000. + */ + maxRetryDelayMs?: number; /** Per-request headers merged after client defaults. */ headers?: Record; } @@ -74,6 +80,12 @@ export interface AnthropicClientOptions { authToken?: string | null; baseURL?: string | null; maxRetries?: number; + /** + * Maximum delay in milliseconds to wait for a server-directed retry. If the + * server's `retry-after` hint exceeds this value, the retry is declined and + * the original error is surfaced. Non-positive values disable the cap. Defaults to 60000. + */ + maxRetryDelayMs?: number; /** Pre-response timeout in milliseconds. Defaults to 10 minutes. */ timeout?: number; defaultHeaders?: Record; @@ -97,7 +109,7 @@ function shouldRetryResponse(response: Response): boolean { } /** Server-suggested delay (`retry-after-ms`, then `retry-after` seconds or HTTP date). */ -export function retryDelayFromHeaders(headers: Headers | undefined): number | undefined { +export function retryDelayFromHeaders(headers: Pick | undefined): number | undefined { if (!headers) return undefined; const retryAfterMs = headers.get("retry-after-ms"); if (retryAfterMs) { @@ -216,6 +228,7 @@ export class AnthropicMessagesClient implements AnthropicMessagesClientLike { const callerSignal = options?.signal; const timeoutMs = options?.timeout ?? opts.timeout ?? DEFAULT_TIMEOUT_MS; const maxRetries = Math.max(0, options?.maxRetries ?? opts.maxRetries ?? DEFAULT_MAX_RETRIES); + const maxRetryDelayMs = options?.maxRetryDelayMs ?? opts.maxRetryDelayMs ?? 60_000; const url = `${opts.baseURL ?? "https://api.anthropic.com"}${path}`; const headers = this.#buildHeaders(options?.headers); const body = JSON.stringify(params); @@ -239,11 +252,20 @@ export class AnthropicMessagesClient implements AnthropicMessagesClientLike { if (response.ok) return response; if (attempt < maxRetries && shouldRetryResponse(response)) { + // Bound the server-directed wait: an over-cap `retry-after` declines + // the retry and surfaces the original error (status/body/headers + // intact) so higher-level recovery can run. A non-positive cap disables enforcement. + // Checked before draining the body so `fromResponse` can still read it. + const headerDelayMs = retryDelayFromHeaders(response.headers); + if (headerDelayMs !== undefined && maxRetryDelayMs > 0 && headerDelayMs > maxRetryDelayMs) { + throw await AIError.AnthropicApiError.fromResponse(response, callerSignal); + } await response.body?.cancel().catch(() => {}); await this.#backoff(attempt, response.headers, callerSignal); continue; } - throw await AIError.AnthropicApiError.fromResponse(response); + + throw await AIError.AnthropicApiError.fromResponse(response, callerSignal); } } diff --git a/packages/ai/src/providers/anthropic.ts b/packages/ai/src/providers/anthropic.ts index c8db71dc1..12c9c699f 100644 --- a/packages/ai/src/providers/anthropic.ts +++ b/packages/ai/src/providers/anthropic.ts @@ -60,19 +60,18 @@ import { isFoundryEnabled } from "../utils/foundry"; import { finalizeErrorMessage, type RawHttpRequestDump } from "../utils/http-inspector"; import { getStreamFirstEventTimeoutMs, getStreamIdleTimeoutMs, iterateWithIdleTimeout } from "../utils/idle-iterator"; import { notifyProviderResponse } from "../utils/provider-response"; +import { getHeadersFromError, getRetryAfterMsFromHeaders } from "../utils/retry-after"; import { COMBINATOR_KEYS, NO_STRICT, toolWireSchema } from "../utils/schema"; import { spillToDescription } from "../utils/schema/spill"; import { createSdkStreamRequestOptions } from "../utils/sdk-stream-timeout"; import { notifyRawSseEvent } from "../utils/sse-debug"; import { isForcedToolChoice } from "../utils/tool-choice"; import { - AnthropicApiError, AnthropicConnectionTimeoutError, type AnthropicFetchOptions, AnthropicMessagesClient, type AnthropicMessagesClientLike, calculateAnthropicRetryDelayMs, - retryDelayFromHeaders, } from "./anthropic-client"; import { type ToolInputSchema as AnthropicToolInputSchema, @@ -450,6 +449,20 @@ export function clearAnthropicFastModeFallback( (value as AnthropicProviderSessionState).fastModeDisabled = false; } } +/** + * Whether the direct Anthropic model's endpoint-scoped fast-mode fallback is + * currently active. Reading the map directly is intentional: inspection must + * not materialize a state entry for a model that has never streamed. + */ +export function isAnthropicFastModeFallbackDisabled( + providerSessionState: Map | undefined, + model: Model, +): boolean { + if (!providerSessionState || model.provider !== "anthropic" || model.api !== "anthropic-messages") return false; + const baseUrl = resolveAnthropicBaseUrl(model as Model<"anthropic-messages">) ?? "https://api.anthropic.com"; + const key = anthropicProviderSessionStateKey(baseUrl, model.id); + return (providerSessionState.get(key) as AnthropicProviderSessionState | undefined)?.fastModeDisabled ?? false; +} function hasStrictAnthropicTools(params: MessageCreateParamsStreaming): boolean { return params.tools?.some(tool => tool.strict === true) ?? false; @@ -1155,6 +1168,7 @@ export type AnthropicClientOptionsArgs = { thinkingDisplay?: AnthropicThinkingDisplay; disableStrictTools?: boolean; fetch?: FetchImpl; + maxRetryDelayMs?: number; claudeCodeSessionId?: string; }; @@ -1164,6 +1178,7 @@ export type AnthropicClientOptionsResult = { authToken?: string | null; baseURL?: string; maxRetries: number; + maxRetryDelayMs?: number; defaultHeaders: Record; fetch?: FetchImpl; fetchOptions?: AnthropicFetchOptions; @@ -1952,6 +1967,7 @@ const streamAnthropicOnce = ( thinkingEnabled: options?.thinkingEnabled, thinkingDisplay: options?.thinkingDisplay, fetch: options?.fetch, + maxRetryDelayMs: options?.maxRetryDelayMs, claudeCodeSessionId: options?.sessionId ?? extractClaudeMetadataSessionId(options?.metadata?.user_id), disableStrictTools, }); @@ -2699,10 +2715,14 @@ const streamAnthropicOnce = ( // Honor the server's retry hint (`retry-after-ms`/`retry-after`) on // 429/529-style failures: retrying sooner than the server asked is a // guaranteed failure that just burns the retry budget. - const headerDelayMs = - streamFailure instanceof Error && streamFailure instanceof AnthropicApiError - ? retryDelayFromHeaders(streamFailure.headers) - : undefined; + const headerDelayMs = getRetryAfterMsFromHeaders(getHeadersFromError(streamFailure)); + // Bound the server-directed wait so a multi-hour `retry-after` cannot + // park the provider stream before higher-level recovery runs. A non-positive cap + // disables the bound; an over-cap hint surfaces the original error immediately. + const maxRetryDelayMs = options?.maxRetryDelayMs ?? 60_000; + if (headerDelayMs !== undefined && maxRetryDelayMs > 0 && headerDelayMs > maxRetryDelayMs) { + throw streamFailure; + } const delayMs = headerDelayMs !== undefined ? Math.max(headerDelayMs, backoffDelayMs) : backoffDelayMs; if (options?.providerRetryWait) { await options.providerRetryWait(delayMs, options.signal); @@ -2847,6 +2867,7 @@ export function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): A thinkingEnabled = false, thinkingDisplay, isOAuth, + maxRetryDelayMs, claudeCodeSessionId, disableStrictTools: disableStrictToolsOverride, } = args; @@ -2917,6 +2938,7 @@ export function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): A authToken: copilotApiKey, baseURL: baseUrl, maxRetries: 5, + maxRetryDelayMs, defaultHeaders, fetch: cchFetch, fetchOptions, @@ -2964,6 +2986,7 @@ export function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): A authToken: null, baseURL: baseUrl, maxRetries: 5, + maxRetryDelayMs, defaultHeaders, fetch: cchFetch, fetchOptions, @@ -2982,6 +3005,7 @@ export function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): A authToken: null, baseURL: baseUrl, maxRetries: 5, + maxRetryDelayMs, defaultHeaders, fetch: cchFetch, fetchOptions, @@ -3004,6 +3028,7 @@ export function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): A authToken: oauthToken ? apiKey : undefined, baseURL: baseUrl, maxRetries: 5, + maxRetryDelayMs, defaultHeaders, fetch: cchFetch, fetchOptions, diff --git a/packages/ai/src/providers/cursor-pi-args.ts b/packages/ai/src/providers/cursor-pi-args.ts new file mode 100644 index 000000000..d7b759f5a --- /dev/null +++ b/packages/ai/src/providers/cursor-pi-args.ts @@ -0,0 +1,135 @@ +/** + * Translate a Pi frame's args into the local tool kwargs that run it. + * + * Shared deliberately by three consumers: the provider synthesizes a display + * block from these, the coding-agent bridge executes with them, and the legacy + * pi shim performs the identical translation for the old wire. Separate + * hand-rolled copies drift, and the drift is invisible — the transcript shows + * one operation while a different one runs. + * + * Kept apart from `cursor/exec-modern.ts` on purpose: these are pure + * string/path functions with no protobuf coupling, while that module pulls in + * `@bufbuild/protobuf` and the generated `agent_pb` graph. The legacy shim is + * compiled into the bundled virtual module registry, so importing it from a + * nested path would drag the whole exec implementation in with it — and + * `./providers/*` is a single-segment wildcard export that cannot serve a + * nested specifier under bunfs (issue #3442). + * + * Every `optional int32` here is presence-sensitive: `0` is a supplied value, + * not "unset", so it must never be folded into a default. + */ + +import * as path from "node:path"; + +/** + * A `pi_read` range composed onto the path as `read`'s inline `:raw:N+K` + * selector. + * + * `read` exposes no range kwargs, so an uncomposed range reads the whole file. + * `offset` is a 1-indexed start clamped like the reference's + * `Math.max(0, offset - 1)` over 0-indexed lines; `limit` is a line count. + * `null` marks a present `limit: 0` — zero lines, which no selector expresses + * and which must not degrade into a whole-file read. + * + * The range is `raw` because a plain `:N+K` deliberately pads with one leading + * and three trailing context lines: helpful for a human reading a snippet, + * wrong for a caller that asked for exactly `limit` lines from `offset`. The + * wire result is an opaque `output` string, so the hashline and line-number + * gutter that `raw` also drops carry nothing the frame's contract needs. + * A range-free read keeps the ordinary form — whole-file reads want them. + */ +export function piReadPath(readPath: string, offset?: number, limit?: number): string | null { + if (limit !== undefined && Math.floor(limit) <= 0) return null; + const start = offset !== undefined ? Math.max(1, Math.floor(offset)) : undefined; + const count = limit !== undefined ? Math.floor(limit) : undefined; + if (start === undefined && count === undefined) return readPath; + if (start === undefined) return `${readPath}:raw:1+${count}`; + return count === undefined ? `${readPath}:raw:${start}-` : `${readPath}:raw:${start}+${count}`; +} + +/** + * The same range as {@link piReadPath}, rendered for a transcript block rather + * than for execution. + * + * Differs only at `limit: 0`, where `piReadPath` returns `null` because no + * selector reads zero lines and the frame is answered with empty output + * directly. The block still has to say so: falling back to the bare path there + * would record a whole-file read whose result is empty, which is the widest + * possible gap between what a rebuilt transcript shows and what happened. + * `+0` is never executed — it exists to be read. + */ +export function piReadDisplayPath(readPath: string, offset?: number, limit?: number): string { + const composed = piReadPath(readPath, offset, limit); + if (composed !== null) return composed; + const start = offset !== undefined ? Math.max(1, Math.floor(offset)) : 1; + return `${readPath}:raw:${start}+0`; +} + +/** + * A legacy `grep` frame's pagination `offset` as the local tool's file `skip`. + * + * `grep` paginates by file and reports "use skip=N for the next page" in that + * same unit, so the offset maps across directly. A present `0` means "start at + * the beginning", which is the unskipped search rather than a skip of zero. + * + * Shared because both the executing bridge and the provider's transcript + * synthesis need it: a block showing an unskipped search beside a result from + * a later file window misreports what was searched. + */ +export function piGrepSkip(offset?: number): number | undefined { + return offset !== undefined && offset > 0 ? Math.floor(offset) : undefined; +} + +/** + * Join a Pi frame's optional `path` with the `glob`/`pattern` it scopes. + * + * The local `grep`/`glob` tools take one combined path spec. An absolute + * pattern ignores the path, and an absent or `.` path leaves the pattern + * standing alone rather than building a `./`- or `//`-prefixed spec. + * + * Uses `node:path` rather than string surgery so Windows absolutes (`C:\…`, + * UNC) are recognised and separators stay normalized. + */ +export function piJoinPath(basePath: string | undefined, pattern: string): string { + if (path.isAbsolute(pattern)) return pattern; + if (!basePath || basePath === ".") return pattern; + return path.join(basePath, pattern); +} + +/** + * The path a `pi_ls` frame lists. + * + * The frame's `limit` is deliberately NOT mapped. It caps directory *entries* + * (the reference does a flat `readdir` and slices the entry array), while the + * local `read` tool renders a depth-2 tree with per-directory caps and elision + * summaries and applies a selector as a *rendered line* slice. Nested rows, + * headers and "N more" lines all count toward that slice, so `:1+K` would cap + * a different unit while looking honored — worse than leaving it unset, which + * at least reports the local listing's own truncation faithfully. + */ +export function piLsPath(basePath: string | undefined): string { + return basePath || "."; +} + +/** Escape a literal string so the regex-only local `grep` tool matches it verbatim. */ +export function piEscapeRegexLiteral(value: string): string { + return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +/** Clamp a present `optional int32` result cap the way the reference does; `undefined` stays unset. */ +export function piLimit(limit: number | undefined): number | undefined { + return limit === undefined ? undefined : Math.max(1, Math.floor(limit)); +} + +/** + * A `pi_bash` frame's timeout as the local `bash` tool's kwarg. + * + * Presence-sensitive like every other `optional int32` here, and unusually + * load-bearing: `bash` documents `timeout: 0` as "disables the command + * deadline", so folding a supplied `0` into `undefined` applies the 300s + * default and kills exactly the long-running command that asked not to be. + * Negative values have no local meaning and fall back to the default. + */ +export function piTimeout(timeout: number | undefined): number | undefined { + return timeout !== undefined && timeout >= 0 ? timeout : undefined; +} diff --git a/packages/ai/src/providers/cursor.ts b/packages/ai/src/providers/cursor.ts index 35232ce74..754c892d5 100644 --- a/packages/ai/src/providers/cursor.ts +++ b/packages/ai/src/providers/cursor.ts @@ -10,11 +10,18 @@ import { AgentRunRequestSchema, type AgentServerMessage, AgentServerMessageSchema, + AgentStoreConflictErrorSchema, + AgentStoreConflictResultSchema, AssistantMessageSchema, BackgroundShellSpawnResultSchema, + CanvasDiagnosticsErrorSchema, + CanvasDiagnosticsResultSchema, ClientHeartbeatSchema, + ComputerUseErrorSchema, ComputerUseResultSchema, ConversationActionSchema, + ConversationSearchErrorSchema, + ConversationSearchResultSchema, type ConversationStateStructure, ConversationStateStructureSchema, ConversationStepSchema, @@ -31,9 +38,14 @@ import { type ExecClientMessage, ExecClientMessageSchema, ExecClientStreamCloseSchema, + ExecClientThrowSchema, type ExecServerMessage, FetchErrorSchema, FetchResultSchema, + ForceBackgroundShellResultSchema, + ForceBackgroundShellStatus, + ForceBackgroundSubagentResultSchema, + ForceBackgroundSubagentStatus, GetBlobResultSchema, GrepContentMatchSchema, GrepContentResultSchema, @@ -49,7 +61,11 @@ import { GrepUnionResultSchema, KvClientMessageSchema, type KvServerMessage, + ListMcpResourcesErrorSchema, + type ListMcpResourcesExecResult, + ListMcpResourcesExecResult_McpResourceSchema, ListMcpResourcesExecResultSchema, + ListMcpResourcesSuccessSchema, type LsDirectoryTreeNode, type LsDirectoryTreeNode_File, LsDirectoryTreeNode_FileSchema, @@ -58,8 +74,11 @@ import { LsRejectedSchema, LsResultSchema, LsSuccessSchema, + McpAllowlistPrecheckResultSchema, + McpApprovedSchema, McpErrorSchema, McpImageContentSchema, + McpRejectedSchema, McpResultSchema, McpSuccessSchema, McpTextContentSchema, @@ -68,10 +87,15 @@ import { McpToolResultContentItemSchema, ModelDetailsSchema, ReadErrorSchema, + ReadMcpResourceErrorSchema, + type ReadMcpResourceExecResult, ReadMcpResourceExecResultSchema, + ReadMcpResourceNotFoundSchema, + ReadMcpResourceSuccessSchema, ReadRejectedSchema, ReadResultSchema, ReadSuccessSchema, + RecordScreenFailureSchema, RecordScreenResultSchema, RequestContextResultSchema, RequestContextSchema, @@ -81,6 +105,7 @@ import { SelectedContextSchema, SelectedImageSchema, SetBlobResultSchema, + ShellAllowlistPrecheckResultSchema, type ShellArgs, ShellFailureSchema, ShellRejectedSchema, @@ -93,8 +118,15 @@ import { ShellStreamStderrSchema, ShellStreamStdoutSchema, ShellSuccessSchema, + SmartModeClassifierErrorSchema, + SmartModeClassifierResultSchema, + SubagentAwaitNotFoundSchema, + SubagentAwaitResultSchema, + SubagentErrorSchema, + SubagentResultSchema, UserMessageActionSchema, UserMessageSchema, + WebFetchAllowlistPrecheckResultSchema, WriteErrorSchema, WriteRejectedSchema, WriteResultSchema, @@ -142,6 +174,7 @@ import { kCursorExecResolved, kStreamingBlockIndex, kStreamingBlockKind, + kStreamingEnvelopeId, kStreamingLastParseLen, kStreamingPartialJson, } from "../utils/block-symbols"; @@ -150,12 +183,47 @@ import { AssistantMessageEventStream } from "../utils/event-stream"; import { connectProxiedSocket, getProxyForProvider, shouldBypassProxy } from "../utils/proxy"; import { createRequestDebugSession, isRequestDebugEnabled, type RequestDebugResponseLog } from "../utils/request-debug"; import { toolWireSchema } from "../utils/schema/wire"; +import { + buildMcpStateResult, + buildNeutralHookResult, + buildPiBashError, + buildPiBashResult, + buildPiEditError, + buildPiEditRejected, + buildPiEditResult, + buildPiFindError, + buildPiFindResult, + buildPiGrepError, + buildPiGrepResult, + buildPiLsError, + buildPiLsResult, + buildPiReadError, + buildPiReadResult, + buildPiWriteError, + buildPiWriteRejected, + buildPiWriteResult, + piEscapeRegexLiteral, + piGrepSkip, + piJoinPath, + piLimit, + piLsPath, + piReadDisplayPath, + piTimeout, +} from "./cursor/exec-modern"; export const CURSOR_API_URL = "https://api2.cursor.sh"; -export const CURSOR_CLIENT_VERSION = "cli-2026.01.09-231024f"; +export const CURSOR_CLIENT_VERSION = "cli-2026.07.23-e383d2b"; const CURSOR_PROXY_TUNNEL_TIMEOUT_MS = 30_000; +/** + * Text for a recognised frame this client answers with its own typed error + * variant. Phrased as a client capability statement, not a tool failure: the + * model reads it and should route around the capability, not retry the call. + */ +const NOT_IMPLEMENTED_SUFFIX = "not implemented by this client"; +const NOT_IMPLEMENTED = `Not implemented by this client`; + const conversationStateCache = new Map(); const conversationBlobStores = new Map>(); @@ -421,6 +489,10 @@ export const streamCursor: StreamFunction<"cursor-agent"> = ( let h2Settled = false; let sawTurnEnded = false; let endStreamError: Error | null = null; + // Reachable from the catch: a stream that dies mid-turn must still close + // and pair the blocks it left open, and `state` itself is scoped to the + // try below. + let openBlockState: BlockState | undefined; const settleH2 = (error?: unknown): void => { if (h2Settled) return; h2Settled = true; @@ -520,6 +592,7 @@ export const streamCursor: StreamFunction<"cursor-agent"> = ( get currentToolCall() { return currentToolCall; }, + openToolCalls: new Map(), resolvedMcpToolCallIds, get firstTokenTime() { return firstTokenTime; @@ -539,6 +612,7 @@ export const streamCursor: StreamFunction<"cursor-agent"> = ( onTodoSnapshot: options?.execHandlers?.todoSync?.bind(options.execHandlers), onToolResult: options?.onToolResult, }; + openBlockState = state; const onConversationCheckpoint = (checkpoint: ConversationStateStructure) => { conversationStateCache.set(conversationId, checkpoint); @@ -677,17 +751,7 @@ export const streamCursor: StreamFunction<"cursor-agent"> = ( endCurrentTextBlock(output, stream, state); endCurrentThinkingBlock(output, stream, state); - if (state.currentToolCall) { - const idx = output.content.indexOf(state.currentToolCall); - state.currentToolCall.arguments = parseStreamingJson(state.currentToolCall[kStreamingPartialJson]); - clearStreamingPartialJson(state.currentToolCall); - stream.push({ - type: "toolcall_end", - contentIndex: idx, - toolCall: state.currentToolCall, - partial: output, - }); - } + flushOpenToolCalls(output, stream, state); calculateCost(model, output.usage); @@ -708,6 +772,19 @@ export const streamCursor: StreamFunction<"cursor-agent"> = ( // (handlers have no cancellation contract and must not delay the // terminal error the user asked for). await drainInFlightDispatches(); + // A stream that dies mid-turn leaves blocks open, and this is the path + // it takes: `settleH2` rejects when the transport closes without + // `turnEnded`, so the success-path flush above never runs. Closing + // them here settles their live cards and pairs the server-owned calls + // (`connect_scm`, native todo) that nothing else answers — an + // unpaired call is stripped from every rebuilt transcript. + // Undefined only when the failure predates the state's construction, + // in which case no block was ever opened. + if (openBlockState) { + endCurrentTextBlock(output, stream, openBlockState); + endCurrentThinkingBlock(output, stream, openBlockState); + flushOpenToolCalls(output, stream, openBlockState); + } const result = await AIError.finalize(error, { api: model.api, signal: options?.signal }); output.stopReason = result.stopReason; output.errorStatus = result.status; @@ -736,7 +813,8 @@ export type ToolCallState = ToolCall & { [kStreamingBlockIndex]: number; [kStreamingPartialJson]?: string; [kStreamingLastParseLen]?: number; - [kStreamingBlockKind]: "mcp" | "todo" | "cursor-exec"; + [kStreamingBlockKind]: "mcp" | "todo" | "cursor-exec" | "connect-scm"; + [kStreamingEnvelopeId]?: string; [kCursorExecResolved]?: true; }; @@ -744,6 +822,17 @@ export interface BlockState { currentTextBlock: (TextContent & { [kStreamingBlockIndex]: number }) | null; currentThinkingBlock: (ThinkingContent & { [kStreamingBlockIndex]: number }) | null; currentToolCall: ToolCallState | null; + /** + * Open streamed tool-call blocks, keyed by the interaction envelope's + * `call_id`. + * + * Cursor interleaves calls: two `toolCallStarted` frames can arrive before + * either completes. A single "current" slot would let the second overwrite + * the first, orphaning a block that nothing then settles. Every keyed block + * stays reachable until its own completion, and `currentToolCall` remains + * only as the fallback for frames that carry no `call_id`. + */ + openToolCalls: Map; /** MCP call IDs synthesized from exec frames before their redundant streamed block arrives. */ resolvedMcpToolCallIds: Set; firstTokenTime: number | undefined; @@ -1195,6 +1284,14 @@ async function handleExecServerMessage( } if (!execCase) { + // A frame carrying a oneof number this build's `agent.proto` does not + // model at all: protobuf decodes it into unknown fields and leaves + // `message.case` unset, so the client cannot even name what was asked. + // Returning silently strands the exec id — the server waits on a reply + // that never comes. Distinct from the `default:` branch below, which + // names a frame it recognises but cannot serve. + log("warn", "unknownExecVariant", { id: execMsg.id, execId: execMsg.execId }); + sendExecClientThrow(h2Request, execMsg, "Unknown exec message variant", "unknown_exec_variant"); return; } @@ -1202,12 +1299,22 @@ async function handleExecServerMessage( case "readArgs": { const args = execMsg.message.value; if (!args.toolCallId) args.toolCallId = crypto.randomUUID(); - synthesizeCursorExecToolCall(output, stream, state, args.toolCallId, "read", { path: args.path }); + // The same composed selector the bridge executes: showing a bare path + // for a ranged read makes the returned slice look like the whole + // file in every rebuilt transcript. + synthesizeCursorExecToolCall(output, stream, state, args.toolCallId, "read", { + path: piReadDisplayPath(args.path, args.offset, args.limit), + }); const { execResult } = await resolveExecHandler( args, execHandlers?.read?.bind(execHandlers), onToolResult, - toolResult => buildReadResultFromToolResult(args.path, toolResult), + toolResult => + buildReadResultFromToolResult( + args.path, + toolResult, + args.offset !== undefined || args.limit !== undefined, + ), reason => buildReadRejectedResult(args.path, reason), error => buildReadErrorResult(args.path, error), { toolCallId: args.toolCallId, toolName: "read" }, @@ -1256,6 +1363,7 @@ async function handleExecServerMessage( pattern: args.pattern, path: searchPath, case: args.caseInsensitive === true ? false : undefined, + skip: piGrepSkip(args.offset), }); const { execResult } = await resolveExecHandler( args, @@ -1418,6 +1526,36 @@ async function handleExecServerMessage( case "mcpArgs": { const args = execMsg.message.value; const mcpCall = decodeMcpCall(args); + // An approval probe, not an invocation: the frame asks whether the + // call would be permitted. Running the tool to find out fires a side + // effect the user has not been asked about, and fires it again when + // the real frame follows — so this must answer without executing. + // + // The host resolves it against the same policy the wrapper applies at + // execution time. Only a definite allow is approved: a pending prompt + // cannot be asked through this frame, and answering yes on its behalf + // would pre-authorize a call the user never saw. Without a handler + // there is nothing to decide with, so it is refused. Either way no + // block is synthesized — nothing ran. + if (mcpCall.approvalOnly) { + const approved = (await execHandlers?.mcpApprovalPreflight?.(mcpCall)) === true; + sendExecClientMessage( + h2Request, + execMsg, + "mcpResult", + create(McpResultSchema, { + result: approved + ? { case: "approved", value: create(McpApprovedSchema, {}) } + : { + case: "rejected", + value: create(McpRejectedSchema, { + reason: `Tool "${mcpCall.toolName || mcpCall.name}" is not approved to run without asking.`, + }), + }, + }), + ); + return; + } if (execHandlers?.mcp) { const existingBlock = output.content.find( block => block.type === "toolCall" && block.id === mcpCall.toolCallId, @@ -1449,54 +1587,571 @@ async function handleExecServerMessage( return; } case "listMcpResourcesExecArgs": { - const execResult = create(ListMcpResourcesExecResultSchema, {}); + // A host holding live MCP connections answers from them; without a + // handler the honest answer is an explicit empty success. An + // unset-oneof result would read as "the call produced nothing". + const args = execMsg.message.value; + let execResult: ListMcpResourcesExecResult; + // The model consumes this catalog, so it needs a block and a paired + // result or the listing is invisible in the UI and gone from every + // rebuilt history. Only synthesized when a handler exists: without + // one the frame is a fixed empty answer that executed nothing. + const toolCallId = execHandlers?.listMcpResources ? crypto.randomUUID() : undefined; + if (toolCallId) { + synthesizeCursorExecToolCall(output, stream, state, toolCallId, "list_mcp_resources", { + server: args.server, + }); + } + try { + const resources = (await execHandlers?.listMcpResources?.({ server: args.server })) ?? []; + execResult = create(ListMcpResourcesExecResultSchema, { + result: { + case: "success", + value: create(ListMcpResourcesSuccessSchema, { + resources: resources.map(resource => + create(ListMcpResourcesExecResult_McpResourceSchema, { + uri: resource.uri, + name: resource.name, + description: resource.description, + mimeType: resource.mimeType, + server: resource.server, + }), + ), + }), + }, + }); + } catch (error) { + execResult = create(ListMcpResourcesExecResultSchema, { + result: { + case: "error", + value: create(ListMcpResourcesErrorSchema, { + error: error instanceof Error ? error.message : String(error), + }), + }, + }); + } + if (toolCallId) { + // Derived from the answer that goes on the wire, so the block can + // never disagree with what the model was told. + const settled = execResult.result; + const text = + settled.case === "success" + ? formatListedMcpResources(settled.value.resources) + : settled.case === "error" + ? settled.value.error || "Failed to list MCP resources" + : (settled.value?.reason ?? "Failed to list MCP resources"); + await pairSynthesizedExecResult( + state, + onToolResult, + toolCallId, + "list_mcp_resources", + text, + settled.case !== "success", + ); + } sendExecClientMessage(h2Request, execMsg, "listMcpResourcesExecResult", execResult); return; } case "readMcpResourceExecArgs": { - const execResult = create(ReadMcpResourceExecResultSchema, {}); + const args = execMsg.message.value; + let execResult: ReadMcpResourceExecResult; + // The read runs locally, and in download mode it writes a workspace + // file — an operation with no transcript block is invisible in the UI + // and absent from every rebuilt history. Only synthesized when a + // handler exists: without one the frame is a fixed `not_found` that + // executed nothing, and a block would claim work that never happened. + const toolCallId = execHandlers?.readMcpResource ? crypto.randomUUID() : undefined; + if (toolCallId) { + synthesizeCursorExecToolCall(output, stream, state, toolCallId, "read_mcp_resource", { + server: args.server, + uri: args.uri, + download_path: args.downloadPath, + }); + } + try { + // `null` is the handler's "no such server or uri", which is exactly + // `not_found`; a throw is a real failure and must not masquerade as + // a missing resource. + const content = await execHandlers?.readMcpResource?.({ + server: args.server, + uri: args.uri, + downloadPath: args.downloadPath, + }); + execResult = content + ? create(ReadMcpResourceExecResultSchema, { + result: { + case: "success", + value: create(ReadMcpResourceSuccessSchema, { + uri: content.uri, + name: content.name, + description: content.description, + mimeType: content.mimeType, + downloadPath: content.downloadPath, + // A download returns no content to the model: the file is + // on disk and the path is the answer. Otherwise the wire's + // content oneof carries one of the two, text winning when + // a host supplies both. + content: + content.downloadPath !== undefined + ? { case: undefined } + : content.text !== undefined + ? { case: "text", value: content.text } + : content.blob !== undefined + ? { case: "blob", value: content.blob } + : { case: undefined }, + }), + }, + }) + : create(ReadMcpResourceExecResultSchema, { + result: { case: "notFound", value: create(ReadMcpResourceNotFoundSchema, { uri: args.uri }) }, + }); + } catch (error) { + execResult = create(ReadMcpResourceExecResultSchema, { + result: { + case: "error", + value: create(ReadMcpResourceErrorSchema, { + uri: args.uri, + error: error instanceof Error ? error.message : String(error), + }), + }, + }); + } + if (toolCallId) { + // Derived from the answer that actually goes on the wire, so no exit + // can drift out of sync with what the model was told. + const settled = execResult.result; + let text: string; + switch (settled.case) { + case "success": + text = settled.value.downloadPath + ? `Downloaded ${args.uri} to ${settled.value.downloadPath}` + : `Read ${args.uri}`; + break; + case "notFound": + text = `No such resource: ${args.uri}`; + break; + // The wire union carries a refusal variant this client never + // builds today — the handler answers content or `null`. Handled + // anyway so the switch stays total: it holds a `reason`, not an + // `error`, so a collapsed default would have read `undefined`. + case "rejected": + text = `Refused: ${settled.value.reason}`; + break; + default: + text = settled.value?.error ?? `Failed to read ${args.uri}`; + break; + } + await pairSynthesizedExecResult( + state, + onToolResult, + toolCallId, + "read_mcp_resource", + text, + settled.case !== "success", + ); + } sendExecClientMessage(h2Request, execMsg, "readMcpResourceExecResult", execResult); return; } case "recordScreenArgs": { - const execResult = create(RecordScreenResultSchema, {}); + const execResult = create(RecordScreenResultSchema, { + result: { case: "failure", value: create(RecordScreenFailureSchema, { error: NOT_IMPLEMENTED }) }, + }); sendExecClientMessage(h2Request, execMsg, "recordScreenResult", execResult); return; } case "computerUseArgs": { - const execResult = create(ComputerUseResultSchema, {}); + const execResult = create(ComputerUseResultSchema, { + result: { case: "error", value: create(ComputerUseErrorSchema, { error: NOT_IMPLEMENTED }) }, + }); sendExecClientMessage(h2Request, execMsg, "computerUseResult", execResult); return; } + case "piReadArgs": { + const args = execMsg.message.value; + const toolCallId = crypto.randomUUID(); + // The displayed block must show the operation that actually runs: the + // bridge composes the same range selector onto the path. + synthesizeCursorExecToolCall(output, stream, state, toolCallId, "read", { + path: piReadDisplayPath(args.path, args.offset, args.limit), + }); + const { execResult } = await resolveExecHandler( + { args, toolCallId }, + execHandlers?.piRead?.bind(execHandlers), + onToolResult, + buildPiReadResult, + buildPiReadError, + buildPiReadError, + { toolCallId, toolName: "read" }, + ); + sendExecClientMessage(h2Request, execMsg, "piReadResult", execResult); + return; + } + case "piBashArgs": { + const args = execMsg.message.value; + const toolCallId = crypto.randomUUID(); + synthesizeCursorExecToolCall(output, stream, state, toolCallId, "bash", { + command: args.command, + timeout: piTimeout(args.timeout), + }); + const { execResult } = await resolveExecHandler( + { args, toolCallId }, + execHandlers?.piBash?.bind(execHandlers), + onToolResult, + buildPiBashResult, + buildPiBashError, + buildPiBashError, + { toolCallId, toolName: "bash" }, + ); + sendExecClientMessage(h2Request, execMsg, "piBashResult", execResult); + return; + } + case "piEditArgs": { + const args = execMsg.message.value; + const toolCallId = crypto.randomUUID(); + // `PiEditReplacement` is the local `edit` tool's replace mode verbatim: + // snake_case `old_text`/`new_text` entries against one path. + synthesizeCursorExecToolCall(output, stream, state, toolCallId, "edit", { + path: args.path, + edits: args.edits.map(edit => ({ old_text: edit.oldText, new_text: edit.newText })), + }); + const { execResult } = await resolveExecHandler( + { args, toolCallId }, + execHandlers?.piEdit?.bind(execHandlers), + onToolResult, + buildPiEditResult, + buildPiEditRejected, + buildPiEditError, + { toolCallId, toolName: "edit" }, + ); + sendExecClientMessage(h2Request, execMsg, "piEditResult", execResult); + return; + } + case "piWriteArgs": { + const args = execMsg.message.value; + const toolCallId = crypto.randomUUID(); + synthesizeCursorExecToolCall(output, stream, state, toolCallId, "write", { + path: args.path, + content: args.content, + }); + const { execResult } = await resolveExecHandler( + { args, toolCallId }, + execHandlers?.piWrite?.bind(execHandlers), + onToolResult, + buildPiWriteResult, + buildPiWriteRejected, + buildPiWriteError, + { toolCallId, toolName: "write" }, + ); + sendExecClientMessage(h2Request, execMsg, "piWriteResult", execResult); + return; + } + case "piGrepArgs": { + const args = execMsg.message.value; + const toolCallId = crypto.randomUUID(); + synthesizeCursorExecToolCall(output, stream, state, toolCallId, "grep", { + pattern: args.literal === true ? piEscapeRegexLiteral(args.pattern) : args.pattern, + path: args.glob ? piJoinPath(args.path, args.glob) : args.path || ".", + case: args.ignoreCase === true ? false : undefined, + // Neither field exists in the model-facing `grep` schema — the bridge + // serves them by building a scoped tool instead. Recorded anyway, for + // the same reason `pi_read` renders its range into the displayed path: + // a capped or context-widened search is otherwise replayed as an + // ordinary grep sitting next to output no ordinary grep produces. + context: args.context, + limit: piLimit(args.limit), + }); + const { execResult } = await resolveExecHandler( + { args, toolCallId }, + execHandlers?.piGrep?.bind(execHandlers), + onToolResult, + buildPiGrepResult, + buildPiGrepError, + buildPiGrepError, + { toolCallId, toolName: "grep" }, + ); + sendExecClientMessage(h2Request, execMsg, "piGrepResult", execResult); + return; + } + case "piFindArgs": { + const args = execMsg.message.value; + const toolCallId = crypto.randomUUID(); + synthesizeCursorExecToolCall(output, stream, state, toolCallId, "glob", { + path: piJoinPath(args.path, args.pattern), + limit: piLimit(args.limit), + }); + const { execResult } = await resolveExecHandler( + { args, toolCallId }, + execHandlers?.piFind?.bind(execHandlers), + onToolResult, + buildPiFindResult, + buildPiFindError, + buildPiFindError, + { toolCallId, toolName: "glob" }, + ); + sendExecClientMessage(h2Request, execMsg, "piFindResult", execResult); + return; + } + case "piLsArgs": { + const args = execMsg.message.value; + const toolCallId = crypto.randomUUID(); + // Same mapping as the legacy `lsArgs` frame: the local `read` tool lists + // directories, so the synthesized block must name `read` to match the + // bridge's own `toolResult`. + synthesizeCursorExecToolCall(output, stream, state, toolCallId, "read", { path: piLsPath(args.path) }); + const { execResult } = await resolveExecHandler( + { args, toolCallId }, + execHandlers?.piLs?.bind(execHandlers), + onToolResult, + buildPiLsResult, + buildPiLsError, + buildPiLsError, + { toolCallId, toolName: "read" }, + ); + sendExecClientMessage(h2Request, execMsg, "piLsResult", execResult); + return; + } + case "miniSweAgentBashArgs": { + // Same `ShellArgs`/`ShellResult` pair as `shellArgs`, under its own frame + // number, so the existing shell handler answers it unchanged. + const args = execMsg.message.value; + if (!args.toolCallId) args.toolCallId = crypto.randomUUID(); + const normalizedArgs: ShellArgs = { ...args, workingDirectory: args.workingDirectory || process.cwd() }; + synthesizeCursorExecToolCall(output, stream, state, args.toolCallId, "bash", { + command: args.command, + cwd: args.workingDirectory || undefined, + timeout: args.timeout && args.timeout > 0 ? args.timeout : undefined, + }); + const { execResult } = await resolveExecHandler( + normalizedArgs, + execHandlers?.shell?.bind(execHandlers), + onToolResult, + toolResult => buildShellResultFromToolResult(normalizedArgs, toolResult), + reason => buildShellRejectedResult(normalizedArgs.command, normalizedArgs.workingDirectory, reason), + error => buildShellFailureResult(normalizedArgs.command, normalizedArgs.workingDirectory, error), + { toolCallId: args.toolCallId, toolName: "bash" }, + ); + sendExecClientMessage(h2Request, execMsg, "miniSweAgentBashResult", sanitizeShellExecResult(execResult)); + return; + } + case "redactedReadArgs": { + // Same `ReadArgs`/`ReadResult` pair as `readArgs`, but the server expects + // the client to strip secrets from the content first. No redaction is + // implemented here, and serving a plain read would hand back exactly the + // unredacted bytes the frame exists to withhold. + const args = execMsg.message.value; + sendExecClientMessage( + h2Request, + execMsg, + "redactedReadResult", + buildReadErrorResult(args.path, "Secret redaction is not implemented by this client"), + ); + return; + } + case "mcpStateExecArgs": { + const args = execMsg.message.value; + sendExecClientMessage( + h2Request, + execMsg, + "mcpStateExecResult", + buildMcpStateResult(requestContextTools, args.serverIdentifiers), + ); + return; + } + case "executeHookArgs": { + const args = execMsg.message.value; + const execResult = buildNeutralHookResult(args.request); + if (!execResult) { + sendExecClientThrow( + h2Request, + execMsg, + `Unsupported hook request: ${args.request?.request.case ?? "unset"}`, + "unknown_hook_request", + ); + return; + } + sendExecClientMessage(h2Request, execMsg, "executeHookResult", execResult); + return; + } + case "subagentArgs": { + const args = execMsg.message.value; + const execResult = create(SubagentResultSchema, { + result: { + case: "error", + value: create(SubagentErrorSchema, { error: `Subagents are ${NOT_IMPLEMENTED_SUFFIX}` }), + }, + }); + log("exec", "subagentRejected", { subagentType: args.subagentType }); + sendExecClientMessage(h2Request, execMsg, "subagentResult", execResult); + return; + } + case "subagentAwaitArgs": { + // No subagent was ever spawned, so every awaited id is genuinely unknown. + const args = execMsg.message.value; + const execResult = create(SubagentAwaitResultSchema, { + result: { + case: "notFound", + value: create(SubagentAwaitNotFoundSchema, { agentId: args.agentId }), + }, + }); + sendExecClientMessage(h2Request, execMsg, "subagentAwaitResult", execResult); + return; + } + case "forceBackgroundShellArgs": { + // Backgrounding targets a running tool call by id. This client runs every + // shell to completion in band, so there is never one to move. + const execResult = create(ForceBackgroundShellResultSchema, { + status: ForceBackgroundShellStatus.NOT_FOUND, + }); + sendExecClientMessage(h2Request, execMsg, "forceBackgroundShellResult", execResult); + return; + } + case "forceBackgroundSubagentArgs": { + const execResult = create(ForceBackgroundSubagentResultSchema, { + status: ForceBackgroundSubagentStatus.NOT_FOUND, + }); + sendExecClientMessage(h2Request, execMsg, "forceBackgroundSubagentResult", execResult); + return; + } + case "smartModeClassifierArgs": { + // The classifier decides whether a risky action needs approval. Answering + // `ALLOW` would silently wave through actions the server asked us to + // judge, so the honest answer is that no classifier exists here. + const execResult = create(SmartModeClassifierResultSchema, { + result: { + case: "error", + value: create(SmartModeClassifierErrorSchema, { + error: `Smart-mode classification is ${NOT_IMPLEMENTED_SUFFIX}`, + }), + }, + }); + sendExecClientMessage(h2Request, execMsg, "smartModeClassifierResult", execResult); + return; + } + case "canvasDiagnosticsArgs": { + const args = execMsg.message.value; + const execResult = create(CanvasDiagnosticsResultSchema, { + result: { + case: "error", + value: create(CanvasDiagnosticsErrorSchema, { + path: args.path, + error: `Canvas diagnostics are ${NOT_IMPLEMENTED_SUFFIX}`, + }), + }, + }); + sendExecClientMessage(h2Request, execMsg, "canvasDiagnosticsResult", execResult); + return; + } + case "shellAllowlistPrecheckArgs": { + // The prechecks ask "is this pre-approved, so may it skip the approval + // prompt?". This client keeps no allowlist, so the answer is always no: + // `false` costs an approval round-trip, `true` would grant one that was + // never configured. + sendExecClientMessage( + h2Request, + execMsg, + "shellAllowlistPrecheckResult", + create(ShellAllowlistPrecheckResultSchema, { allowlisted: false }), + ); + return; + } + case "mcpAllowlistPrecheckArgs": { + sendExecClientMessage( + h2Request, + execMsg, + "mcpAllowlistPrecheckResult", + create(McpAllowlistPrecheckResultSchema, { allowlisted: false }), + ); + return; + } + case "webFetchAllowlistPrecheckArgs": { + sendExecClientMessage( + h2Request, + execMsg, + "webFetchAllowlistPrecheckResult", + create(WebFetchAllowlistPrecheckResultSchema, { allowlisted: false }), + ); + return; + } + case "conversationSearchArgs": { + // Cursor conversation history lives server-side; this client keeps no + // local index of it to search. + // + // The streamed `search_conversations_tool_call` envelope announces this + // call but the interaction decoder builds no block for it, so the block + // and its paired result are synthesized here — exactly like every other + // exec frame. Without the pair, `buildSessionContext` strips the whole + // interaction on replay. The frame carries its own `tool_call_id`, so + // the streamed announcement and this block agree on the key. + const args = execMsg.message.value; + const toolCallId = args.toolCallId || crypto.randomUUID(); + const error = `Conversation search is ${NOT_IMPLEMENTED_SUFFIX}`; + synthesizeCursorExecToolCall(output, stream, state, toolCallId, "search_conversations", { + query: args.query, + limit: args.limit, + }); + await pairSynthesizedExecResult(state, onToolResult, toolCallId, "search_conversations", error); + const execResult = create(ConversationSearchResultSchema, { + result: { case: "error", value: create(ConversationSearchErrorSchema, { error }) }, + }); + sendExecClientMessage(h2Request, execMsg, "conversationSearchResult", execResult); + return; + } + case "agentStoreConflictArgs": { + // The agent store is Cursor's own on-disk journal; this client never + // writes one, so it has no conflict events to replay. + const execResult = create(AgentStoreConflictResultSchema, { + result: { + case: "error", + value: create(AgentStoreConflictErrorSchema, { + error: `Agent store conflicts are ${NOT_IMPLEMENTED_SUFFIX}`, + }), + }, + }); + sendExecClientMessage(h2Request, execMsg, "agentStoreConflictResult", execResult); + return; + } + case "gitDiffRequest": { + // `GetDiffResponse` has no error variant: it models five output formats + // plus before/after file contents and nothing else. Any in-band answer is + // therefore a claim that a diff was computed, so a `throw` is the only + // truthful reply. + sendExecClientThrow(h2Request, execMsg, `Git diff is ${NOT_IMPLEMENTED_SUFFIX}`, "exec_variant_unsupported"); + return; + } default: { + // A frame number this build recognises structurally but has no answer + // for. Distinct from the unset-case path above: there the client cannot + // even name the frame. log("warn", "unhandledExecMessage", { execCase }); - // Send a bare ExecClientMessage (id + execId only, no typed result) so the - // server gets an acknowledgement and doesn't hang waiting forever. - const ack = create(ExecClientMessageSchema, { - id: execMsg.id, - execId: execMsg.execId, - }); - const clientMessage = create(AgentClientMessageSchema, { - message: { case: "execClientMessage", value: ack }, - }); - h2Request.write(frameConnectMessage(toBinary(AgentClientMessageSchema, clientMessage))); + sendExecClientThrow( + h2Request, + execMsg, + `No handler for exec message of type ${execCase}`, + "exec_variant_unsupported", + ); } } } -function sendExecClientMessage( +/** + * Send one typed answer on the exec channel. + * + * `ExecClientMessage["message"]` is a discriminated union pairing each case + * with its own result type, so the generic is keyed on the case: passing a + * `ReadResult` under `"shellResult"` is a compile error rather than a wire + * message the server rejects at runtime. + */ +function sendExecClientMessage>( h2Request: http2.ClientHttp2Stream, execMsg: ExecServerMessage, - messageCase: ExecClientMessage["message"]["case"], - value: T, + messageCase: TCase, + value: Extract["value"], ): void { const execClientMessage = create(ExecClientMessageSchema, { id: execMsg.id, execId: execMsg.execId, - message: { - case: messageCase, - value: value as any, - }, + message: { case: messageCase, value } as ExecClientMessage["message"], }); const clientMessage = create(AgentClientMessageSchema, { @@ -1509,6 +2164,42 @@ function sendExecClientMessage( log("execClientMessage", messageCase, value); } +/** + * Fail one exec frame in band. + * + * `ExecClientThrow` is the protocol's failure channel for a frame that cannot + * be answered at all — as opposed to a frame answered with its own typed error + * variant, which means "the tool ran and failed". Cursor's own executor sends + * exactly this for a frame no handler claims + * (`agent-exec/dist/index.js`: `No handler found for server message of type …` + * → `case: 'throw'` then `streamClose`), so the server already knows how to + * recover from it: it surfaces the error to the model instead of blocking on a + * reply that never comes. + * + * The alternative this replaces — writing an `ExecClientMessage` whose `message` + * oneof is unset — is not a valid answer: the server sees a reply carrying no + * result and cannot tell it apart from a malformed frame. + */ +function sendExecClientThrow( + h2Request: http2.ClientHttp2Stream, + execMsg: ExecServerMessage, + error: string, + errorCode?: string, +): void { + const controlMessage = create(ExecClientControlMessageSchema, { + message: { + case: "throw", + value: create(ExecClientThrowSchema, { id: execMsg.id, error, errorCode }), + }, + }); + const clientMessage = create(AgentClientMessageSchema, { + message: { case: "execClientControlMessage", value: controlMessage }, + }); + h2Request.write(frameConnectMessage(toBinary(AgentClientMessageSchema, clientMessage))); + log("execClientControl", "throw", { id: execMsg.id, execId: execMsg.execId, error, errorCode }); + sendExecClientStreamClose(h2Request, execMsg); +} + function sendExecClientStreamClose(h2Request: http2.ClientHttp2Stream, execMsg: ExecServerMessage): void { const closeMessage = create(ExecClientControlMessageSchema, { message: { @@ -1696,6 +2387,27 @@ function toolResultToText(toolResult: ToolResultMessage): string { return toolResult.content.map(item => (item.type === "text" ? item.text : `[${item.mimeType} image]`)).join("\n"); } +/** + * The catalog as the paired transcript result records it. + * + * Cursor receives every resource's identity on the wire, but rebuilt history is + * serialized from this local result — so recording only a count leaves the + * model, one reload later, aware that it once saw N resources and unable to + * name any of them. The URI is what a follow-up `read_mcp_resource` needs, so + * it leads; name and mime type follow only when the server supplied them. + */ +function formatListedMcpResources( + resources: { uri: string; name?: string; mimeType?: string; server?: string }[], +): string { + if (resources.length === 0) return "No MCP resources available"; + const lines = resources.map(resource => { + const qualifiers = [resource.name, resource.mimeType].filter(part => !!part).join(", "); + const server = resource.server ? `[${resource.server}] ` : ""; + return qualifiers ? `- ${server}${resource.uri} (${qualifiers})` : `- ${server}${resource.uri}`; + }); + return [`Listed ${resources.length} MCP resource(s):`, ...lines].join("\n"); +} + function toolResultWasTruncated(toolResult: ToolResultMessage): boolean { if (!toolResult.details || typeof toolResult.details !== "object") { return false; @@ -1712,21 +2424,52 @@ function toolResultDetailBoolean(toolResult: ToolResultMessage, key: string): bo return typeof value === "boolean" ? value : false; } -function buildReadResultFromToolResult(path: string, toolResult: ToolResultMessage) { +/** + * The file's own line count, when the tool recorded one. + * + * `details.meta.truncation.totalLines` is the whole file; the flat + * `details.truncation.totalLines` counts from the window's start line and is + * deliberately not consulted here. Absent for a read that returned the file + * whole, where the payload IS the file and counting it is exact. + */ +function readTotalLinesFromDetails(toolResult: ToolResultMessage): number | undefined { + if (!toolResult.details || typeof toolResult.details !== "object") return undefined; + const meta = (toolResult.details as { meta?: { truncation?: { totalLines?: unknown } } }).meta; + const totalLines = meta?.truncation?.totalLines; + return typeof totalLines === "number" && Number.isFinite(totalLines) ? totalLines : undefined; +} + +function readFileSizeFromDetails(toolResult: ToolResultMessage): number | undefined { + const details = toolResult.details; + if (!details || typeof details !== "object" || !("fileSize" in details)) return undefined; + const { fileSize } = details; + return typeof fileSize === "number" && Number.isSafeInteger(fileSize) && fileSize >= 0 ? fileSize : undefined; +} + +function buildReadResultFromToolResult(path: string, toolResult: ToolResultMessage, rangeApplied = false) { const text = toolResultToText(toolResult); if (toolResult.isError) { return buildReadErrorResult(path, text || "Read failed"); } - const totalLines = text ? text.split("\n").length : 0; + // Counting the payload is only the file's length when the payload is the + // whole file. Under a composed window it is the window's, and answering a + // 20-line page of a 100-line file with `total_lines: 20` tells a paginating + // server it has reached the end. + const totalLines = readTotalLinesFromDetails(toolResult) ?? (text ? text.split("\n").length : 0); return create(ReadResultSchema, { result: { case: "success", value: create(ReadSuccessSchema, { path, totalLines, - fileSize: BigInt(Buffer.byteLength(text, "utf-8")), + fileSize: BigInt(readFileSizeFromDetails(toolResult) ?? Buffer.byteLength(text, "utf-8")), truncated: toolResultWasTruncated(toolResult), output: { case: "content", value: text }, + // Set when this client composed the frame's window onto the read, + // left false when it read the file whole. The proto names the + // field but nothing here pins the server's use of it, so the only + // safe contract is that it describes what we actually did. + rangeApplied, }), }, }); @@ -1952,7 +2695,7 @@ function buildLsRejectedResult(path: string, reason: string) { } function buildGrepResultFromToolResult( - args: { pattern: string; path?: string; outputMode?: string }, + args: { pattern: string; path?: string; outputMode?: string; offset?: number }, toolResult: ToolResultMessage, ) { const text = toolResultToText(toolResult); @@ -1980,6 +2723,11 @@ function buildGrepResultFromToolResult( totalFiles: files.length, clientTruncated, ripgrepTruncated: false, + // Echoes the offset this client actually applied; absent when + // the frame requested none. The proto names the field but + // nothing here pins the server's use of it, so it reports what + // we did rather than asserting a pagination protocol. + offsetApplied: args.offset, }), }, }); @@ -2008,6 +2756,7 @@ function buildGrepResultFromToolResult( totalMatches, clientTruncated, ripgrepTruncated: false, + offsetApplied: args.offset, }), }, }); @@ -2055,6 +2804,7 @@ function buildGrepResultFromToolResult( totalMatchedLines, clientTruncated, ripgrepTruncated: false, + offsetApplied: args.offset, }), }, }); @@ -2184,6 +2934,7 @@ function decodeMcpCall(args: { toolCallId: string; providerIdentifier: string; toolName: string; + smartModeApprovalOnly?: boolean; }): CursorMcpCall { const decodedArgs: Record = {}; for (const [key, value] of Object.entries(args.args ?? {})) { @@ -2196,6 +2947,7 @@ function decodeMcpCall(args: { toolCallId: args.toolCallId, args: decodedArgs, rawArgs: args.args ?? {}, + approvalOnly: args.smartModeApprovalOnly === true, }; } @@ -2304,6 +3056,205 @@ function selectMcpCall(toolCall: CursorMcpToolCallCarrier | undefined): CursorMc return toolCall?.mcpToolCall; } +/** + * The streamed `ToolCall` variants whose block the exec channel owns. + * + * Each of these is announced on the interaction stream AND dispatched as its + * own `ExecServerMessage` frame — the Pi family (45-51), plus the two MCP + * resource frames — so the block is synthesized once, by the exec handler, + * which is the side that has the result. + * + * `connect_scm` is deliberately NOT here: `ExecServerMessage` has no + * connect-SCM case at all (field 44 is `git_diff_request`), so nothing on the + * exec channel ever answers it and the streamed announcement is the only + * signal. `search_conversations` is not here either: frame 53 answers it, but + * it carries its own `tool_call_id` on the streamed envelope and pairs there, + * so the exec branch does not synthesize a block for it. + */ +const EXEC_OWNED_TOOL_CALL_CASES: ReadonlySet = new Set([ + "piReadToolCall", + "piBashToolCall", + "piEditToolCall", + "piWriteToolCall", + "piGrepToolCall", + "piFindToolCall", + "piLsToolCall", + "listMcpResourcesToolCall", + "readMcpResourceToolCall", +]); + +function isExecOwnedToolCall(toolCall: { tool?: { case?: string } } | undefined): boolean { + const variant = toolCall?.tool?.case; + return variant !== undefined && EXEC_OWNED_TOOL_CALL_CASES.has(variant); +} + +/** + * Retain a freshly opened streamed tool-call block. + * + * Keyed by the interaction envelope's `call_id`, which is the only key every + * `ToolCall*Update` for that call shares. The block's own `id` is deliberately + * not the key: MCP, Pi and connect-SCM blocks are filed under the id carried + * inside the call's `args`, because that is what the exec channel pairs its + * result under and what the transcript files the visible block under. + * + * `currentToolCall` is still set, as the fallback for frames that carry no + * `call_id` (proto3-optional, and unset on what older builds send). + */ +/** + * Close every tool-call block still open when the stream ends. + * + * Not just the last one started: with interleaved calls several can be open at + * once, and an unclosed block leaves its live card animating and its call + * unpaired. + * + * Only blocks fed by a streamed argument buffer get reparsed. Todo, + * connect-SCM and MCP-settled frames arrive with complete `arguments` and + * never set the partial buffer; `parseStreamingJson(undefined)` returns `{}`, + * so reparsing unconditionally would erase the arguments of every such block + * caught open by a truncated stream. + * + * Server-owned blocks are also paired here. `connect-scm` and `todo` are + * stamped {@link kCursorExecResolved} the moment they open, so `agent-loop.ts` + * synthesizes no placeholder for them and only their `toolCallCompleted` frame + * pairs a result. A transport that closes before that frame would leave the + * call unpaired, and `buildSessionContext` strips a dangling call from every + * rebuilt transcript — the interaction disappears. An interrupted result is + * emitted instead. + * + * MCP blocks are excluded even when resolved: the exec dispatch that marked + * them owns their result, and `drainInFlightDispatches` awaits it before this + * runs, so pairing here would duplicate one against the same `toolCallId`. + */ +export function flushOpenToolCalls( + output: AssistantMessage, + stream: AssistantMessageEventStream, + state: BlockState, +): void { + const openBlocks = new Set(state.openToolCalls.values()); + if (state.currentToolCall) openBlocks.add(state.currentToolCall); + for (const block of openBlocks) { + const idx = output.content.indexOf(block); + const partialJson = block[kStreamingPartialJson]; + if (partialJson !== undefined) { + block.arguments = parseStreamingJson(partialJson); + clearStreamingPartialJson(block); + } + const kind = block[kStreamingBlockKind]; + if (kind === "connect-scm" || kind === "todo") { + state.onToolResult?.({ + role: "toolResult", + toolCallId: block.id, + toolName: block.name, + content: [{ type: "text", text: "The connection to Cursor closed before this call completed." }], + isError: true, + timestamp: Date.now(), + }); + } + stream.push({ type: "toolcall_end", contentIndex: idx, toolCall: block, partial: output }); + } + state.openToolCalls.clear(); + state.setToolCall(null); +} + +function retainStreamedCall(state: BlockState, block: ToolCallState, envelopeId: string | undefined): void { + if (envelopeId) state.openToolCalls.set(envelopeId, block); + state.setToolCall(block); +} + +/** + * The open block a streamed update addresses, or `null` to ignore the update. + * + * Cursor interleaves calls: `start A, start B, complete A` is legal, so the + * update must reach block A even though B opened last. An id naming no open + * block is ignored rather than misapplied — settling the wrong block would pair + * it with another call's result. + * + * A missing id falls back to the current block: the correlation key is + * optional, and dropping those updates would strand a block stamped + * {@link kCursorExecResolved}, which nothing else settles and whose whole + * interaction is then stripped from every rebuilt transcript. + */ +function resolveStreamedCall(state: BlockState, envelopeId: string | undefined): ToolCallState | null { + if (!envelopeId) return state.currentToolCall; + const keyed = state.openToolCalls.get(envelopeId); + if (keyed) return keyed; + // Blocks opened before this build tracked envelope ids, and blocks opened + // from a frame that carried none, are only reachable as `currentToolCall`. + const current = state.currentToolCall; + return current && current[kStreamingEnvelopeId] === undefined ? current : null; +} + +/** Release a settled block from both the keyed map and the current slot. */ +function releaseStreamedCall(state: BlockState, block: ToolCallState): void { + const envelopeId = block[kStreamingEnvelopeId]; + if (envelopeId) state.openToolCalls.delete(envelopeId); + if (state.currentToolCall === block) state.setToolCall(null); +} + +interface CursorConnectScmRepository { + owner?: string; + repo?: string; +} + +interface CursorConnectScmCall { + args?: { + toolCallId?: string; + /** `ConnectScmArgs.target` oneof; `github` is its only member today. */ + target?: { case?: string; value?: { repository?: CursorConnectScmRepository } }; + github?: { repository?: CursorConnectScmRepository }; + }; + /** `ConnectScmResult.result` oneof: `success` | `error` | `rejected`. */ + result?: { result?: { case?: string; value?: { error?: string; reason?: string } } }; +} + +interface CursorConnectScmCarrier { + tool?: { case?: string; value?: unknown }; + connectScmToolCall?: CursorConnectScmCall; +} + +/** + * The streamed `connect_scm_tool_call` variant, if this update carries one. + * + * Same oneof-vs-flattened handling as {@link selectMcpCall}: a wire-decoded + * `ToolCall` exposes its variant as `{ case, value }`, while hand-shaped test + * fixtures use the flat property. + */ +function selectConnectScmCall(toolCall: CursorConnectScmCarrier | undefined): CursorConnectScmCall | undefined { + const oneof = toolCall?.tool; + if (oneof?.case === "connectScmToolCall") return oneof.value as CursorConnectScmCall; + return toolCall?.connectScmToolCall; +} + +/** The repository a connect-SCM call targets, across the `target` oneof. */ +function selectConnectScmRepository(call: CursorConnectScmCall | undefined): CursorConnectScmRepository | undefined { + const target = call?.args?.target; + if (target?.case === "github") return target.value?.repository; + return call?.args?.github?.repository; +} + +/** + * Render a settled `ConnectScmResult` as the text of its paired tool result. + * + * Returns `isError` because the three outcomes are not interchangeable: only + * `success` means the repository was connected, and reporting a rejection as + * success would tell the model to proceed against a repo it cannot reach. + */ +function describeConnectScmResult(call: CursorConnectScmCall | undefined): { text: string; isError: boolean } { + const result = call?.result?.result; + switch (result?.case) { + case "success": + return { text: "SCM connected", isError: false }; + case "error": + return { text: result.value?.error || "SCM connection failed", isError: true }; + case "rejected": + return { text: result.value?.reason || "SCM connection rejected", isError: true }; + default: + // A completion carrying no result settles the block anyway: it is + // stamped resolved, so nothing downstream would ever pair it. + return { text: "SCM connection reported no result", isError: true }; + } +} + /** * Extract the authoritative full todo list from a completed native todo call. * @@ -2637,6 +3588,42 @@ export function synthesizeCursorExecToolCall( stream.push({ type: "toolcall_end", contentIndex: idx, toolCall: block, partial: output }); } +/** + * Pair a `toolResult` for a synthesized block the client answered itself, + * without ever consulting a handler. + * + * {@link resolveExecHandler} does this for every frame backed by a local tool. + * Frames answered from a fixed verdict — no handler, no local execution — still + * need the pair for the same reason: the block was stamped + * {@link kCursorExecResolved}, so `agent-loop.ts` emits no placeholder for it + * and `buildSessionContext` strips an unpaired call, taking the whole + * interaction out of every rebuilt transcript. + * + * `isError` defaults true because most such verdicts are refusals; the MCP + * resource frames run locally and can genuinely succeed, and a success filed + * as an error would render as a failed call in every rebuilt transcript. + */ +async function pairSynthesizedExecResult( + state: BlockState, + onToolResult: CursorToolResultHandler | undefined, + toolCallId: string, + toolName: string, + text: string, + isError = true, +): Promise { + const synthesized: ToolResultMessage = { + role: "toolResult", + toolCallId, + toolName, + content: [{ type: "text", text }], + isError, + timestamp: Date.now(), + }; + const sink = onToolResult ?? state.onToolResult; + if (!sink) return; + await sink(synthesized); +} + /** Exported for tests: drives one Cursor interaction update through the streaming state machine. */ export function processInteractionUpdate( update: any, @@ -2683,6 +3670,43 @@ export function processInteractionUpdate( stream.push({ type: "thinking_delta", contentIndex: idx, delta, partial: output }); } else if (updateCase === "thinkingCompleted") { endCurrentThinkingBlock(output, stream, state); + } else if (updateCase === "toolCallStarted" && selectConnectScmCall(update.message.value.toolCall)) { + // `connect_scm` is resolved entirely server-side and has NO exec frame: + // `ExecServerMessage` carries no connect-SCM case (field 44 is + // `git_diff_request`), so the streamed pair is the only signal this client + // sees. The authoritative outcome rides on the COMPLETION's `result` + // oneof, so the block is opened here and settled there — answering now + // would persist a verdict before the server has given one. + // + // Stamped resolved so `agent-loop.ts` runs no local tool for it: there is + // no local `connect_scm`, and the completion pairs the result itself. + endCurrentTextBlock(output, stream, state); + endCurrentThinkingBlock(output, stream, state); + const scmCall = selectConnectScmCall(update.message.value.toolCall); + const repository = selectConnectScmRepository(scmCall); + const block: ToolCallState = { + type: "toolCall", + id: scmCall?.args?.toolCallId || update.message.value.callId || crypto.randomUUID(), + name: "connect_scm", + arguments: repository ? { owner: repository.owner, repo: repository.repo } : {}, + [kStreamingBlockIndex]: output.content.length, + [kStreamingBlockKind]: "connect-scm", + [kStreamingEnvelopeId]: update.message.value.callId || undefined, + [kCursorExecResolved]: true, + }; + output.content.push(block); + retainStreamedCall(state, block, update.message.value.callId); + stream.push({ type: "toolcall_start", contentIndex: output.content.length - 1, partial: output }); + } else if (updateCase === "toolCallStarted" && isExecOwnedToolCall(update.message.value.toolCall)) { + // The exec channel already synthesized this block (and marked it resolved) + // when it ran the tool locally, so the streamed announcement must not + // create a second one. Modern builds stream a `pi_*_tool_call` envelope + // alongside every `ExecServerMessage` 45-51 frame; before this branch the + // duplicate was avoided only because the decoder recognised neither, which + // would silently start double-rendering the moment a variant was added. + endCurrentTextBlock(output, stream, state); + endCurrentThinkingBlock(output, stream, state); + log("exec", "streamedToolCallOwnedByExec", { case: update.message.value.toolCall?.tool?.case }); } else if (updateCase === "toolCallStarted") { endCurrentTextBlock(output, stream, state); endCurrentThinkingBlock(output, stream, state); @@ -2707,12 +3731,13 @@ export function processInteractionUpdate( [kStreamingBlockIndex]: output.content.length, [kStreamingPartialJson]: "", [kStreamingBlockKind]: "mcp", + [kStreamingEnvelopeId]: update.message.value.callId || undefined, }; if (resolvedByExec) { markCursorExecResolved(block); } output.content.push(block); - state.setToolCall(block); + retainStreamedCall(state, block, update.message.value.callId); stream.push({ type: "toolcall_start", contentIndex: output.content.length - 1, partial: output }); return; } @@ -2733,54 +3758,92 @@ export function processInteractionUpdate( arguments: buildTodoDisplayArgs(toolCall), [kStreamingBlockIndex]: output.content.length, [kStreamingBlockKind]: "todo", + // Only the real envelope id is a correlation key; the minted + // fallback below names no frame the server will ever send back. + [kStreamingEnvelopeId]: update.message.value.callId || undefined, [kCursorExecResolved]: true, }; output.content.push(block); - state.setToolCall(block); + retainStreamedCall(state, block, update.message.value.callId); stream.push({ type: "toolcall_start", contentIndex: output.content.length - 1, partial: output }); } } } else if (updateCase === "toolCallDelta" || updateCase === "partialToolCall") { - if (state.currentToolCall?.[kStreamingBlockKind] === "mcp") { + // Same correlation rule as the completion path below: an argument delta + // belonging to a different call must not be appended to this block's + // buffer, which would corrupt the JSON both of them parse. + const target = resolveStreamedCall(state, update.message.value.callId); + if (target?.[kStreamingBlockKind] === "mcp") { // Cursor's `args_text_delta` is "aggregated args text so far" per agent.proto: each // delta is a cumulative snapshot of the JSON-text args. Strip the prefix we already // have to recover the new suffix; fall back to treating the value as an incremental // fragment when it doesn't extend the buffer. const snapshot: string = update.message.value.argsTextDelta || ""; - const current = state.currentToolCall[kStreamingPartialJson] ?? ""; + const current = target[kStreamingPartialJson] ?? ""; const chunk = snapshot.startsWith(current) ? snapshot.slice(current.length) : snapshot; if (chunk.length === 0) { return; } const nextBuffer = current + chunk; - state.currentToolCall[kStreamingPartialJson] = nextBuffer; + target[kStreamingPartialJson] = nextBuffer; // Throttle mid-stream parses to keep total parse work O(N) instead of O(N²) // in the argument-buffer length; the authoritative full parse runs in // `toolCallCompleted` (mcp branch) and the fallback end-of-stream path. - const throttled = parseStreamingJsonThrottled(nextBuffer, state.currentToolCall[kStreamingLastParseLen] ?? 0); + const throttled = parseStreamingJsonThrottled(nextBuffer, target[kStreamingLastParseLen] ?? 0); if (throttled) { - state.currentToolCall.arguments = throttled.value; - state.currentToolCall[kStreamingLastParseLen] = throttled.parsedLen; + target.arguments = throttled.value; + target[kStreamingLastParseLen] = throttled.parsedLen; } - const idx = output.content.indexOf(state.currentToolCall); + const idx = output.content.indexOf(target); stream.push({ type: "toolcall_delta", contentIndex: idx, delta: chunk, partial: output }); } } else if (updateCase === "toolCallCompleted") { - if (state.currentToolCall) { + // Correlate on the envelope's `call_id`, NOT the block id: MCP, Pi and SCM + // blocks are filed under the id inside the call's `args` (which is what + // the exec channel pairs its result under), and that need not equal the + // envelope id. Cursor also interleaves calls, so the block this settles + // is looked up by id rather than assumed to be the last one opened — + // otherwise an unrelated completion closes whichever block is current and + // pairs it with the wrong result. + const settled = resolveStreamedCall(state, update.message.value.callId); + if (settled) { const toolCall = update.message.value.toolCall; - if (state.currentToolCall[kStreamingBlockKind] === "mcp") { + if (settled[kStreamingBlockKind] === "mcp") { // Authoritative full parse of the accumulated argument buffer; the delta // path throttles mid-stream parses, so `arguments` may lag the buffer. - const partial = state.currentToolCall[kStreamingPartialJson]; + const partial = settled[kStreamingPartialJson]; if (partial !== undefined) { - state.currentToolCall.arguments = parseStreamingJson(partial); + settled.arguments = parseStreamingJson(partial); } const decodedArgs = decodeMcpArgsMap(selectMcpCall(toolCall)?.args?.args); - state.currentToolCall.arguments = mergeCursorMcpToolCallArgs( - state.currentToolCall.arguments as Record | undefined, + settled.arguments = mergeCursorMcpToolCallArgs( + settled.arguments as Record | undefined, decodedArgs, ); - } else if (state.currentToolCall[kStreamingBlockKind] === "todo") { + } else if (settled[kStreamingBlockKind] === "connect-scm") { + // The authoritative outcome arrives only here, on the completion's + // `ConnectScmResult` oneof. The block was stamped resolved at start, + // so nothing downstream pairs it: settling is this branch's job, and + // a completion with no `toolCall` still settles rather than leaking a + // dangling call into every rebuilt transcript. + // + // Late args are merged too — a start frame may announce the call + // before the target repository is known. + const scmCall = selectConnectScmCall(toolCall); + const repository = selectConnectScmRepository(scmCall); + if (repository) { + settled.arguments = { owner: repository.owner, repo: repository.repo }; + } + const { text, isError } = describeConnectScmResult(scmCall); + state.onToolResult?.({ + role: "toolResult", + toolCallId: settled.id, + toolName: "connect_scm", + content: [{ type: "text", text }], + isError, + timestamp: Date.now(), + }); + } else if (settled[kStreamingBlockKind] === "todo") { // Only the server's success snapshot is authoritative: the request args // may differ from what was actually stored after a merge, and on // `UpdateTodosError` nothing was stored at all. No snapshot => leave @@ -2794,7 +3857,7 @@ export function processInteractionUpdate( const snapshot = toolCall ? extractTodoSnapshot(toolCall) : null; const error = toolCall ? extractTodoError(toolCall) : null; if (snapshot) { - state.currentToolCall.arguments = { todos: snapshot.todos, merged: snapshot.merged }; + settled.arguments = { todos: snapshot.todos, merged: snapshot.merged }; } // The host settles EVERY completed native todo call, successful or // not: the interactive card only resolves on a matching @@ -2810,7 +3873,7 @@ export function processInteractionUpdate( let persisted: ToolResultMessage | undefined; let hostError: string | null = null; try { - persisted = state.onTodoSnapshot?.(snapshot, state.currentToolCall.id, error) ?? undefined; + persisted = state.onTodoSnapshot?.(snapshot, settled.id, error) ?? undefined; } catch (callbackError) { // A throwing host callback (e.g. session persistence failing on // disk error) must not leave the resolved block unpaired: the @@ -2820,14 +3883,12 @@ export function processInteractionUpdate( hostError = callbackError instanceof Error ? callbackError.message : String(callbackError); log("error", "onTodoSnapshot", { error: hostError }); } - state.onToolResult?.( - persisted ?? buildTodoToolResult(state.currentToolCall.id, snapshot, hostError ?? error), - ); + state.onToolResult?.(persisted ?? buildTodoToolResult(settled.id, snapshot, hostError ?? error)); } - const idx = output.content.indexOf(state.currentToolCall); - clearStreamingPartialJson(state.currentToolCall); - stream.push({ type: "toolcall_end", contentIndex: idx, toolCall: state.currentToolCall, partial: output }); - state.setToolCall(null); + const idx = output.content.indexOf(settled); + clearStreamingPartialJson(settled); + stream.push({ type: "toolcall_end", contentIndex: idx, toolCall: settled, partial: output }); + releaseStreamedCall(state, settled); } } else if (updateCase === "turnEnded") { output.stopReason = "stop"; @@ -2877,7 +3938,18 @@ function readCursorBlob(blobStore: Map, blobId: Uint8Array): return data; } -const CURSOR_NATIVE_TOOL_NAMES = new Set(["bash", "read", "write", "delete", "ls", "grep", "lsp", "todo"]); +/** + * Local tools Cursor already drives natively over the exec channel, so + * advertising them again as MCP tools would give the model two ways to call the + * same thing. + * + * `lsp` is deliberately NOT here. The native `diagnosticsArgs` frame covers + * exactly one of the tool's actions (`action: "diagnostics"`); the rest — + * `definition`, `references`, `rename`, `code_actions`, `hover`, + * `implementation`, `type_definition`, `symbols`, ... — have no native frame at + * all, so filtering the whole tool out hid every one of them from the model. + */ +const CURSOR_NATIVE_TOOL_NAMES = new Set(["bash", "read", "write", "delete", "ls", "grep", "todo"]); export function buildMcpToolDefinitions(tools: Tool[] | undefined): McpToolDefinition[] { if (!tools || tools.length === 0) { diff --git a/packages/ai/src/providers/cursor/exec-modern.ts b/packages/ai/src/providers/cursor/exec-modern.ts new file mode 100644 index 000000000..ebf1bccdf --- /dev/null +++ b/packages/ai/src/providers/cursor/exec-modern.ts @@ -0,0 +1,495 @@ +/** + * Proto builders for the modern Cursor CLI exec frames (`ExecServerMessage` + * 27-31, 36-38, 40-55). + * + * Split out of `cursor.ts` because these are pure `create(...)` shapes with no + * transport, stream, or block-state coupling: the dispatcher decides *which* + * answer a frame gets, this module knows *what* that answer looks like on the + * wire. Every builder returns a result whose oneof case is set — an + * `ExecClientMessage` carrying a result with an unset oneof is a fake success + * the server reads as "the tool ran and produced nothing". + */ + +import { create } from "@bufbuild/protobuf"; +import { + AfterAgentResponseRequestResponseSchema, + AfterAgentThoughtRequestResponseSchema, + BeforeSubmitPromptRequestResponseSchema, + type ExecuteHookRequest, + type ExecuteHookResponse, + ExecuteHookResponseSchema, + type ExecuteHookResult, + ExecuteHookResultSchema, + type McpStateExecResult, + McpStateExecResultSchema, + McpStateServerSchema, + McpStateSuccessSchema, + type McpToolDefinition, + PiBashExecErrorSchema, + type PiBashExecResult, + PiBashExecResultSchema, + PiBashExecSuccessSchema, + PiEditExecErrorSchema, + PiEditExecRejectedSchema, + type PiEditExecResult, + PiEditExecResultSchema, + PiEditExecSuccessSchema, + PiFindExecErrorSchema, + type PiFindExecResult, + PiFindExecResultSchema, + PiFindExecSuccessSchema, + PiGrepExecErrorSchema, + type PiGrepExecResult, + PiGrepExecResultSchema, + PiGrepExecSuccessSchema, + PiLsExecErrorSchema, + type PiLsExecResult, + PiLsExecResultSchema, + PiLsExecSuccessSchema, + PiReadExecErrorSchema, + type PiReadExecResult, + PiReadExecResultSchema, + PiReadExecSuccessSchema, + type PiTruncation, + PiTruncationSchema, + PiWriteExecErrorSchema, + PiWriteExecRejectedSchema, + type PiWriteExecResult, + PiWriteExecResultSchema, + PiWriteExecSuccessSchema, + PostToolUseFailureRequestResponseSchema, + PostToolUseRequestResponseSchema, + PreCompactRequestResponseSchema, + PreToolUseRequestResponseSchema, + StopRequestResponseSchema, + SubagentStartRequestResponseSchema, + SubagentStopRequestResponseSchema, +} from "@oh-my-pi/pi-catalog/discovery/cursor-gen/agent_pb"; +import type { ToolResultMessage } from "../../types"; + +/** + * The pure arg translation lives in `../cursor-pi-args` so the legacy pi shim + * can share it without pulling this module's protobuf graph into the bundled + * virtual registry. Re-exported here because this is where the frame builders + * and their translation are consumed together. + */ +export { + piEscapeRegexLiteral, + piGrepSkip, + piJoinPath, + piLimit, + piLsPath, + piReadDisplayPath, + piReadPath, + piTimeout, +} from "../cursor-pi-args"; + +/** Flatten a tool result's content into the single `output` string the Pi frames carry. */ +export function piOutputText(toolResult: ToolResultMessage): string { + return toolResult.content.map(item => (item.type === "text" ? item.text : `[${item.mimeType} image]`)).join("\n"); +} + +/** + * Read one field off a tool result's `details` bag, or off a nested object + * inside it. + * + * `details` is `unknown` by design — every tool ships its own shape — so each + * read is narrowed rather than asserted, and the caller decides what a value of + * the wrong type means. + */ +function bagValue(bag: unknown, key: string): unknown { + if (!bag || typeof bag !== "object" || !(key in bag)) return undefined; + return Reflect.get(bag, key); +} + +/** + * A positive integer count from `details`, or `undefined`. + * + * The Pi frames model their limit counters as `optional uint32`: zero means + * "the limit was reached at zero results", so a missing, non-numeric, or + * non-positive value must stay unset rather than be sent as 0. + */ +function detailCount(toolResult: ToolResultMessage, key: string): number | undefined { + const value = bagValue(toolResult.details, key); + return positiveCount(value); +} + +function positiveCount(value: unknown): number | undefined { + return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : undefined; +} + +/** + * The entry cap a listing hit, from either shape a local tool records it in. + * + * `glob` sets a flat `details.resultLimitReached` alongside the structured + * meta; `read` — which serves `pi_ls` — records the cap only through + * `OutputMeta` at `details.meta.limits.resultLimit.reached`. Reading just the + * flat field dropped `entry_limit_reached` for every real listing, so Cursor + * received clipped output with no incompleteness signal. + */ +function resultLimitReached(toolResult: ToolResultMessage): number | undefined { + const flat = detailCount(toolResult, "resultLimitReached"); + if (flat !== undefined) return flat; + const limits = bagValue(bagValue(toolResult.details, "meta"), "limits"); + return positiveCount(bagValue(bagValue(limits, "resultLimit"), "reached")); +} + +/** + * Translate a local tool's truncation summary into `PiTruncation`. + * + * Two shapes reach here. `read`/`grep` set `details.truncation` + * (`TruncationResult`), which carries an explicit `truncated` boolean. `bash` + * sets `details.meta.truncation` (`TruncationMeta`), which has **no** such + * flag — its presence *is* the signal, and requiring the boolean silently + * dropped every Bash truncation, handing Cursor clipped output with no notice + * that it was clipped. + * + * Returns `undefined` when nothing was truncated: the field is `optional` on + * every Pi success message, and emitting a zeroed `PiTruncation` would tell the + * server the output was trimmed to nothing. + */ +export function piTruncation(toolResult: ToolResultMessage): PiTruncation | undefined { + const direct = bagValue(toolResult.details, "truncation"); + // `TruncationResult` is authoritative when present and explicitly false. + const truncation = direct !== undefined ? direct : bagValue(bagValue(toolResult.details, "meta"), "truncation"); + if (truncation === undefined || truncation === null) return undefined; + // `TruncationResult` gates on its flag; `TruncationMeta` has none and is + // only ever attached when output was actually trimmed. + const flag = bagValue(truncation, "truncated"); + if (flag !== undefined && flag !== true) return undefined; + const truncatedBy = bagValue(truncation, "truncatedBy"); + const totalLines = bagValue(truncation, "totalLines"); + const outputLines = bagValue(truncation, "outputLines"); + const outputBytes = bagValue(truncation, "outputBytes"); + return create(PiTruncationSchema, { + truncated: true, + truncatedBy: typeof truncatedBy === "string" ? truncatedBy : "", + totalLines: typeof totalLines === "number" ? totalLines : 0, + outputLines: typeof outputLines === "number" ? outputLines : 0, + outputBytes: typeof outputBytes === "number" ? outputBytes : 0, + firstLineExceedsLimit: bagValue(truncation, "firstLineExceedsLimit") === true, + lastLinePartial: bagValue(truncation, "lastLinePartial") === true, + }); +} + +export function buildPiReadResult(toolResult: ToolResultMessage): PiReadExecResult { + const text = piOutputText(toolResult); + if (toolResult.isError) return buildPiReadError(text || "Read failed"); + return create(PiReadExecResultSchema, { + result: { + case: "success", + value: create(PiReadExecSuccessSchema, { output: text, truncation: piTruncation(toolResult) }), + }, + }); +} + +export function buildPiReadError(error: string): PiReadExecResult { + return create(PiReadExecResultSchema, { + result: { case: "error", value: create(PiReadExecErrorSchema, { error }) }, + }); +} + +export function buildPiBashResult(toolResult: ToolResultMessage): PiBashExecResult { + const text = piOutputText(toolResult); + const truncation = piTruncation(toolResult); + if (toolResult.isError) { + return create(PiBashExecResultSchema, { + result: { + case: "error", + value: create(PiBashExecErrorSchema, { error: text || "Command failed", truncation }), + }, + }); + } + return create(PiBashExecResultSchema, { + result: { + case: "success", + value: create(PiBashExecSuccessSchema, { output: text, truncation }), + }, + }); +} + +export function buildPiBashError(error: string): PiBashExecResult { + return create(PiBashExecResultSchema, { + result: { case: "error", value: create(PiBashExecErrorSchema, { error }) }, + }); +} + +/** + * `PiEditExecSuccess` requires `diff` and `patch` alongside `output`. The local + * `edit` tool reports them under `details`; when it does not, the strings stay + * empty rather than being faked from the output text. + */ +export function buildPiEditResult(toolResult: ToolResultMessage): PiEditExecResult { + const text = piOutputText(toolResult); + if (toolResult.isError) return buildPiEditError(text || "Edit failed"); + const diff = bagValue(toolResult.details, "diff"); + const patch = bagValue(toolResult.details, "patch"); + return create(PiEditExecResultSchema, { + result: { + case: "success", + value: create(PiEditExecSuccessSchema, { + output: text, + diff: typeof diff === "string" ? diff : "", + patch: typeof patch === "string" ? patch : "", + firstChangedLine: detailCount(toolResult, "firstChangedLine"), + }), + }, + }); +} + +export function buildPiEditError(error: string): PiEditExecResult { + return create(PiEditExecResultSchema, { + result: { case: "error", value: create(PiEditExecErrorSchema, { error }) }, + }); +} + +/** + * A refusal is not an execution failure: `PiEditExecResult` models them as + * separate variants, and answering a denied call with `error` reads as "the + * edit ran and broke", which invites a retry of an operation that was never + * permitted. + */ +export function buildPiEditRejected(reason: string): PiEditExecResult { + return create(PiEditExecResultSchema, { + result: { case: "rejected", value: create(PiEditExecRejectedSchema, { reason }) }, + }); +} + +export function buildPiWriteResult(toolResult: ToolResultMessage): PiWriteExecResult { + const text = piOutputText(toolResult); + if (toolResult.isError) return buildPiWriteError(text || "Write failed"); + return create(PiWriteExecResultSchema, { + result: { case: "success", value: create(PiWriteExecSuccessSchema, { output: text }) }, + }); +} + +export function buildPiWriteError(error: string): PiWriteExecResult { + return create(PiWriteExecResultSchema, { + result: { case: "error", value: create(PiWriteExecErrorSchema, { error }) }, + }); +} + +/** Same variant split as {@link buildPiEditRejected}. */ +export function buildPiWriteRejected(reason: string): PiWriteExecResult { + return create(PiWriteExecResultSchema, { + result: { case: "rejected", value: create(PiWriteExecRejectedSchema, { reason }) }, + }); +} + +export function buildPiGrepResult(toolResult: ToolResultMessage): PiGrepExecResult { + const text = piOutputText(toolResult); + if (toolResult.isError) return buildPiGrepError(text || "Grep failed"); + const matchLimitReached = detailCount(toolResult, "perFileLimitReached"); + return create(PiGrepExecResultSchema, { + result: { + case: "success", + value: create(PiGrepExecSuccessSchema, { + output: text, + truncation: piTruncation(toolResult) ?? grepInternalCapTruncation(toolResult, text, matchLimitReached), + matchLimitReached, + linesTruncated: bagValue(toolResult.details, "linesTruncated") === true, + }), + }, + }); +} + +/** + * The one grep cap that reaches Cursor through no other field. + * + * `GrepTool` folds every cap into the flat `details.truncated`, but only + * populates `details.truncation` when the output text itself was trimmed and + * `perFileLimitReached` when a per-file or explicit total cap fired. Its native + * backend's own ceiling (`INTERNAL_TOTAL_CAP`) sets neither, so a search that + * hit it answered as an unqualified success over incomplete results — the one + * failure mode a caller cannot detect and cannot retry around. + * + * Only consulted once the specific signals came back empty, so a cap that did + * report itself is never restated. `totalLines` stays 0: the flat flag says + * that results were dropped, not how many there were. + */ +function grepInternalCapTruncation( + toolResult: ToolResultMessage, + text: string, + matchLimitReached: number | undefined, +): PiTruncation | undefined { + if (matchLimitReached !== undefined) return undefined; + if (bagValue(toolResult.details, "truncated") !== true) return undefined; + return create(PiTruncationSchema, { + truncated: true, + truncatedBy: "matches", + totalLines: 0, + outputLines: text ? text.split("\n").length : 0, + outputBytes: Buffer.byteLength(text, "utf-8"), + firstLineExceedsLimit: false, + lastLinePartial: false, + }); +} + +export function buildPiGrepError(error: string): PiGrepExecResult { + return create(PiGrepExecResultSchema, { + result: { case: "error", value: create(PiGrepExecErrorSchema, { error }) }, + }); +} + +export function buildPiFindResult(toolResult: ToolResultMessage): PiFindExecResult { + const text = piOutputText(toolResult); + if (toolResult.isError) return buildPiFindError(text || "Find failed"); + return create(PiFindExecResultSchema, { + result: { + case: "success", + value: create(PiFindExecSuccessSchema, { + output: text, + truncation: piTruncation(toolResult), + resultLimitReached: resultLimitReached(toolResult), + }), + }, + }); +} + +export function buildPiFindError(error: string): PiFindExecResult { + return create(PiFindExecResultSchema, { + result: { case: "error", value: create(PiFindExecErrorSchema, { error }) }, + }); +} + +export function buildPiLsResult(toolResult: ToolResultMessage): PiLsExecResult { + const text = piOutputText(toolResult); + if (toolResult.isError) return buildPiLsError(text || "Ls failed"); + return create(PiLsExecResultSchema, { + result: { + case: "success", + value: create(PiLsExecSuccessSchema, { + output: text, + truncation: piTruncation(toolResult), + entryLimitReached: resultLimitReached(toolResult), + }), + }, + }); +} + +export function buildPiLsError(error: string): PiLsExecResult { + return create(PiLsExecResultSchema, { + result: { case: "error", value: create(PiLsExecErrorSchema, { error }) }, + }); +} + +/** + * Answer `mcpStateExecArgs` (frame 36) from the catalog already advertised in + * `RequestContext.tools`. + * + * This client hosts no MCP servers of its own: every forwarded tool is a local + * pi-agent tool published under a synthetic `providerIdentifier`. Regrouping + * the same list keeps the server's view of "which servers exist and what do + * they expose" consistent with what it was told at context time, instead of + * claiming zero servers while tool calls for them keep arriving. + * + * `serverIdentifiers` filters the answer when the server asks about specific + * servers. `kickOnly` is a restart request — there is nothing to restart, so it + * is answered with the same state rather than an error. + */ +export function buildMcpStateResult( + tools: McpToolDefinition[], + serverIdentifiers: readonly string[], +): McpStateExecResult { + const byProvider = new Map(); + for (const tool of tools) { + const identifier = tool.providerIdentifier; + const existing = byProvider.get(identifier); + if (existing) existing.push(tool); + else byProvider.set(identifier, [tool]); + } + + const wanted = serverIdentifiers.length > 0 ? new Set(serverIdentifiers) : undefined; + const servers = []; + for (const [identifier, serverTools] of byProvider) { + if (wanted && !wanted.has(identifier)) continue; + servers.push( + create(McpStateServerSchema, { + serverName: identifier, + serverIdentifier: identifier, + tools: serverTools, + status: "connected", + }), + ); + } + + return create(McpStateExecResultSchema, { + result: { case: "success", value: create(McpStateSuccessSchema, { servers }) }, + }); +} + +/** + * Build the neutral response for a hook query: the matching response case with + * every field unset. + * + * This client runs no Cursor hooks, and every field of every response variant + * is `optional` — so an empty response of the right case means "no hook had + * anything to say", which is exactly true. It is NOT the unset-oneof fake + * success: the case itself is set, only the payload is empty. + * + * `ExecuteHookRequest` and `ExecuteHookResponse` are parallel oneofs whose case + * names line up, but the two unions are unrelated to the compiler: a `switch` + * is what makes each pairing individually type-checked, and it forces a + * deliberate branch when a future regen adds a request case. + * + * Returns `null` for a request case this build does not model, which the + * dispatcher answers with `ExecClientThrow` rather than guessing a case. + */ +export function buildNeutralHookResult(request: ExecuteHookRequest | undefined): ExecuteHookResult | null { + let response: ExecuteHookResponse; + switch (request?.request.case) { + case "preCompact": + response = create(ExecuteHookResponseSchema, { + response: { case: "preCompact", value: create(PreCompactRequestResponseSchema, {}) }, + }); + break; + case "subagentStart": + response = create(ExecuteHookResponseSchema, { + response: { case: "subagentStart", value: create(SubagentStartRequestResponseSchema, {}) }, + }); + break; + case "subagentStop": + response = create(ExecuteHookResponseSchema, { + response: { case: "subagentStop", value: create(SubagentStopRequestResponseSchema, {}) }, + }); + break; + case "preToolUse": + response = create(ExecuteHookResponseSchema, { + response: { case: "preToolUse", value: create(PreToolUseRequestResponseSchema, {}) }, + }); + break; + case "postToolUse": + response = create(ExecuteHookResponseSchema, { + response: { case: "postToolUse", value: create(PostToolUseRequestResponseSchema, {}) }, + }); + break; + case "postToolUseFailure": + response = create(ExecuteHookResponseSchema, { + response: { case: "postToolUseFailure", value: create(PostToolUseFailureRequestResponseSchema, {}) }, + }); + break; + case "beforeSubmitPrompt": + response = create(ExecuteHookResponseSchema, { + response: { case: "beforeSubmitPrompt", value: create(BeforeSubmitPromptRequestResponseSchema, {}) }, + }); + break; + case "afterAgentResponse": + response = create(ExecuteHookResponseSchema, { + response: { case: "afterAgentResponse", value: create(AfterAgentResponseRequestResponseSchema, {}) }, + }); + break; + case "afterAgentThought": + response = create(ExecuteHookResponseSchema, { + response: { case: "afterAgentThought", value: create(AfterAgentThoughtRequestResponseSchema, {}) }, + }); + break; + case "stop": + response = create(ExecuteHookResponseSchema, { + response: { case: "stop", value: create(StopRequestResponseSchema, {}) }, + }); + break; + default: + return null; + } + return create(ExecuteHookResultSchema, { response }); +} diff --git a/packages/ai/src/providers/cursor/proto/agent.proto b/packages/ai/src/providers/cursor/proto/agent.proto index 766ee0c94..47c37aeac 100644 --- a/packages/ai/src/providers/cursor/proto/agent.proto +++ b/packages/ai/src/providers/cursor/proto/agent.proto @@ -197,6 +197,7 @@ message ReadLintsToolError { message McpToolError { string error = 1; + string read_tool_def_reminder = 2; } // Result for MCP tool calls (separate from exec results) @@ -212,6 +213,7 @@ message McpToolResult { message McpToolCall { McpArgs args = 1; McpToolResult result = 2; + optional string description = 3; } message SemSearchToolCall { @@ -415,7 +417,21 @@ message ToolCall { TruncatedToolCall truncated_tool_call = 34; StartGrindExecutionToolCall start_grind_execution_tool_call = 35; StartGrindPlanningToolCall start_grind_planning_tool_call = 36; + PiReadToolCall pi_read_tool_call = 61; + PiBashToolCall pi_bash_tool_call = 62; + PiEditToolCall pi_edit_tool_call = 63; + PiWriteToolCall pi_write_tool_call = 64; + PiGrepToolCall pi_grep_tool_call = 65; + PiFindToolCall pi_find_tool_call = 66; + PiLsToolCall pi_ls_tool_call = 67; + ConnectScmToolCall connect_scm_tool_call = 68; + SearchConversationsToolCall search_conversations_tool_call = 69; } + // Modern builds carry the call id on the envelope instead of inside each + // variant's args. Fields 37-53, 55, 56, 58 (further tool variants) and 54 + // (hook_additional_contexts), 59/60 (started_at_ms/completed_at_ms) are not + // modelled and decode into unknown fields; do not reuse those numbers. + optional string tool_call_id = 57; } message TruncatedToolCallArgs { @@ -1098,6 +1114,13 @@ message BackgroundShellSpawnArgs { ShellCommandParsingResult parsing_result = 4; optional SandboxPolicy sandbox_policy = 5; bool enable_write_shell_stdin_tool = 6; + optional string description = 7; + optional CommandClassifierResult classifier_result = 8; + optional ShellOutputNotificationConfig output_notification = 9; + optional SmartModeApproval smart_mode_approval = 10; + optional ShellHookApprovalRequirement hook_approval_requirement = 11; + bool skip_approval = 12; + optional string conversation_id = 13; } // Result of spawning a background shell @@ -1646,6 +1669,7 @@ message ExecClientThrow { uint32 id = 1; string error = 2; optional string stack_trace = 3; + optional string error_code = 4; } message ExecClientHeartbeat { @@ -1703,7 +1727,31 @@ message ExecServerMessage { RecordScreenArgs record_screen_args = 21; ComputerUseArgs computer_use_args = 22; WriteShellStdinArgs write_shell_stdin_args = 23; + ReadArgs redacted_read_args = 29; + McpStateExecArgs mcp_state_exec_args = 36; + ExecuteHookArgs execute_hook_args = 27; + SubagentArgs subagent_args = 28; + ForceBackgroundShellArgs force_background_shell_args = 30; + ForceBackgroundSubagentArgs force_background_subagent_args = 31; + SubagentAwaitArgs subagent_await_args = 37; + SmartModeClassifierArgs smart_mode_classifier_args = 38; + CanvasDiagnosticsArgs canvas_diagnostics_args = 40; + ShellAllowlistPrecheckArgs shell_allowlist_precheck_args = 41; + McpAllowlistPrecheckArgs mcp_allowlist_precheck_args = 42; + WebFetchAllowlistPrecheckArgs web_fetch_allowlist_precheck_args = 43; + GetDiffRequest git_diff_request = 44; + PiReadExecArgs pi_read_args = 45; + PiBashExecArgs pi_bash_args = 46; + PiEditExecArgs pi_edit_args = 47; + PiWriteExecArgs pi_write_args = 48; + PiGrepExecArgs pi_grep_args = 49; + PiFindExecArgs pi_find_args = 50; + PiLsExecArgs pi_ls_args = 51; + ShellArgs mini_swe_agent_bash_args = 52; + ConversationSearchArgs conversation_search_args = 53; + AgentStoreConflictArgs agent_store_conflict_args = 54; } + optional bool accept_hook_additional_contexts = 55; } message ExecClientMessage { @@ -1728,7 +1776,32 @@ message ExecClientMessage { RecordScreenResult record_screen_result = 21; ComputerUseResult computer_use_result = 22; WriteShellStdinResult write_shell_stdin_result = 23; + ReadResult redacted_read_result = 29; + McpStateExecResult mcp_state_exec_result = 36; + ExecuteHookResult execute_hook_result = 27; + SubagentResult subagent_result = 28; + ForceBackgroundShellResult force_background_shell_result = 30; + ForceBackgroundSubagentResult force_background_subagent_result = 31; + SubagentAwaitResult subagent_await_result = 37; + SmartModeClassifierResult smart_mode_classifier_result = 38; + CanvasDiagnosticsResult canvas_diagnostics_result = 40; + ShellAllowlistPrecheckResult shell_allowlist_precheck_result = 41; + McpAllowlistPrecheckResult mcp_allowlist_precheck_result = 42; + WebFetchAllowlistPrecheckResult web_fetch_allowlist_precheck_result = 43; + GetDiffResponse git_diff_response = 44; + PiReadExecResult pi_read_result = 46; + PiBashExecResult pi_bash_result = 47; + PiEditExecResult pi_edit_result = 48; + PiWriteExecResult pi_write_result = 49; + PiGrepExecResult pi_grep_result = 50; + PiFindExecResult pi_find_result = 51; + PiLsExecResult pi_ls_result = 52; + ConversationSearchResult conversation_search_result = 53; + AgentStoreConflictResult agent_store_conflict_result = 54; + ShellResult mini_swe_agent_bash_result = 55; } + optional int32 local_execution_time_ms = 39; + repeated HookAdditionalContext hook_additional_contexts = 45; } message FetchArgs { @@ -1807,6 +1880,7 @@ message GrepArgs { optional bool sort_ascending = 13; string tool_call_id = 14; optional SandboxPolicy sandbox_policy = 15; + optional int32 offset = 16; } message GrepResult { @@ -1848,6 +1922,8 @@ message GrepCountResult { bool client_truncated = 4; // true if we truncated the output from the ripgrep call itself bool ripgrep_truncated = 5; + optional int32 head_limit_applied = 6; + optional int32 offset_applied = 7; } message GrepFileCount { @@ -1864,6 +1940,8 @@ message GrepFilesResult { bool client_truncated = 3; // true if we truncated the output from the ripgrep call itself bool ripgrep_truncated = 4; + optional int32 head_limit_applied = 5; + optional int32 offset_applied = 6; } message GrepContentResult { @@ -1877,6 +1955,8 @@ message GrepContentResult { bool client_truncated = 4; // true if we truncated the output from the ripgrep call itself bool ripgrep_truncated = 5; + optional int32 head_limit_applied = 6; + optional int32 offset_applied = 7; } message GrepFileMatch { @@ -2014,6 +2094,10 @@ message McpArgs { string tool_call_id = 3; string provider_identifier = 4; string tool_name = 5; + optional SmartModeApproval smart_mode_approval = 6; + bool smart_mode_approval_only = 7; + bool skip_approval = 8; + string server_identifier = 9; } message McpResult { @@ -2023,6 +2107,8 @@ message McpResult { McpRejected rejected = 3; McpPermissionDenied permission_denied = 4; McpToolNotFound tool_not_found = 5; + McpServerNotFound server_not_found = 6; + McpApproved approved = 7; } } @@ -2120,6 +2206,8 @@ message ReadMcpResourceExecArgs { string uri = 2; // Optional: when set, the resource will be downloaded to this path relative to the workspace, and the content will not be returned to the model. optional string download_path = 3; + string tool_call_id = 4; + optional SmartModeApproval smart_mode_approval = 5; } // Read MCP resource exec result @@ -2145,6 +2233,7 @@ message ReadMcpResourceSuccess { string text = 5; bytes blob = 6; } + optional OutputLocation output_location = 9; } message ReadMcpResourceError { @@ -2167,6 +2256,7 @@ message McpToolDefinition { string tool_name = 5; string description = 2; bytes input_schema = 3; + optional string input_schema_json = 6; } message McpTools { @@ -2177,6 +2267,7 @@ message McpTools { message McpInstructions { string server_name = 1; string instructions = 2; + string server_identifier = 3; } message McpDescriptor { @@ -2187,11 +2278,17 @@ message McpDescriptor { optional string folder_path = 3; optional string server_use_instructions = 4; repeated McpToolDescriptor tools = 5; + optional string plugin = 7; + optional string marketplace = 8; + optional string plugin_db_id = 9; + optional string marketplace_id = 10; } message McpToolDescriptor { string tool_name = 1; optional string definition_path = 2; + optional string description = 3; + optional string input_schema_json = 5; } message McpFileSystemOptions { @@ -2203,6 +2300,9 @@ message McpFileSystemOptions { message ReadArgs { string path = 1; string tool_call_id = 2; + optional int32 offset = 4; + optional uint32 limit = 5; + optional string encoding_hint = 6; } message ReadResult { @@ -2228,6 +2328,7 @@ message ReadSuccess { string content = 2; bytes data = 5; } + bool range_applied = 8; } message ReadError { @@ -2376,6 +2477,9 @@ message RepositoryIndexingInfo { message RequestContextArgs { optional string notes_session_id = 2; optional string workspace_id = 3; + optional string read_only_pinned_tree_sha = 4; + optional string read_only_plugin_cache_root = 5; + optional bool use_cached = 7; } message RequestContextResult { @@ -2388,6 +2492,7 @@ message RequestContextResult { message RequestContextSuccess { RequestContext request_context = 1; + optional bool served_from_disk_cache = 2; } message RequestContextError { @@ -2807,6 +2912,8 @@ message ShellCommandParsingResult { repeated ShellCommandParsingResult_ExecutableCommand executable_commands = 2; bool has_redirects = 3; bool has_command_substitution = 4; + optional bool all_redirects_are_dev_null = 5; + repeated ShellCommandParsingResult_Redirect redirects = 6; } message ShellCommandParsingResult_ExecutableCommandArg { @@ -2838,6 +2945,13 @@ message ShellArgs { int32 timeout_behavior = 13; // Hard timeout: kill the command after this many ms, even if running in background optional int32 hard_timeout = 14; + optional string description = 15; + optional CommandClassifierResult classifier_result = 16; + bool close_stdin = 17; + optional ShellOutputNotificationConfig output_notification = 18; + optional SmartModeApproval smart_mode_approval = 19; + optional ShellHookApprovalRequirement hook_approval_requirement = 20; + optional string conversation_id = 21; } message ShellResult { @@ -2873,6 +2987,7 @@ message ShellStreamExit { bool aborted = 4; // If aborted is true, this field indicates the reason for the abort optional int32 abort_reason = 5; + optional int32 local_execution_time_ms = 6; } message ShellStreamStart { @@ -2886,6 +3001,7 @@ message ShellStreamBackgrounded { optional uint32 pid = 4; // The ms_to_wait value that was used for backgrounding, for display purposes optional int32 ms_to_wait = 5; + optional ShellBackgroundReason reason = 6; } message ShellStream { @@ -2897,6 +3013,7 @@ message ShellStream { ShellRejected rejected = 5; ShellPermissionDenied permission_denied = 6; ShellStreamBackgrounded backgrounded = 7; + ShellStreamHookContext hook_context = 8; } } @@ -2928,6 +3045,11 @@ message ShellSuccess { optional uint32 pid = 11; // The ms_to_wait value used for backgrounding (for display in result) optional int32 ms_to_wait = 12; + optional int32 local_execution_time_ms = 13; + optional ShellBackgroundReason background_reason = 14; + optional string output_head = 15; + optional string output_tail = 16; + optional uint32 elided_chars = 17; } message ShellFailure { @@ -2947,6 +3069,10 @@ message ShellFailure { optional int32 abort_reason = 10; // Whether the command was aborted (by user or timeout) bool aborted = 11; + optional int32 local_execution_time_ms = 12; + optional string output_head = 13; + optional string output_tail = 14; + optional uint32 elided_chars = 15; } message ShellTimeout { @@ -3242,6 +3368,7 @@ message WriteArgs { bool return_file_content_after_write = 4; // Raw binary data to write. When set, file_text is ignored and the bytes are written directly without any text processing (e.g., line ending normalization). bytes file_bytes = 5; + optional string encoding_hint = 6; } message WriteResult { @@ -3524,3 +3651,883 @@ service LifecycleService { // Asks worker to exit(0) so that a new worker can take his place rpc RenewInstance(Empty) returns (Empty); } + +// ===== Pi exec tools (modern Cursor CLI, ExecServerMessage 45-51) ===== + +message PiTruncation { + bool truncated = 1; + string truncated_by = 2; + uint32 total_lines = 3; + uint32 output_lines = 4; + uint32 output_bytes = 5; + optional uint32 max_lines = 6; + optional uint32 max_bytes = 7; + bool first_line_exceeds_limit = 8; + bool last_line_partial = 9; +} + +message PiEditReplacement { + string old_text = 1; + string new_text = 2; +} + +message PiReadExecArgs { + string path = 1; + optional int32 offset = 2; + optional int32 limit = 3; +} + +message PiReadExecSuccess { + string output = 1; + optional PiTruncation truncation = 2; +} + +message PiReadExecError { + string error = 1; +} + +message PiReadExecResult { + oneof result { + PiReadExecSuccess success = 1; + PiReadExecError error = 2; + } +} + +message PiBashExecArgs { + string command = 1; + optional double timeout = 2; +} + +message PiBashExecSuccess { + string output = 1; + optional PiTruncation truncation = 2; + optional string full_output_path = 3; +} + +message PiBashExecError { + string error = 1; + optional PiTruncation truncation = 2; + optional string full_output_path = 3; +} + +message PiBashExecResult { + oneof result { + PiBashExecSuccess success = 1; + PiBashExecError error = 2; + } +} + +message PiEditExecArgs { + string path = 1; + repeated PiEditReplacement edits = 2; +} + +message PiEditExecSuccess { + string output = 1; + string diff = 2; + string patch = 3; + optional uint32 first_changed_line = 4; +} + +message PiEditExecError { + string error = 1; +} + +message PiEditExecRejected { + string reason = 1; +} + +message PiEditExecResult { + oneof result { + PiEditExecSuccess success = 1; + PiEditExecError error = 2; + PiEditExecRejected rejected = 3; + } +} + +message PiWriteExecArgs { + string path = 1; + string content = 2; +} + +message PiWriteExecSuccess { + string output = 1; +} + +message PiWriteExecError { + string error = 1; +} + +message PiWriteExecRejected { + string reason = 1; +} + +message PiWriteExecResult { + oneof result { + PiWriteExecSuccess success = 1; + PiWriteExecError error = 2; + PiWriteExecRejected rejected = 3; + } +} + +message PiGrepExecArgs { + string pattern = 1; + optional string path = 2; + optional string glob = 3; + optional bool ignore_case = 4; + optional bool literal = 5; + optional int32 context = 6; + optional int32 limit = 7; +} + +message PiGrepExecSuccess { + string output = 1; + optional PiTruncation truncation = 2; + optional uint32 match_limit_reached = 3; + bool lines_truncated = 4; +} + +message PiGrepExecError { + string error = 1; +} + +message PiGrepExecResult { + oneof result { + PiGrepExecSuccess success = 1; + PiGrepExecError error = 2; + } +} + +message PiFindExecArgs { + string pattern = 1; + optional string path = 2; + optional int32 limit = 3; +} + +message PiFindExecSuccess { + string output = 1; + optional PiTruncation truncation = 2; + optional uint32 result_limit_reached = 3; +} + +message PiFindExecError { + string error = 1; +} + +message PiFindExecResult { + oneof result { + PiFindExecSuccess success = 1; + PiFindExecError error = 2; + } +} + +message PiLsExecArgs { + optional string path = 1; + optional int32 limit = 2; +} + +message PiLsExecSuccess { + string output = 1; + optional PiTruncation truncation = 2; + optional uint32 entry_limit_reached = 3; +} + +message PiLsExecError { + string error = 1; +} + +message PiLsExecResult { + oneof result { + PiLsExecSuccess success = 1; + PiLsExecError error = 2; + } +} + +// ===== MCP (modern) ===== + +message McpServerNotFound { + string name = 1; + repeated string available_servers = 2; +} + +message McpApproved { +} + +message McpStateExecArgs { + repeated string server_identifiers = 1; + bool kick_only = 2; +} + +message McpStateServer { + string server_name = 1; + string server_identifier = 2; + optional string plugin = 3; + optional string marketplace = 4; + repeated McpToolDefinition tools = 5; + repeated McpInstructions instructions = 6; + optional string status = 7; +} + +message McpStateSuccess { + repeated McpStateServer servers = 1; +} + +message McpStateError { + string error = 1; +} + +message McpStateRejected { + string reason = 1; +} + +message McpStateExecResult { + oneof result { + McpStateSuccess success = 1; + McpStateError error = 2; + McpStateRejected rejected = 3; + } +} + +// ===== Allowlist prechecks ===== + +enum CommandClassifierResult_SuggestedSandboxMode { + SUGGESTED_SANDBOX_MODE_UNSPECIFIED = 0; + SUGGESTED_SANDBOX_MODE_SANDBOX = 1; + SUGGESTED_SANDBOX_MODE_NO_SANDBOX = 2; + SUGGESTED_SANDBOX_MODE_UNDETERMINED = 3; +} + +message CommandClassifierResult_ClassifiedCommand { + string name = 1; + repeated string arguments = 2; + optional string suggested_allowlist_entry = 3; + repeated string subcommand_tokens = 4; +} + +message CommandClassifierResult { + repeated CommandClassifierResult_ClassifiedCommand commands = 1; + CommandClassifierResult_SuggestedSandboxMode suggested_sandbox_mode = 2; + bool classification_failed = 3; +} + +message ShellCommandParsingResult_Redirect { + string operator = 1; + repeated uint32 destination_fds = 2; + string target_node_type = 3; + optional string target_text = 4; +} + +message ShellAllowlistPrecheckArgs { + string command = 1; + string working_directory = 2; + ShellCommandParsingResult parsing_result = 3; + optional CommandClassifierResult classifier_result = 4; + optional string tool_call_id = 5; +} + +message ShellAllowlistPrecheckResult { + bool allowlisted = 1; +} + +message McpAllowlistPrecheckArgs { + string provider_identifier = 1; + string tool_name = 2; + optional string tool_call_id = 3; +} + +message McpAllowlistPrecheckResult { + bool allowlisted = 1; +} + +message WebFetchAllowlistPrecheckArgs { + string url = 1; + optional string tool_call_id = 2; +} + +message WebFetchAllowlistPrecheckResult { + bool allowlisted = 1; +} + +// ===== Shell (modern additions) ===== + +message SmartModeApproval { + string request_id = 1; + string reason = 2; +} + +message ShellOutputNotificationConfig { + string pattern = 1; + string reason = 2; + optional double debounce = 3; + optional int32 notification_limit = 4; +} + +enum ShellHookApprovalRequirement_Kind { + SHELL_HOOK_APPROVAL_REQUIREMENT_KIND_UNSPECIFIED = 0; + SHELL_HOOK_APPROVAL_REQUIREMENT_KIND_FORCE_PROMPT = 1; +} + +message ShellHookApprovalRequirement { + ShellHookApprovalRequirement_Kind kind = 1; + optional string reason = 2; +} + +enum ShellBackgroundReason { + SHELL_BACKGROUND_REASON_UNSPECIFIED = 0; + SHELL_BACKGROUND_REASON_TIMEOUT = 1; + SHELL_BACKGROUND_REASON_USER_REQUEST = 2; +} + +enum ForceBackgroundShellStatus { + FORCE_BACKGROUND_SHELL_STATUS_UNSPECIFIED = 0; + FORCE_BACKGROUND_SHELL_STATUS_ACCEPTED = 1; + FORCE_BACKGROUND_SHELL_STATUS_NOT_FOUND = 2; +} + +message ForceBackgroundShellArgs { + string tool_call_id = 1; +} + +message ForceBackgroundShellResult { + ForceBackgroundShellStatus status = 1; + optional ShellResult shell_result = 2; +} + +message HookAdditionalContext { + string hook_event_name = 1; + string content = 2; +} + +message ShellStreamHookContext { + repeated HookAdditionalContext hook_additional_contexts = 1; +} + +// ===== Subagents ===== + +enum SubagentBackgroundReason { + SUBAGENT_BACKGROUND_REASON_UNSPECIFIED = 0; + SUBAGENT_BACKGROUND_REASON_AGENT_REQUEST = 1; + SUBAGENT_BACKGROUND_REASON_USER_REQUEST = 2; + SUBAGENT_BACKGROUND_REASON_QUEUED_FOLLOW_UP = 3; +} + +enum ForceBackgroundSubagentStatus { + FORCE_BACKGROUND_SUBAGENT_STATUS_UNSPECIFIED = 0; + FORCE_BACKGROUND_SUBAGENT_STATUS_ACCEPTED = 1; + FORCE_BACKGROUND_SUBAGENT_STATUS_NOT_FOUND = 2; +} + +// Subset: only the fields this client reads. Upstream also defines model_id=3, readonly=5, +// resume_agent_id=6, run_in_background=7, continuation_config=8, parent_conversation_id=9, +// credentials 10-12, interrupt=13, mode=14, fork_agent_id=15, root_parent_conversation_id=16, +// selected_context=17, direct_meta_parent_child_subagent=18, environment=19, cloud_base_branch=20. +// Those decode into unknown fields; do not reuse these numbers. +message SubagentArgs { + string tool_call_id = 1; + string subagent_type = 2; + string prompt = 4; +} + +message SubagentSuccess { + string agent_id = 1; + optional string final_message = 2; + int32 tool_call_count = 3; + SubagentBackgroundReason background_reason = 4; + optional string transcript_path = 5; +} + +message SubagentError { + optional string agent_id = 1; + string error = 2; +} + +message SubagentResult { + oneof result { + SubagentSuccess success = 1; + SubagentError error = 2; + } +} + +message SubagentAwaitArgs { + string agent_id = 1; + uint32 timeout_ms = 2; +} + +message SubagentAwaitComplete { + string agent_id = 1; + optional string transcript_path = 2; + int32 tool_call_count = 3; + optional string final_message = 4; +} + +message SubagentAwaitStillRunning { + string agent_id = 1; + optional string transcript_path = 2; +} + +message SubagentAwaitNotFound { + string agent_id = 1; +} + +message SubagentAwaitError { + optional string agent_id = 1; + string error = 2; +} + +message SubagentAwaitResult { + oneof result { + SubagentAwaitComplete complete = 1; + SubagentAwaitStillRunning still_running = 2; + SubagentAwaitNotFound not_found = 3; + SubagentAwaitError error = 4; + } +} + +message ForceBackgroundSubagentArgs { + string tool_call_id = 1; +} + +message ForceBackgroundSubagentResult { + ForceBackgroundSubagentStatus status = 1; +} + +// ===== Hooks ===== + +// Payload not modelled: this client runs no Cursor hooks and only reads the oneof case. +message PreCompactRequestQuery { +} + +// Payload not modelled: this client runs no Cursor hooks and only reads the oneof case. +message SubagentStartRequestQuery { +} + +// Payload not modelled: this client runs no Cursor hooks and only reads the oneof case. +message SubagentStopRequestQuery { +} + +// Payload not modelled: this client runs no Cursor hooks and only reads the oneof case. +message PreToolUseRequestQuery { +} + +// Payload not modelled: this client runs no Cursor hooks and only reads the oneof case. +message PostToolUseRequestQuery { +} + +// Payload not modelled: this client runs no Cursor hooks and only reads the oneof case. +message PostToolUseFailureRequestQuery { +} + +// Payload not modelled: this client runs no Cursor hooks and only reads the oneof case. +message BeforeSubmitPromptRequestQuery { +} + +// Payload not modelled: this client runs no Cursor hooks and only reads the oneof case. +message AfterAgentResponseRequestQuery { +} + +// Payload not modelled: this client runs no Cursor hooks and only reads the oneof case. +message AfterAgentThoughtRequestQuery { +} + +// Payload not modelled: this client runs no Cursor hooks and only reads the oneof case. +message StopRequestQuery { +} + +message PreCompactRequestResponse { + optional string user_message = 1; +} + +message SubagentStartRequestResponse { + optional string permission = 1; + optional string user_message = 2; + optional string additional_context = 3; +} + +message SubagentStopRequestResponse { + optional string followup_message = 1; + optional string additional_context = 2; +} + +message PreToolUseRequestResponse { + optional string permission = 1; + optional string user_message = 2; + optional string agent_message = 3; + optional string updated_input = 4; + optional string additional_context = 5; +} + +message PostToolUseRequestResponse { + optional string additional_context = 1; +} + +message PostToolUseFailureRequestResponse { + optional string additional_context = 1; +} + +message BeforeSubmitPromptRequestResponse { + optional bool continue = 1; + optional string user_message = 2; + optional string additional_context = 3; +} + +message AfterAgentResponseRequestResponse { +} + +message AfterAgentThoughtRequestResponse { +} + +message StopRequestResponse { + optional string followup_message = 1; +} + +message ExecuteHookRequest { + oneof request { + PreCompactRequestQuery pre_compact = 1; + SubagentStartRequestQuery subagent_start = 2; + SubagentStopRequestQuery subagent_stop = 3; + PreToolUseRequestQuery pre_tool_use = 4; + PostToolUseRequestQuery post_tool_use = 5; + PostToolUseFailureRequestQuery post_tool_use_failure = 6; + BeforeSubmitPromptRequestQuery before_submit_prompt = 7; + AfterAgentResponseRequestQuery after_agent_response = 8; + AfterAgentThoughtRequestQuery after_agent_thought = 9; + StopRequestQuery stop = 11; + } +} + +message ExecuteHookResponse { + oneof response { + PreCompactRequestResponse pre_compact = 1; + SubagentStartRequestResponse subagent_start = 2; + SubagentStopRequestResponse subagent_stop = 3; + PreToolUseRequestResponse pre_tool_use = 4; + PostToolUseRequestResponse post_tool_use = 5; + PostToolUseFailureRequestResponse post_tool_use_failure = 6; + BeforeSubmitPromptRequestResponse before_submit_prompt = 7; + AfterAgentResponseRequestResponse after_agent_response = 8; + AfterAgentThoughtRequestResponse after_agent_thought = 9; + StopRequestResponse stop = 11; + } +} + +message ExecuteHookArgs { + ExecuteHookRequest request = 1; +} + +message ExecuteHookResult { + ExecuteHookResponse response = 1; +} + +// ===== Smart-mode classifier ===== + +enum SmartModeClassifierDecision { + SMART_MODE_CLASSIFIER_DECISION_UNSPECIFIED = 0; + SMART_MODE_CLASSIFIER_DECISION_ALLOW = 1; + SMART_MODE_CLASSIFIER_DECISION_BLOCK = 2; +} + +// Subset: arguments=2 (google.protobuf.Struct) decodes into unknown fields; do not reuse. +message SmartModeRiskTarget { + string action = 1; +} + +message SmartModeClassifierConversationMessage { + string role = 1; + string content = 2; +} + +message SmartModeClassifierArgs { + string tool_call_id = 1; + optional string parent_conversation_id = 2; + SmartModeRiskTarget target = 3; + repeated SmartModeClassifierConversationMessage conversation_context = 4; +} + +message SmartModeClassifierSuccess { + SmartModeClassifierDecision decision = 1; + optional string block_reason = 2; +} + +message SmartModeClassifierError { + string error = 1; +} + +message SmartModeClassifierResult { + oneof result { + SmartModeClassifierSuccess success = 1; + SmartModeClassifierError error = 2; + } +} + +// ===== Canvas diagnostics ===== + +message CanvasDiagnosticsArgs { + string path = 1; + string tool_call_id = 2; +} + +message CanvasDiagnosticsSuccess { + string path = 1; + repeated Diagnostic diagnostics = 2; +} + +message CanvasDiagnosticsError { + string path = 1; + string error = 2; +} + +message CanvasDiagnosticsResult { + oneof result { + CanvasDiagnosticsSuccess success = 1; + CanvasDiagnosticsError error = 2; + } +} + +// ===== Conversation search ===== + +enum ConversationSearchSource { + CONVERSATION_SEARCH_SOURCE_UNSPECIFIED = 0; + CONVERSATION_SEARCH_SOURCE_LOCAL = 1; + CONVERSATION_SEARCH_SOURCE_CLOUD_CACHE = 2; +} + +message ConversationSearchHit { + string conversation_id = 1; + string title = 2; + ConversationSearchSource source = 3; + int64 updated_at_ms = 4; + optional string snippet = 5; +} + +message ConversationSearchSuccess { + repeated ConversationSearchHit hits = 1; + bool truncated = 2; + bool partial = 3; + bool rebuilding = 4; +} + +message ConversationSearchError { + string error = 1; +} + +message ConversationSearchArgs { + string query = 1; + string tool_call_id = 2; + optional int32 limit = 3; +} + +message ConversationSearchResult { + oneof result { + ConversationSearchSuccess success = 1; + ConversationSearchError error = 2; + } +} + +// ===== Agent store conflicts ===== + +message AgentStoreConflictCursor { + string journal_epoch = 1; + uint64 seq = 2; + string last_event_id = 3; +} + +message AgentStoreConflictEvent { + uint32 v = 1; + string event_id = 2; + string journal_epoch = 3; + uint64 seq = 4; + uint64 ts_ms = 5; + string kind = 6; + optional string store_id = 7; + optional string original_rel_path = 8; + optional string conflict_rel_path = 9; + optional string original_abs_path = 10; + optional string conflict_abs_path = 11; + optional uint64 preserved_bytes = 12; +} + +message AgentStoreConflictSuccess { + repeated AgentStoreConflictEvent events = 1; + AgentStoreConflictCursor next_cursor = 2; + bool gap = 3; +} + +message AgentStoreConflictError { + string error = 1; +} + +message AgentStoreConflictArgs { + optional AgentStoreConflictCursor cursor = 1; + optional bool advance = 2; +} + +message AgentStoreConflictResult { + oneof result { + AgentStoreConflictSuccess success = 1; + AgentStoreConflictError error = 2; + } +} + +// ===== Git diff (upstream aiserver.v1; declared here in agent.v1 — the exec channel is binary +// protobuf, so field numbers, not type names, define wire compatibility) ===== + +enum GetDiffRequest_OutputFormat { + OUTPUT_FORMAT_UNSPECIFIED = 0; + OUTPUT_FORMAT_NAME_STATUS = 1; + OUTPUT_FORMAT_NAME_STATUS_AND_NUMSTAT = 2; + OUTPUT_FORMAT_FILE_DIFFS = 3; + OUTPUT_FORMAT_DIFFS_WITH_BEFORE_AND_AFTER = 4; +} + +enum GitDiff_DiffType { + DIFF_TYPE_UNSPECIFIED = 0; + DIFF_TYPE_DIFF_TO_HEAD = 1; + DIFF_TYPE_DIFF_FROM_BRANCH_TO_MAIN = 2; +} + +message FileDiff_Chunk { + string content = 1; + repeated string lines = 2; + int32 old_start = 3; + int32 old_lines = 4; + int32 new_start = 5; + int32 new_lines = 6; +} + +message FileDiff { + int32 added = 4; + int32 removed = 5; + string from = 1; + string to = 2; + repeated FileDiff_Chunk chunks = 3; + optional string before_file_contents = 6; + optional string after_file_contents = 7; + optional bool is_generated = 8; +} + +message GitDiff { + repeated FileDiff diffs = 1; + GitDiff_DiffType diff_type = 2; +} + +message GetDiffResponse_SubmoduleDiff { + string relative_path = 1; + GitDiff diff = 2; + bool errored = 3; +} + +message GetDiffRequest { + string cwd = 1; + string ref = 2; + string base_ref = 3; + bool merge_base = 4; + repeated string target_paths = 5; + optional int32 unified_context_lines = 6; + int32 max_untracked_files = 7; + int32 submodule_recurse_depth = 9; + bool include_space_changes = 10; + bool committed_only = 11; + bool compute_patch_id = 12; + optional bool return_head_sha = 13; + optional int32 max_response_bytes = 14; + optional GetDiffRequest_OutputFormat output_format = 8; +} + +message GetDiffResponse { + GitDiff diff = 1; + repeated GetDiffResponse_SubmoduleDiff submodule_diffs = 2; + optional string patch_id = 3; + optional string head_sha = 4; + optional bool has_uncommitted_changes = 5; +} + +// ===== Pi tool calls (ToolCall 61-67) ===== +// +// The streamed `ToolCall` wrappers for the Pi exec frames. Their `args`/`result` +// payloads are field-identical to the `Pi*Exec*` messages above (verified against +// the modern CLI's `pi_*_tool_pb.js`), and the exec channel is binary protobuf, +// so field numbers — not type names — define wire compatibility. Reusing the +// exec messages keeps one shape per Pi tool instead of two that must stay in sync. + +message PiReadToolCall { + PiReadExecArgs args = 1; + PiReadExecResult result = 2; +} + +message PiBashToolCall { + PiBashExecArgs args = 1; + PiBashExecResult result = 2; +} + +message PiEditToolCall { + PiEditExecArgs args = 1; + PiEditExecResult result = 2; +} + +message PiWriteToolCall { + PiWriteExecArgs args = 1; + PiWriteExecResult result = 2; +} + +message PiGrepToolCall { + PiGrepExecArgs args = 1; + PiGrepExecResult result = 2; +} + +message PiFindToolCall { + PiFindExecArgs args = 1; + PiFindExecResult result = 2; +} + +message PiLsToolCall { + PiLsExecArgs args = 1; + PiLsExecResult result = 2; +} + +// ===== Conversation search / SCM tool calls (ToolCall 68-69) ===== + +message SearchConversationsToolCall { + ConversationSearchArgs args = 1; + ConversationSearchResult result = 2; +} + +message ConnectScmGithubRepository { + string owner = 1; + string repo = 2; +} + +message ConnectScmGithub { + ConnectScmGithubRepository repository = 1; + optional string ghe_application = 2; +} + +message ConnectScmArgs { + string tool_call_id = 1; + oneof target { + ConnectScmGithub github = 2; + } +} + +message ConnectScmSuccess { +} + +message ConnectScmError { + string error = 1; +} + +message ConnectScmRejected { + string reason = 1; +} + +message ConnectScmResult { + oneof result { + ConnectScmSuccess success = 1; + ConnectScmError error = 2; + ConnectScmRejected rejected = 3; + } +} + +message ConnectScmToolCall { + ConnectScmArgs args = 1; + ConnectScmResult result = 2; +} diff --git a/packages/ai/src/registry/exa.ts b/packages/ai/src/registry/exa.ts new file mode 100644 index 000000000..aaa5fb70c --- /dev/null +++ b/packages/ai/src/registry/exa.ts @@ -0,0 +1,19 @@ +import { createApiKeyLogin } from "./api-key-login"; +import type { OAuthLoginCallbacks } from "./oauth/types"; +import type { ProviderDefinition } from "./types"; + +export const loginExa = createApiKeyLogin({ + providerLabel: "Exa", + authUrl: "https://dashboard.exa.ai/api-keys", + instructions: "Create or copy your API key from the Exa dashboard.", + promptMessage: "Paste your Exa API key", + placeholder: "API key", + validation: null, +}); + +export const exaProvider = { + id: "exa", + name: "Exa", + envKeys: "EXA_API_KEY", + login: (cb: OAuthLoginCallbacks) => loginExa(cb), +} as const satisfies ProviderDefinition; diff --git a/packages/ai/src/registry/registry.ts b/packages/ai/src/registry/registry.ts index 58c45906c..20c74bf2e 100644 --- a/packages/ai/src/registry/registry.ts +++ b/packages/ai/src/registry/registry.ts @@ -12,6 +12,7 @@ import { coreWeaveProvider } from "./coreweave"; import { cursorProvider } from "./cursor"; import { deepseekProvider } from "./deepseek"; import { devinProvider } from "./devin"; +import { exaProvider } from "./exa"; import { firepassProvider } from "./firepass"; import { fireworksProvider } from "./fireworks"; import { githubCopilotProvider } from "./github-copilot"; @@ -93,6 +94,7 @@ const ALL = [ googleAntigravityProvider, googleGeminiCliProvider, openaiCodexDeviceProvider, + xaiProvider, xaiOauthProvider, gitlabDuoProvider, gitLabDuoWorkflowProvider, @@ -138,6 +140,7 @@ const ALL = [ opencodeGoProvider, tavilyProvider, kagiProvider, + exaProvider, parallelProvider, ollamaProvider, ollamaCloudProvider, @@ -147,7 +150,6 @@ const ALL = [ openaiProvider, googleProvider, googleVertexProvider, - xaiProvider, groqProvider, mistralProvider, minimaxProvider, diff --git a/packages/ai/src/registry/xai.ts b/packages/ai/src/registry/xai.ts index 538f9ec3e..be34cf9a4 100644 --- a/packages/ai/src/registry/xai.ts +++ b/packages/ai/src/registry/xai.ts @@ -1,6 +1,22 @@ +import { createApiKeyLogin } from "./api-key-login"; +import type { OAuthLoginCallbacks } from "./oauth/types"; import type { ProviderDefinition } from "./types"; +export const loginXAI = createApiKeyLogin({ + providerLabel: "xAI", + authUrl: "https://console.x.ai/team/default/api-keys", + instructions: "Create or copy your API key from the xAI Console", + promptMessage: "Paste your xAI API key", + placeholder: "xai-...", + validation: { + kind: "models-endpoint", + provider: "xAI", + modelsUrl: "https://api.x.ai/v1/models", + }, +}); + export const xaiProvider = { id: "xai", - name: "xAI", + name: "xAI API", + login: (cb: OAuthLoginCallbacks) => loginXAI(cb), } as const satisfies ProviderDefinition; diff --git a/packages/ai/src/stream.ts b/packages/ai/src/stream.ts index 8fde5d432..1cd31d2a9 100644 --- a/packages/ai/src/stream.ts +++ b/packages/ai/src/stream.ts @@ -691,7 +691,6 @@ type KeyResolver = string | (() => string | undefined); const LEGACY_ENV_KEYS: Record = { // Non-provider / search-tool keys and API-name keys not modeled as registry provider defs. "azure-openai-responses": "AZURE_OPENAI_API_KEY", - exa: "EXA_API_KEY", jina: "JINA_API_KEY", brave: "BRAVE_API_KEY", tinyfish: "TINYFISH_API_KEY", diff --git a/packages/ai/src/types.ts b/packages/ai/src/types.ts index 539ad2e5b..fc822e798 100644 --- a/packages/ai/src/types.ts +++ b/packages/ai/src/types.ts @@ -11,6 +11,20 @@ import type { LsArgs, LsResult, McpResult, + PiBashExecArgs, + PiBashExecResult, + PiEditExecArgs, + PiEditExecResult, + PiFindExecArgs, + PiFindExecResult, + PiGrepExecArgs, + PiGrepExecResult, + PiLsExecArgs, + PiLsExecResult, + PiReadExecArgs, + PiReadExecResult, + PiWriteExecArgs, + PiWriteExecResult, ReadArgs, ReadResult, ShellArgs, @@ -938,6 +952,14 @@ export interface CursorMcpCall { toolCallId: string; args: Record; rawArgs: Record; + /** + * The frame asks only whether this call would be permitted — it must not + * run. The server sends it to resolve a smart-mode approval decision ahead + * of the real invocation, and answers with the dedicated `approved` + * variant, so executing here would fire a side-effecting tool the user has + * not yet been asked about (and fire it twice once the real call arrives). + */ + approvalOnly?: boolean; } export interface CursorTodoSnapshotItem { @@ -989,6 +1011,54 @@ export interface CursorShellStreamCallbacks { onStderr(data: string): void; } +/** + * A modern Pi exec frame plus the call id the dispatcher minted for it. + * + * Unlike the legacy exec args (`ReadArgs`, `ShellArgs`, ...), the Pi frames + * carry no `tool_call_id` field: on modern builds the id rides the streamed + * `ToolCall` envelope (`ToolCall.tool_call_id = 57`) instead of each variant's + * args. The exec channel has no access to that envelope, so the dispatcher + * mints an id and hands it to the handler, keeping the synthesized transcript + * block and its paired `toolResult` on the same key. + */ +export interface CursorPiCall { + args: TArgs; + toolCallId: string; +} + +/** One resource a host's MCP servers advertise. */ +export interface CursorMcpResource { + uri: string; + name?: string; + description?: string; + mimeType?: string; + /** The server advertising it; Cursor addresses reads by this name. */ + server: string; +} + +/** + * The content of one resource read. + * + * `text` and `blob` are the wire's content oneof: exactly one is sent, with + * `text` winning when a host supplies both. A download instead sets + * `downloadPath` and no content at all — the model is told where the file + * landed rather than being handed its bytes. + */ +export interface CursorMcpResourceContent { + uri: string; + name?: string; + description?: string; + mimeType?: string; + text?: string; + blob?: Uint8Array; + /** + * Where the host wrote the resource, workspace-relative, when the frame + * asked for a download. Set this INSTEAD of `text`/`blob`: the wire + * contract is that a download returns no content to the model. + */ + downloadPath?: string; +} + export interface CursorExecHandlers { read?: (args: ReadArgs) => Promise>; ls?: (args: LsArgs) => Promise>; @@ -1002,6 +1072,51 @@ export interface CursorExecHandlers { ) => Promise>; diagnostics?: (args: DiagnosticsArgs) => Promise>; mcp?: (call: CursorMcpCall) => Promise>; + /** + * Answers "would this MCP call be permitted", without running it. + * + * A modern `mcpArgs` frame carrying `smart_mode_approval_only` asks for the + * permission decision alone, ahead of the real invocation. Executing the + * tool to answer it would fire a side effect the user never approved — and + * fire it twice once the real call arrives. + * + * `true` only when the host's policy resolves to a definite allow. A pending + * prompt is `false`: it can only be answered interactively at execution + * time, and there is no "ask me later" reply in this frame's result. When no + * handler is registered the provider refuses, since it cannot decide. + */ + mcpApprovalPreflight?: (call: CursorMcpCall) => Promise; + /** + * Modern Cursor CLI Pi tool frames (`ExecServerMessage` 45-51). They are a + * distinct frame family from the legacy `readArgs`/`shellArgs`/... set, not + * an alias: different args, different result oneofs, and no `tool_call_id`. + */ + piRead?: (call: CursorPiCall) => Promise>; + piBash?: (call: CursorPiCall) => Promise>; + piEdit?: (call: CursorPiCall) => Promise>; + piWrite?: (call: CursorPiCall) => Promise>; + piGrep?: (call: CursorPiCall) => Promise>; + piFind?: (call: CursorPiCall) => Promise>; + piLs?: (call: CursorPiCall) => Promise>; + /** + * The resources the host's MCP servers advertise, optionally filtered to one + * server. Without a handler the provider answers an empty catalog, which + * hides resources a host is in fact holding live connections to. + */ + listMcpResources?: (args: { server?: string }) => Promise; + /** + * Read one resource. `null` means the server or uri is genuinely unknown, + * which the provider answers as `not_found`; throwing surfaces as `error`. + */ + readMcpResource?: (args: { + server: string; + uri: string; + /** + * When set, write the resource here (workspace-relative) and return + * `downloadPath` instead of content. + */ + downloadPath?: string; + }) => Promise; /** Mirror Cursor's server-owned todo list into local session state. */ todoSync?: CursorTodoSyncHandler; onToolResult?: CursorToolResultHandler; diff --git a/packages/ai/src/usage/umans.ts b/packages/ai/src/usage/umans.ts new file mode 100644 index 000000000..c32675d61 --- /dev/null +++ b/packages/ai/src/usage/umans.ts @@ -0,0 +1,192 @@ +import { ProviderHttpError } from "../error"; +import type { + UsageAmount, + UsageFetchContext, + UsageFetchParams, + UsageLimit, + UsageProvider, + UsageReport, + UsageStatus, + UsageWindow, +} from "../usage"; +import { isRecord } from "../utils"; + +const UMANS_PROVIDER = "umans"; +const DEFAULT_ENDPOINT = "https://api.code.umans.ai"; +const USAGE_PATH = "/v1/usage"; +const FIVE_HOURS_MS = 5 * 60 * 60 * 1000; + +/** Umans `GET /v1/usage` response (subset; extras ignored). */ +interface UmansUsagePayload { + plan?: { display_name?: string }; + limits?: { + requests?: { limit?: number; hard_cap?: number | null; window_seconds?: number }; + concurrency?: { limit?: number; hard_cap?: number | null }; + }; + usage?: { + requests_in_window?: number; + remaining_requests?: number; + concurrent_sessions?: number; + tokens_in?: number; + tokens_out?: number; + priority?: { low?: boolean }; + }; +} + +function normalizeBaseUrl(baseUrl?: string): string { + if (!baseUrl?.trim()) return DEFAULT_ENDPOINT; + const trimmed = baseUrl.trim(); + // Strip a trailing `/v1` (with optional surrounding slashes) so the usage + // path doesn't double it, but preserve any preceding path prefix (e.g. a + // path-mounted gateway like `https://gateway.example/team/umans/v1`). + const withoutTrailingSlash = trimmed.replace(/\/+$/, ""); + return withoutTrailingSlash.replace(/\/v1$/i, "") || DEFAULT_ENDPOINT; +} + +function toFiniteNumber(value: unknown): number | undefined { + if (typeof value !== "number" || !Number.isFinite(value)) return undefined; + return value; +} + +function resolveStatus(usedFraction: number | undefined): UsageStatus | undefined { + if (usedFraction === undefined) return undefined; + if (usedFraction >= 1) return "exhausted"; + if (usedFraction >= 0.9) return "warning"; + return "ok"; +} + +function buildAmount(args: { + used: number | undefined; + limit: number | undefined; + remaining: number | undefined; + unit: UsageAmount["unit"]; +}): UsageAmount { + const used = args.used; + const limit = args.limit; + const usedFraction = used !== undefined && limit !== undefined && limit > 0 ? Math.min(used / limit, 1) : undefined; + const remainingFraction = usedFraction !== undefined ? Math.max(1 - usedFraction, 0) : undefined; + return { + used, + limit, + remaining: args.remaining, + usedFraction, + remainingFraction, + unit: args.unit, + }; +} + +function buildRequestsLimit(payload: UmansUsagePayload, provider: string): UsageLimit | null { + const limit = toFiniteNumber(payload.limits?.requests?.limit); + const windowSeconds = toFiniteNumber(payload.limits?.requests?.window_seconds); + const used = toFiniteNumber(payload.usage?.requests_in_window); + const remaining = toFiniteNumber(payload.usage?.remaining_requests); + if (limit === undefined && used === undefined) return null; + const amount = buildAmount({ used, limit, remaining, unit: "requests" }); + // Rolling window: each request ages out `window_seconds` after it fired, so + // there is no single reset timestamp. Surface the window size + label only. + // `window.id` is `"5h"` to match the status-line usage segment's window-id + // contract (it only recognizes `"5h"`/`"7d"`); `label` stays human-readable. + const window: UsageWindow = { + id: "5h", + label: "rolling 5h", + durationMs: windowSeconds ? windowSeconds * 1000 : FIVE_HOURS_MS, + }; + return { + id: "umans:requests", + label: "Requests (rolling 5h)", + scope: { provider, windowId: window.id, shared: true }, + window, + amount, + status: resolveStatus(amount.usedFraction), + }; +} + +function buildConcurrencyLimit(payload: UmansUsagePayload, provider: string): UsageLimit | null { + const limit = toFiniteNumber(payload.limits?.concurrency?.limit); + const used = toFiniteNumber(payload.usage?.concurrent_sessions); + if (limit === undefined && used === undefined) return null; + const amount = buildAmount({ used, limit, remaining: undefined, unit: "requests" }); + return { + id: "umans:concurrency", + label: "Concurrency", + // Concurrency is instantaneous, not windowed. + scope: { provider, windowId: "concurrency" }, + amount, + status: resolveStatus(amount.usedFraction), + }; +} + +async function fetchUmansUsage(params: UsageFetchParams, ctx: UsageFetchContext): Promise { + if (params.provider !== UMANS_PROVIDER) return null; + const credential = params.credential; + if (credential.type !== "api_key" || !credential.apiKey) return null; + + const baseUrl = normalizeBaseUrl(params.baseUrl); + const url = `${baseUrl}${USAGE_PATH}`; + const headers: Record = { + authorization: `Bearer ${credential.apiKey}`, + accept: "application/json", + }; + + let payload: UmansUsagePayload | null = null; + try { + const response = await ctx.fetch(url, { headers, signal: params.signal }); + if (!response.ok) { + // Auth failures (401/403) must throw so checkCredentials flags the bad + // key as ok:false rather than ok:null (unknown). Other non-ok statuses + // are transient — return null so the probe reports "no data". + if (response.status === 401 || response.status === 403) { + throw new ProviderHttpError( + `Umans usage endpoint returned ${response.status} ${response.statusText}`.trim(), + response.status, + ); + } + ctx.logger?.warn("Umans usage fetch failed", { status: response.status, statusText: response.statusText }); + return null; + } + const json = (await response.json()) as unknown; + if (!isRecord(json)) { + ctx.logger?.warn("Umans usage response was not a JSON object"); + return null; + } + payload = json as unknown as UmansUsagePayload; + } catch (error) { + // Re-throw auth errors so the credential-health probe can surface them. + if (error instanceof ProviderHttpError) throw error; + ctx.logger?.warn("Umans usage fetch error", { error: String(error) }); + return null; + } + + const limits: UsageLimit[] = []; + const requests = buildRequestsLimit(payload, params.provider); + if (requests) limits.push(requests); + const concurrency = buildConcurrencyLimit(payload, params.provider); + if (concurrency) limits.push(concurrency); + if (limits.length === 0) return null; + + const notes: string[] = []; + if (payload.usage?.priority?.low === true) { + notes.push("Requests deprioritized after a rate-limit burst."); + } + + return { + provider: params.provider, + fetchedAt: Date.now(), + limits, + notes: notes.length > 0 ? notes : undefined, + metadata: { + plan: payload.plan?.display_name, + accountId: credential.accountId, + email: credential.email, + endpoint: url, + }, + raw: payload as Record, + }; +} + +export const umansUsageProvider: UsageProvider = { + id: UMANS_PROVIDER, + fetchUsage: fetchUmansUsage, + supports: params => params.provider === UMANS_PROVIDER && params.credential.type === "api_key", + validatesCredentials: true, +}; diff --git a/packages/ai/src/utils/block-symbols.ts b/packages/ai/src/utils/block-symbols.ts index d09f8fe13..1792cdb71 100644 --- a/packages/ai/src/utils/block-symbols.ts +++ b/packages/ai/src/utils/block-symbols.ts @@ -25,6 +25,18 @@ export const kStreamingBlockIndex = Symbol("provider.block.index"); /** Stores the last parsed argument prefix length for throttled streaming JSON parsing. */ export const kStreamingLastParseLen = Symbol("provider.block.lastParseLen"); +/** + * The Cursor interaction envelope's `call_id` for a streamed tool-call block. + * + * Tracked separately from the block's own `id` because they are NOT the same + * key: MCP and Pi blocks are filed under the id inside the call's `args`, which + * is what the exec channel pairs its result under, while every streamed + * `ToolCall*Update` correlates on the envelope's `call_id`. Matching + * completions against the block id would mis-route every call whose args carry + * their own id. + */ +export const kStreamingEnvelopeId = Symbol("provider.block.envelopeId"); + /** Marks streamed tool-call arguments that already received an authoritative done payload. */ export const kStreamingArgumentsDone = Symbol("provider.block.argumentsDone"); diff --git a/packages/ai/src/utils/schema/normalize.ts b/packages/ai/src/utils/schema/normalize.ts index 7cd2a6d37..d904d7685 100644 --- a/packages/ai/src/utils/schema/normalize.ts +++ b/packages/ai/src/utils/schema/normalize.ts @@ -55,6 +55,7 @@ export interface NormalizeSchemaOptions { inferTypeForBareEnum: boolean; foldOneOfIntoAnyOf: boolean; dropNonScalarEnum: boolean; + stringEnumsOnly?: boolean; rejectResidualIncompatibilities?: ReadonlyArray; validateAndFallback?: { fallback: unknown }; } @@ -106,6 +107,15 @@ const SUBSCHEMA_VALUE_KEYS: Record = { contentSchema: true, }; +/** + * Keywords whose value is either a boolean keyword value or an object + * subschema. Object values must be walked, while bare booleans stay literal. + */ +const BOOLEAN_OR_SCHEMA_VALUE_KEYS: Record = { + additionalProperties: true, + unevaluatedProperties: true, +}; + /** Keywords whose value is an array of subschemas. */ const SUBSCHEMA_ARRAY_KEYS: Record = { anyOf: true, @@ -124,6 +134,61 @@ const SUBSCHEMA_MAP_KEYS: Record = { definitions: true, }; +type SchemaChildKind = "schema" | "map"; + +/** Classify only JSON Schema-valued children; instance payloads remain opaque. */ +function classifySchemaChild(key: string, value: unknown, insideSchemaMap: boolean): SchemaChildKind | undefined { + if (insideSchemaMap) return "schema"; + const normalizedKey = SNAKE_TO_CAMEL_RENAMES.get(key) ?? key; + if (Object.hasOwn(SUBSCHEMA_MAP_KEYS, normalizedKey)) return "map"; + if (Object.hasOwn(SUBSCHEMA_VALUE_KEYS, normalizedKey) || Object.hasOwn(SUBSCHEMA_ARRAY_KEYS, normalizedKey)) { + return "schema"; + } + if (Object.hasOwn(BOOLEAN_OR_SCHEMA_VALUE_KEYS, normalizedKey) && isJsonObject(value)) return "schema"; + return undefined; +} + +function hasUnrepresentableGoogleEnumConstraint( + value: unknown, + insideSchemaMap = false, + seen = new Set(), +): boolean { + if (Array.isArray(value)) { + if (seen.has(value)) return false; + seen.add(value); + return value.some(entry => hasUnrepresentableGoogleEnumConstraint(entry, false, seen)); + } + if (!isJsonObject(value)) return false; + if (seen.has(value)) return false; + seen.add(value); + + if (insideSchemaMap) { + for (const key in value) { + if (Object.hasOwn(value, key) && hasUnrepresentableGoogleEnumConstraint(value[key], false, seen)) { + return true; + } + } + return false; + } + + if ( + Array.isArray(value.enum) && + (value.enum.length === 0 || value.enum.some(enumValue => typeof enumValue !== "string")) + ) { + return true; + } + if (Object.hasOwn(value, "const") && typeof value.const !== "string") return true; + + for (const key in value) { + if (!Object.hasOwn(value, key)) continue; + const childKind = classifySchemaChild(key, value[key], false); + if (childKind && hasUnrepresentableGoogleEnumConstraint(value[key], childKind === "map", seen)) { + return true; + } + } + return false; +} + const CLOUD_CODE_ASSIST_CLAUDE_FALLBACK_SCHEMA = { type: "object", properties: {}, @@ -361,14 +426,22 @@ function normalizeSchemaObjectNode(value: JsonObject, options: NormalizeSchemaWa continue; } if (options.stripNullableKeyword && key === "nullable") continue; - result[key] = normalizeSchemaNode(entry, { - ...options, - insideSchemaMap: !options.insideSchemaMap && Object.hasOwn(SUBSCHEMA_MAP_KEYS, key), - booleanIsSubschema: - options.insideSchemaMap || - Object.hasOwn(SUBSCHEMA_VALUE_KEYS, key) || - Object.hasOwn(SUBSCHEMA_ARRAY_KEYS, key), - }); + if ( + options.stringEnumsOnly && + !options.insideSchemaMap && + key === "not" && + hasUnrepresentableGoogleEnumConstraint(entry) + ) { + continue; + } + const childKind = classifySchemaChild(key, entry, options.insideSchemaMap); + result[key] = childKind + ? normalizeSchemaNode(entry, { + ...options, + insideSchemaMap: childKind === "map", + booleanIsSubschema: childKind === "schema", + }) + : entry; } applyDescriptionSpill(result, spill, options); return applyNodePostProcessing(result, options); @@ -387,14 +460,22 @@ function normalizeSchemaObjectNode(value: JsonObject, options: NormalizeSchemaWa constValue = entry; continue; } - result[key] = normalizeSchemaNode(entry, { - ...options, - insideSchemaMap: !options.insideSchemaMap && Object.hasOwn(SUBSCHEMA_MAP_KEYS, key), - booleanIsSubschema: - options.insideSchemaMap || - Object.hasOwn(SUBSCHEMA_VALUE_KEYS, key) || - Object.hasOwn(SUBSCHEMA_ARRAY_KEYS, key), - }); + if ( + options.stringEnumsOnly && + !options.insideSchemaMap && + key === "not" && + hasUnrepresentableGoogleEnumConstraint(entry) + ) { + continue; + } + const childKind = classifySchemaChild(key, entry, options.insideSchemaMap); + result[key] = childKind + ? normalizeSchemaNode(entry, { + ...options, + insideSchemaMap: childKind === "map", + booleanIsSubschema: childKind === "schema", + }) + : entry; } if (options.normalizeTypeArrayToNullable && Array.isArray(result.type)) { @@ -464,6 +545,7 @@ function applyNodePostProcessing(schema: JsonObject, options: NormalizeSchemaWal } if (options.foldOneOfIntoAnyOf) current = foldOneOfIntoAnyOf(current); if (options.dropNonScalarEnum) current = dropNonScalarEnumForMfjs(current); + if (options.stringEnumsOnly && options.booleanIsSubschema) current = dropNonStringEnumForGoogle(current); return current; } @@ -484,6 +566,13 @@ function dropNonScalarEnumForMfjs(schema: JsonObject): JsonObject { return copySchemaWithout(schema, "enum"); } +/** Google's Schema enum field accepts string values only; omit unsupported enums without dropping the node's type. */ +function dropNonStringEnumForGoogle(schema: JsonObject): JsonObject { + if (!Array.isArray(schema.enum)) return schema; + const isStringEnum = schema.enum.length > 0 && schema.enum.every(value => typeof value === "string"); + return isStringEnum ? schema : copySchemaWithout(schema, "enum"); +} + /** Copy all keys from a schema except the specified combiner key. */ export function copySchemaWithout(schema: JsonObject, combiner: string): JsonObject { const { [combiner]: _, ...rest } = schema; @@ -732,16 +821,25 @@ function collapseSameTypeCombinerVariants(schema: JsonObject, combiner: "anyOf" * create new anyOf in merged subtrees after child normalization already ran. */ export function stripResidualCombiners(value: unknown, epoch: number = epochNext()): unknown { + return stripResidualCombinersNode(value, epoch, false); +} + +function stripResidualCombinersNode(value: unknown, epoch: number, insideSchemaMap: boolean): unknown { if (Array.isArray(value)) { if (!once(value, epoch)) return []; - return value.map(entry => stripResidualCombiners(entry, epoch)); + return value.map(entry => stripResidualCombinersNode(entry, epoch, false)); } if (!isJsonObject(value)) return value; if (!once(value, epoch)) return {}; const result: JsonObject = {}; for (const key in value) { - if (Object.hasOwn(value, key)) result[key] = stripResidualCombiners(value[key], epoch); + if (!Object.hasOwn(value, key)) continue; + const entry = value[key]; + const childKind = classifySchemaChild(key, entry, insideSchemaMap); + result[key] = childKind ? stripResidualCombinersNode(entry, epoch, childKind === "map") : entry; } + if (insideSchemaMap) return result; + let current: JsonObject = result; let changed = true; while (changed) { @@ -840,6 +938,7 @@ function normalizeNullablePropertiesForCloudCodeAssist( value: unknown, isPropertySchema = false, epoch: number = epochNext(), + insideSchemaMap = false, ): NullableNormalizationResult { if (Array.isArray(value)) { if (!once(value, epoch)) { @@ -859,9 +958,14 @@ function normalizeNullablePropertiesForCloudCodeAssist( const normalized: JsonObject = {}; for (const key in value) { - if (Object.hasOwn(value, key)) - normalized[key] = normalizeNullablePropertiesForCloudCodeAssist(value[key], false, epoch).schema; + if (!Object.hasOwn(value, key)) continue; + const entry = value[key]; + const childKind = classifySchemaChild(key, entry, insideSchemaMap); + normalized[key] = childKind + ? normalizeNullablePropertiesForCloudCodeAssist(entry, false, epoch, childKind === "map").schema + : entry; } + if (insideSchemaMap) return { schema: normalized, nullable: false }; if (isJsonObject(normalized.properties)) { const properties = normalized.properties; @@ -933,7 +1037,7 @@ function hasResidualSchemaIncompatibilities( ): boolean { if (Array.isArray(value)) { if (!once(value, epoch)) return false; - return value.some(entry => hasResidualSchemaIncompatibilities(entry, checks, epoch, insideSchemaMap)); + return value.some(entry => hasResidualSchemaIncompatibilities(entry, checks, epoch, false)); } if (!isJsonObject(value)) { return false; @@ -942,25 +1046,22 @@ function hasResidualSchemaIncompatibilities( return false; } - if (checks.typeArray && Array.isArray(value.type)) return true; - if (checks.typeNull && value.type === "null") return true; - if (checks.nullable && Object.hasOwn(value, "nullable")) return true; - if (!insideSchemaMap && checks.not && Object.hasOwn(value, "not")) return true; - if (!insideSchemaMap && checks.combiners) { - for (const combiner of CCA_FORBIDDEN_COMBINERS) { - if (Array.isArray(value[combiner])) return true; + if (!insideSchemaMap) { + if (checks.typeArray && Array.isArray(value.type)) return true; + if (checks.typeNull && value.type === "null") return true; + if (checks.nullable && Object.hasOwn(value, "nullable")) return true; + if (checks.not && Object.hasOwn(value, "not")) return true; + if (checks.combiners) { + for (const combiner of CCA_FORBIDDEN_COMBINERS) { + if (Array.isArray(value[combiner])) return true; + } } } - for (const k in value) { - if (!Object.hasOwn(value, k)) continue; - if ( - hasResidualSchemaIncompatibilities( - value[k], - checks, - epoch, - !insideSchemaMap && Object.hasOwn(SUBSCHEMA_MAP_KEYS, k), - ) - ) { + for (const key in value) { + if (!Object.hasOwn(value, key)) continue; + const entry = value[key]; + const childKind = classifySchemaChild(key, entry, insideSchemaMap); + if (childKind && hasResidualSchemaIncompatibilities(entry, checks, epoch, childKind === "map")) { return true; } } @@ -1012,6 +1113,7 @@ export function normalizeSchemaForGoogle(value: unknown): unknown { extractNullableFromUnions: false, inferTypeForBareEnum: true, dropNonScalarEnum: false, + stringEnumsOnly: true, foldOneOfIntoAnyOf: false, }); } diff --git a/packages/ai/test/anthropic-client.test.ts b/packages/ai/test/anthropic-client.test.ts index e7b72e079..4670d455a 100644 --- a/packages/ai/test/anthropic-client.test.ts +++ b/packages/ai/test/anthropic-client.test.ts @@ -28,6 +28,10 @@ const anthropicErrorBody = JSON.stringify({ type: "error", error: { type: "invalid_request_error", message: "The compiled grammar is too large." }, }); +const anthropicOverloadedErrorBody = JSON.stringify({ + type: "error", + error: { type: "overloaded_error", message: "Overloaded" }, +}); describe("AnthropicMessagesClient error mapping", () => { it("maps non-2xx responses to AnthropicApiError with status and body in message", async () => { @@ -134,6 +138,19 @@ describe("AnthropicMessagesClient retries", () => { expect(error).toBeInstanceOf(AIError.AnthropicApiError); expect(calls.length).toBe(3); // initial attempt + 2 retries }); + + it("disables the cap when maxRetryDelayMs is negative", async () => { + const { calls, fetch } = createFetchMock([ + new Response("overloaded", { status: 429, headers: { "retry-after-ms": "1" } }), + new Response("{}", { status: 200 }), + ]); + const client = new AnthropicMessagesClient({ apiKey: "sk-test", maxRetries: 5, fetch }); + + const response = await client.messages.create(params, { maxRetryDelayMs: -1 }).asResponse(); + + expect(response.status).toBe(200); + expect(calls.length).toBe(2); + }); }); describe("AnthropicMessagesClient timeout and abort", () => { @@ -212,3 +229,449 @@ describe("AnthropicMessagesClient request assembly", () => { expect(calls[0].url).toBe("https://api.anthropic.com/v1/messages"); }); }); + +describe("AnthropicMessagesClient retry-after cap", () => { + it("uses the documented 60-second default cap when callers omit one", async () => { + const { calls, fetch } = createFetchMock([ + new Response(anthropicOverloadedErrorBody, { status: 429, headers: { "retry-after": "120" } }), + ]); + const client = new AnthropicMessagesClient({ apiKey: "sk-test", maxRetries: 5, fetch }); + + const error = await client.messages + .create(params) + .asResponse() + .catch(err => err as AIError.AnthropicApiError); + + expect(error).toBeInstanceOf(AIError.AnthropicApiError); + expect(error.status).toBe(429); + expect(calls.length).toBe(1); + }); + + it("declines a retry and preserves original status/body/headers when retry-after exceeds maxRetryDelayMs", async () => { + const errorHeaders = { "retry-after": "120", "request-id": "req_cap" }; + const { calls, fetch } = createFetchMock([ + new Response(anthropicOverloadedErrorBody, { status: 429, headers: errorHeaders }), + ]); + const client = new AnthropicMessagesClient({ apiKey: "sk-test", maxRetries: 5, fetch }); + + const error = await client.messages + .create(params, { maxRetryDelayMs: 60_000 }) + .asResponse() + .catch(err => err as AIError.AnthropicApiError); + if (!(error instanceof AIError.AnthropicApiError)) throw new Error("Expected AnthropicApiError"); + + expect(error).toBeInstanceOf(AIError.AnthropicApiError); + expect(error.status).toBe(429); + expect(error.message).toContain("overloaded"); + expect(error.headers.get("request-id")).toBe("req_cap"); + expect(calls.length).toBe(1); + }); + + it("cancels and releases an open error-body reader when the caller aborts", async () => { + const controller = new AbortController(); + const encoder = new TextEncoder(); + let readBlocked = false; + let bodyCancelled = false; + let response: Response | undefined; + const openBody = new ReadableStream({ + start(streamController) { + streamController.enqueue(encoder.encode("overloaded")); + }, + pull() { + readBlocked = true; + return Promise.withResolvers().promise; + }, + cancel() { + bodyCancelled = true; + }, + }); + const fetch: FetchImpl = async () => { + response = new Response(openBody, { + status: 429, + headers: { "retry-after": "120", "request-id": "req_abort" }, + }); + return response; + }; + const client = new AnthropicMessagesClient({ apiKey: "sk-test", maxRetries: 5, fetch }); + + const pending = client.messages + .create(params, { signal: controller.signal, maxRetryDelayMs: 60_000 }) + .asResponse(); + for (let i = 0; i < 1000 && !readBlocked; i++) await Promise.resolve(); + if (!readBlocked) throw new Error("Anthropic error-body read did not block"); + + controller.abort(); + const error = await pending.catch(err => err as Error); + if (!(error instanceof Error)) throw new Error("Expected request abort error"); + for (let i = 0; i < 1000 && !bodyCancelled; i++) await Promise.resolve(); + if (!bodyCancelled) throw new Error("Anthropic error-body reader was not cancelled"); + + expect(error).toBeInstanceOf(AIError.AbortError); + expect(error.message).toBe("Request was aborted."); + expect(response?.body?.locked).toBe(false); + }); + + it("bounds a never-ending error body without a caller signal", async () => { + const encoder = new TextEncoder(); + let readBlocked = false; + let bodyCancelled = false; + let response: Response | undefined; + const openBody = new ReadableStream({ + start(streamController) { + streamController.enqueue(encoder.encode("overloaded")); + }, + pull() { + readBlocked = true; + return new Promise(() => {}); + }, + cancel() { + bodyCancelled = true; + }, + }); + const fetch: FetchImpl = async () => { + response = new Response(openBody, { + status: 529, + headers: { "retry-after": "120", "request-id": "req_body_timeout" }, + }); + return response; + }; + const client = new AnthropicMessagesClient({ apiKey: "sk-test", maxRetries: 5, fetch }); + + AIError.__anthropicApiErrorForTesting.setBodyReadTimeoutMs(5); + try { + const error = await client.messages + .create(params, { maxRetryDelayMs: 60_000 }) + .asResponse() + .catch(err => err as AIError.AnthropicApiError); + if (!(error instanceof AIError.AnthropicApiError)) throw new Error("Expected AnthropicApiError"); + + expect(readBlocked).toBe(true); + expect(error.status).toBe(529); + expect(error.message).toBe("529 overloaded"); + expect(error.headers.get("request-id")).toBe("req_body_timeout"); + expect(bodyCancelled).toBe(true); + expect(response?.body?.locked).toBe(false); + } finally { + AIError.__anthropicApiErrorForTesting.setBodyReadTimeoutMs(undefined); + } + }); + + it("preserves bounded partial error details when the body stream errors", async () => { + const encoder = new TextEncoder(); + let response: Response | undefined; + const partialBody = new ReadableStream({ + start(streamController) { + streamController.enqueue(encoder.encode("partial detail")); + }, + pull(streamController) { + streamController.error(new Error("socket closed")); + }, + }); + const client = new AnthropicMessagesClient({ + apiKey: "sk-test", + maxRetries: 0, + fetch: async () => { + response = new Response(partialBody, { status: 502, headers: { "request-id": "req_partial" } }); + return response; + }, + }); + + const error = await client.messages + .create(params) + .asResponse() + .catch(err => err as AIError.AnthropicApiError); + if (!(error instanceof AIError.AnthropicApiError)) throw new Error("Expected AnthropicApiError"); + + expect(error.status).toBe(502); + expect(error.message).toBe("502 partial detail"); + expect(error.headers.get("request-id")).toBe("req_partial"); + expect(response?.body?.locked).toBe(false); + }); + + it("flushes an incomplete UTF-8 prefix at clean error-body EOF", async () => { + let response: Response | undefined; + const incompleteBody = new ReadableStream({ + start(streamController) { + streamController.enqueue(new Uint8Array([0xe2, 0x82])); + streamController.close(); + }, + }); + const client = new AnthropicMessagesClient({ + apiKey: "sk-test", + maxRetries: 0, + fetch: async () => { + response = new Response(incompleteBody, { status: 500 }); + return response; + }, + }); + + const error = await client.messages + .create(params) + .asResponse() + .catch(err => err as AIError.AnthropicApiError); + if (!(error instanceof AIError.AnthropicApiError)) throw new Error("Expected AnthropicApiError"); + + expect(error.message).toBe("500 \uFFFD"); + expect(response?.body?.locked).toBe(false); + }); + + it("keeps complete error text without flushing an incomplete UTF-8 prefix after a read rejection", async () => { + const completeText = new TextEncoder().encode("complete text"); + const completeTextWithIncompletePrefix = new Uint8Array(completeText.byteLength + 2); + completeTextWithIncompletePrefix.set(completeText); + completeTextWithIncompletePrefix.set([0xe2, 0x82], completeText.byteLength); + let response: Response | undefined; + let pullErrored = false; + const rejectedBody = new ReadableStream({ + start(streamController) { + streamController.enqueue(completeTextWithIncompletePrefix); + }, + pull(streamController) { + pullErrored = true; + streamController.error(new Error("socket closed")); + }, + }); + const client = new AnthropicMessagesClient({ + apiKey: "sk-test", + maxRetries: 0, + fetch: async () => { + response = new Response(rejectedBody, { status: 502 }); + return response; + }, + }); + + const error = await client.messages + .create(params) + .asResponse() + .catch(err => err as AIError.AnthropicApiError); + if (!(error instanceof AIError.AnthropicApiError)) throw new Error("Expected AnthropicApiError"); + + expect(error.message).toBe("502 complete text"); + expect(error.message).not.toContain("\uFFFD"); + expect(response?.body?.locked).toBe(false); + expect(pullErrored).toBe(true); + }); + + it("does not flush an incomplete UTF-8 prefix when the error body times out", async () => { + const readBlocked = Promise.withResolvers(); + let didBlockRead = false; + let bodyCancelled = false; + const incompleteBody = new ReadableStream({ + start(streamController) { + streamController.enqueue(new Uint8Array([0xe2, 0x82])); + }, + pull() { + didBlockRead = true; + return readBlocked.promise; + }, + cancel() { + bodyCancelled = true; + }, + }); + const client = new AnthropicMessagesClient({ + apiKey: "sk-test", + maxRetries: 0, + fetch: async () => new Response(incompleteBody, { status: 500 }), + }); + + AIError.__anthropicApiErrorForTesting.setBodyReadTimeoutMs(5); + try { + const error = await client.messages + .create(params) + .asResponse() + .catch(err => err as AIError.AnthropicApiError); + if (!(error instanceof AIError.AnthropicApiError)) throw new Error("Expected AnthropicApiError"); + + expect(error.message).toBe("500 status code (no body)"); + expect(error.message).not.toContain("\uFFFD"); + expect(didBlockRead).toBe(true); + expect(bodyCancelled).toBe(true); + } finally { + AIError.__anthropicApiErrorForTesting.setBodyReadTimeoutMs(undefined); + } + }); + + it("bounds, marks, and cancels a continuously ready oversized error body", async () => { + const chunk = new TextEncoder().encode("x".repeat(1024)); + let bodyCancelled = false; + let response: Response | undefined; + const oversizedBody = new ReadableStream({ + pull(streamController) { + streamController.enqueue(chunk); + }, + cancel() { + bodyCancelled = true; + }, + }); + const fetch: FetchImpl = async () => { + response = new Response(oversizedBody, { status: 400 }); + return response; + }; + const client = new AnthropicMessagesClient({ apiKey: "sk-test", maxRetries: 0, fetch }); + + const startedAt = performance.now(); + const error = await client.messages + .create(params) + .asResponse() + .catch(err => err as AIError.AnthropicApiError); + if (!(error instanceof AIError.AnthropicApiError)) throw new Error("Expected AnthropicApiError"); + for (let i = 0; i < 1000 && !bodyCancelled; i++) await Promise.resolve(); + if (!bodyCancelled) throw new Error("Anthropic error-body reader was not cancelled"); + + expect(performance.now() - startedAt).toBeLessThan(1_000); + expect(error.message).toBe(`400 ${"x".repeat(64 * 1024)}\n[Response body truncated after 64 KiB]`); + expect(response?.body?.locked).toBe(false); + }); + + it("preserves an exact 64 KiB error body without marking or cancelling it", async () => { + const body = new Uint8Array(64 * 1024).fill(120); + let bodyCancelled = false; + const exactBody = new ReadableStream({ + start(streamController) { + streamController.enqueue(body); + streamController.close(); + }, + cancel() { + bodyCancelled = true; + }, + }); + const client = new AnthropicMessagesClient({ + apiKey: "sk-test", + maxRetries: 0, + fetch: async () => new Response(exactBody, { status: 400 }), + }); + + const error = await client.messages + .create(params) + .asResponse() + .catch(err => err as AIError.AnthropicApiError); + if (!(error instanceof AIError.AnthropicApiError)) throw new Error("Expected AnthropicApiError"); + + expect(error.message).toBe(`400 ${"x".repeat(64 * 1024)}`); + expect(bodyCancelled).toBe(false); + }); + + it("marks and cancels only after observing the 64 KiB-plus-one error-body byte", async () => { + const firstChunk = new Uint8Array(64 * 1024).fill(120); + const overflowByte = new Uint8Array([120]); + let bodyCancelled = false; + const overflowingBody = new ReadableStream({ + start(streamController) { + streamController.enqueue(firstChunk); + streamController.enqueue(overflowByte); + }, + cancel() { + bodyCancelled = true; + }, + }); + const client = new AnthropicMessagesClient({ + apiKey: "sk-test", + maxRetries: 0, + fetch: async () => new Response(overflowingBody, { status: 400 }), + }); + + const error = await client.messages + .create(params) + .asResponse() + .catch(err => err as AIError.AnthropicApiError); + if (!(error instanceof AIError.AnthropicApiError)) throw new Error("Expected AnthropicApiError"); + + expect(error.message).toBe(`400 ${"x".repeat(64 * 1024)}\n[Response body truncated after 64 KiB]`); + expect(bodyCancelled).toBe(true); + }); + + it("does not append a replacement character for UTF-8 split by the truncation boundary", async () => { + const body = new Uint8Array(64 * 1024 + 2).fill(120); + body.set([0xe2, 0x82, 0xac], 64 * 1024 - 1); + const client = new AnthropicMessagesClient({ + apiKey: "sk-test", + maxRetries: 0, + fetch: async () => new Response(body, { status: 400 }), + }); + + const error = await client.messages + .create(params) + .asResponse() + .catch(err => err as AIError.AnthropicApiError); + if (!(error instanceof AIError.AnthropicApiError)) throw new Error("Expected AnthropicApiError"); + + expect(error.message).toBe(`400 ${"x".repeat(64 * 1024 - 1)}\n[Response body truncated after 64 KiB]`); + expect(error.message).not.toContain("\uFFFD"); + }); + + it("bounds continuously-ready empty error-body chunks without accumulating read reactions", async () => { + let pulls = 0; + let bodyCancelled = false; + const emptyBody = new ReadableStream({ + pull(streamController) { + pulls += 1; + streamController.enqueue(new Uint8Array()); + }, + cancel() { + bodyCancelled = true; + }, + }); + const client = new AnthropicMessagesClient({ + apiKey: "sk-test", + maxRetries: 0, + fetch: async () => new Response(emptyBody, { status: 500 }), + }); + + AIError.__anthropicApiErrorForTesting.setBodyReadTimeoutMs(5); + try { + const startedAt = performance.now(); + const error = await client.messages + .create(params) + .asResponse() + .catch(err => err as AIError.AnthropicApiError); + if (!(error instanceof AIError.AnthropicApiError)) throw new Error("Expected AnthropicApiError"); + + expect(performance.now() - startedAt).toBeLessThan(1_000); + expect(pulls).toBeGreaterThan(0); + expect(pulls).toBeLessThan(100_000); + expect(bodyCancelled).toBe(true); + expect(error.message).toBe("500 status code (no body)"); + } finally { + AIError.__anthropicApiErrorForTesting.setBodyReadTimeoutMs(undefined); + } + }); + + it("checks the deadline before an always-ready error body can starve timer callbacks", async () => { + let bodyCancelled = false; + let response: Response | undefined; + const alwaysReadyBody = new ReadableStream({ + start(streamController) { + streamController.enqueue(new Uint8Array([120])); + }, + pull(streamController) { + streamController.enqueue(new Uint8Array([120])); + }, + cancel() { + bodyCancelled = true; + }, + }); + const fetch: FetchImpl = async () => { + response = new Response(alwaysReadyBody, { status: 500 }); + return response; + }; + const client = new AnthropicMessagesClient({ apiKey: "sk-test", maxRetries: 0, fetch }); + + AIError.__anthropicApiErrorForTesting.setBodyReadTimeoutMs(0); + try { + const startedAt = performance.now(); + const error = await client.messages + .create(params) + .asResponse() + .catch(err => err as AIError.AnthropicApiError); + if (!(error instanceof AIError.AnthropicApiError)) throw new Error("Expected AnthropicApiError"); + for (let i = 0; i < 1000 && !bodyCancelled; i++) await Promise.resolve(); + if (!bodyCancelled) throw new Error("Anthropic error-body reader was not cancelled"); + + expect(performance.now() - startedAt).toBeLessThan(1_000); + expect(error.message).toBe("500 status code (no body)"); + expect(response?.body?.locked).toBe(false); + } finally { + AIError.__anthropicApiErrorForTesting.setBodyReadTimeoutMs(undefined); + } + }); +}); diff --git a/packages/ai/test/anthropic-fast-mode.test.ts b/packages/ai/test/anthropic-fast-mode.test.ts index a1d8fa904..d683e9aee 100644 --- a/packages/ai/test/anthropic-fast-mode.test.ts +++ b/packages/ai/test/anthropic-fast-mode.test.ts @@ -1,6 +1,10 @@ import { describe, expect, it } from "bun:test"; import { isFastModeUnsupported } from "@oh-my-pi/pi-ai/error"; -import { clearAnthropicFastModeFallback, streamAnthropic } from "@oh-my-pi/pi-ai/providers/anthropic"; +import { + clearAnthropicFastModeFallback, + isAnthropicFastModeFallbackDisabled, + streamAnthropic, +} from "@oh-my-pi/pi-ai/providers/anthropic"; import type { Context, Model, ProviderSessionState, ServiceTier } from "@oh-my-pi/pi-ai/types"; import { buildModel } from "@oh-my-pi/pi-catalog/build"; @@ -88,6 +92,49 @@ describe("Anthropic priority service tier → speed='fast'", () => { expect(payload.speed).toBeUndefined(); } }); + describe("provider-session fallback scope", () => { + function stateKey(model: Model<"anthropic-messages">): string { + return `anthropic-messages:${model.baseUrl}\u0000${model.id}`; + } + + it("inspects the exact model and endpoint fallback without bleeding to other requests", async () => { + const model = makeAnthropicModel("claude-opus-4-7"); + const otherModel = makeAnthropicModel("claude-opus-4-6"); + const otherEndpoint = buildModel({ + ...model, + id: model.id, + name: `${model.id} gateway`, + baseUrl: "https://gateway.example/v1", + }); + const state = new Map(); + state.set(stateKey(model), { + strictToolsDisabled: false, + fastModeDisabled: true, + replayUnsignedThinkingDisabled: false, + close: () => {}, + } as ProviderSessionState); + + const matching = (await capturePayload(model, { + serviceTier: "priority", + providerSessionState: state, + })) as Record; + const differentModel = (await capturePayload(otherModel, { + serviceTier: "priority", + providerSessionState: state, + })) as Record; + const differentEndpoint = (await capturePayload(otherEndpoint, { + serviceTier: "priority", + providerSessionState: state, + })) as Record; + + expect(isAnthropicFastModeFallbackDisabled(state, model)).toBe(true); + expect(isAnthropicFastModeFallbackDisabled(state, otherModel)).toBe(false); + expect(isAnthropicFastModeFallbackDisabled(state, otherEndpoint)).toBe(false); + expect(matching.speed).toBeUndefined(); + expect(differentModel.speed).toBe("fast"); + expect(differentEndpoint.speed).toBe("fast"); + }); + }); }); describe("clearAnthropicFastModeFallback", () => { diff --git a/packages/ai/test/anthropic-stream-timeout.test.ts b/packages/ai/test/anthropic-stream-timeout.test.ts index 5aea65d9e..c4a2d1e69 100644 --- a/packages/ai/test/anthropic-stream-timeout.test.ts +++ b/packages/ai/test/anthropic-stream-timeout.test.ts @@ -1,8 +1,8 @@ import { afterEach, describe, expect, it, vi } from "bun:test"; import * as AIError from "@oh-my-pi/pi-ai/error"; import { streamAnthropic } from "@oh-my-pi/pi-ai/providers/anthropic"; -import type { AnthropicMessagesClientLike } from "@oh-my-pi/pi-ai/providers/anthropic-client"; -import type { Context, Model } from "@oh-my-pi/pi-ai/types"; +import { AnthropicMessagesClient, type AnthropicMessagesClientLike } from "@oh-my-pi/pi-ai/providers/anthropic-client"; +import type { Context, FetchImpl, Model } from "@oh-my-pi/pi-ai/types"; import { buildModel } from "@oh-my-pi/pi-catalog/build"; import { waitForDelayOrAbort } from "./helpers"; @@ -90,6 +90,36 @@ function createSuccessfulAnthropicEvents(text: string): MockAnthropicEvent[] { ]; } +function createAnthropicSseResponse(text: string): Response { + const body = createSuccessfulAnthropicEvents(text) + .map(event => `event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`) + .join(""); + return new Response(body, { + status: 200, + headers: { "content-type": "text/event-stream", "request-id": "req_retry_success" }, + }); +} + +function createResponseClient(responses: Response[]): { + calls: { count: number }; + client: AnthropicMessagesClientLike; +} { + const calls = { count: 0 }; + const fetch: FetchImpl = async () => { + const response = responses[Math.min(calls.count++, responses.length - 1)]; + if (!response) throw new Error("Expected an Anthropic mock response"); + return new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers: response.headers, + }); + }; + return { + calls, + client: new AnthropicMessagesClient({ apiKey: "sk-test", maxRetries: 0, fetch }), + }; +} + function createAnthropicMockStream({ signal, connectDelayMs = 0, @@ -533,3 +563,195 @@ describe("anthropic provider retry delays", () => { expect(JSON.parse(JSON.stringify(result.content))).toEqual([{ type: "text", text: "recovered from 502" }]); }); }); + +describe("anthropic retry-after cap (maxRetryDelayMs)", () => { + it("surfaces the original HTTP error without a second attempt when retry-after exceeds the default 60s cap", async () => { + const { calls, client } = createResponseClient([ + new Response('{"type":"error","error":{"type":"rate_limit_error","message":"Too many requests"}}', { + status: 429, + headers: { "retry-after": "120" }, + }), + ]); + const providerRetryWait = vi.fn(async () => {}); + + const result = await streamAnthropic(model, context, { client, providerRetryWait }).result(); + + expect(calls.count).toBe(1); + expect(providerRetryWait).not.toHaveBeenCalled(); + expect(result.stopReason).toBe("error"); + expect(result.errorStatus).toBe(429); + expect(result.errorMessage).toContain("rate_limit_error"); + }); + + it("surfaces the original HTTP error when retry-after exceeds an explicit cap", async () => { + const { calls, client } = createResponseClient([ + new Response('{"type":"error","error":{"type":"overloaded_error","message":"Overloaded"}}', { + status: 529, + headers: { "retry-after-ms": "10000" }, + }), + ]); + const providerRetryWait = vi.fn(async () => {}); + + const result = await streamAnthropic(model, context, { + client, + providerRetryWait, + maxRetryDelayMs: 5_000, + }).result(); + + expect(calls.count).toBe(1); + expect(providerRetryWait).not.toHaveBeenCalled(); + expect(result.stopReason).toBe("error"); + expect(result.errorStatus).toBe(529); + }); + + it("disables the cap when maxRetryDelayMs is negative", async () => { + const { calls, client } = createResponseClient([ + new Response('{"type":"error","error":{"type":"overloaded_error","message":"Overloaded"}}', { + status: 529, + headers: { "retry-after": "1" }, + }), + createAnthropicSseResponse("after unbounded wait"), + ]); + const providerRetryWait = vi.fn(async () => {}); + + const result = await streamAnthropic(model, context, { + client, + providerRetryWait, + maxRetryDelayMs: -1, + }).result(); + + expect(calls.count).toBe(2); + expect(providerRetryWait).toHaveBeenCalledWith(1_000, undefined); + expect(result.stopReason).toBe("stop"); + }); + + it("disables the cap when maxRetryDelayMs is 0 and waits the full server hint", async () => { + const { calls, client } = createResponseClient([ + new Response('{"type":"error","error":{"type":"overloaded_error","message":"Overloaded"}}', { + status: 529, + headers: { "retry-after": "120" }, + }), + createAnthropicSseResponse("after long wait"), + ]); + const providerRetryWait = vi.fn(async () => {}); + + const result = await streamAnthropic(model, context, { + client, + providerRetryWait, + maxRetryDelayMs: 0, + }).result(); + + expect(calls.count).toBe(2); + expect(providerRetryWait).toHaveBeenCalledWith(120_000, undefined); + expect(result.stopReason).toBe("stop"); + }); + + it("retries when the HTTP retry-after hint is under the cap", async () => { + const { calls, client } = createResponseClient([ + new Response('{"type":"error","error":{"type":"overloaded_error","message":"Overloaded"}}', { + status: 529, + headers: { "retry-after": "30" }, + }), + createAnthropicSseResponse("after backoff"), + ]); + const providerRetryWait = vi.fn(async () => {}); + + const result = await streamAnthropic(model, context, { + client, + providerRetryWait, + maxRetryDelayMs: 60_000, + }).result(); + + expect(calls.count).toBe(2); + expect(providerRetryWait).toHaveBeenCalledWith(30_000, undefined); + expect(result.stopReason).toBe("stop"); + }); + + it("honors retry headers from structurally compatible injected SDK errors", async () => { + let attempt = 0; + const error = Object.assign(new Error("529 overloaded"), { + status: 529, + headers: new Headers({ "retry-after-ms": "10000" }), + }); + const create = ((_body: unknown) => { + attempt += 1; + return createRejectedAnthropicRequest(error) as never; + }) as unknown as AnthropicMessagesClientLike["messages"]["create"]; + const providerRetryWait = vi.fn(async () => {}); + + const result = await streamAnthropic(model, context, { + client: { messages: { create } }, + providerRetryWait, + maxRetryDelayMs: 5_000, + }).result(); + + expect(attempt).toBe(1); + expect(providerRetryWait).not.toHaveBeenCalled(); + expect(result.stopReason).toBe("error"); + expect(result.errorStatus).toBe(529); + }); + + it("honors record-valued retry headers from injected SDK errors", async () => { + let attempt = 0; + const error = Object.assign(new Error("529 overloaded"), { + status: 529, + headers: { "Retry-After-Ms": "10000" }, + }); + const create = ((_body: unknown) => { + attempt += 1; + return createRejectedAnthropicRequest(error) as never; + }) as unknown as AnthropicMessagesClientLike["messages"]["create"]; + const providerRetryWait = vi.fn(async () => {}); + + const result = await streamAnthropic(model, context, { + client: { messages: { create } }, + providerRetryWait, + maxRetryDelayMs: 5_000, + }).result(); + + expect(attempt).toBe(1); + expect(providerRetryWait).not.toHaveBeenCalled(); + expect(result.stopReason).toBe("error"); + expect(result.errorStatus).toBe(529); + }); + + it("honors nested response retry headers from injected SDK errors", async () => { + let attempt = 0; + const error = Object.assign(new Error("529 overloaded"), { + response: { status: 529, headers: { "retry-after-ms": "10000" } }, + }); + const create = ((_body: unknown) => { + attempt += 1; + return createRejectedAnthropicRequest(error) as never; + }) as unknown as AnthropicMessagesClientLike["messages"]["create"]; + const providerRetryWait = vi.fn(async () => {}); + + const result = await streamAnthropic(model, context, { + client: { messages: { create } }, + providerRetryWait, + maxRetryDelayMs: 5_000, + }).result(); + + expect(attempt).toBe(1); + expect(providerRetryWait).not.toHaveBeenCalled(); + expect(result.stopReason).toBe("error"); + expect(result.errorStatus).toBe(529); + }); + + it("passes maxRetryDelayMs to internally constructed Anthropic clients", async () => { + let calls = 0; + const fetch: FetchImpl = async () => { + calls += 1; + return new Response('{"type":"error","error":{"type":"overloaded_error","message":"Overloaded"}}', { + status: 429, + headers: { "retry-after-ms": "10" }, + }); + }; + + const result = await streamAnthropic(model, context, { fetch, maxRetryDelayMs: 5 }).result(); + + expect(calls).toBe(1); + expect(result.stopReason).toBe("error"); + expect(result.errorStatus).toBe(429); + }); +}); diff --git a/packages/ai/test/cursor-exec-handlers.test.ts b/packages/ai/test/cursor-exec-handlers.test.ts index 26d4856ab..bfdc4fad3 100644 --- a/packages/ai/test/cursor-exec-handlers.test.ts +++ b/packages/ai/test/cursor-exec-handlers.test.ts @@ -689,6 +689,7 @@ function newBlockState(): BlockState { get currentToolCall() { return toolCall; }, + openToolCalls: new Map(), resolvedMcpToolCallIds: new Set(), firstTokenTime: undefined, setTextBlock: b => { diff --git a/packages/ai/test/cursor-exec-modern.test.ts b/packages/ai/test/cursor-exec-modern.test.ts new file mode 100644 index 000000000..93cb5a730 --- /dev/null +++ b/packages/ai/test/cursor-exec-modern.test.ts @@ -0,0 +1,2069 @@ +import { describe, expect, it } from "bun:test"; +import { create, fromBinary, toBinary } from "@bufbuild/protobuf"; +import { + type BlockState, + CURSOR_CLIENT_VERSION, + flushOpenToolCalls, + handleServerMessage, + processInteractionUpdate, + type ToolCallState, +} from "@oh-my-pi/pi-ai/providers/cursor"; +import type { AssistantMessage, CursorExecHandlers, ToolResultMessage } from "@oh-my-pi/pi-ai/types"; +import { kCursorExecResolved, setStreamingPartialJson } from "@oh-my-pi/pi-ai/utils/block-symbols"; +import { AssistantMessageEventStream } from "@oh-my-pi/pi-ai/utils/event-stream"; +import { + type AgentClientMessage, + AgentClientMessageSchema, + AgentServerMessageSchema, + AgentStoreConflictArgsSchema, + CanvasDiagnosticsArgsSchema, + ComputerUseArgsSchema, + ConnectScmArgsSchema, + ConnectScmErrorSchema, + ConnectScmGithubRepositorySchema, + ConnectScmGithubSchema, + ConnectScmRejectedSchema, + ConnectScmResultSchema, + ConnectScmSuccessSchema, + ConnectScmToolCallSchema, + ConversationSearchArgsSchema, + type ExecServerMessage, + ExecServerMessageSchema, + ExecuteHookArgsSchema, + ExecuteHookRequestSchema, + ForceBackgroundShellArgsSchema, + ForceBackgroundShellStatus, + ForceBackgroundSubagentArgsSchema, + ForceBackgroundSubagentStatus, + GetDiffRequestSchema, + GrepArgsSchema, + ListMcpResourcesExecArgsSchema, + McpAllowlistPrecheckArgsSchema, + McpArgsSchema, + McpStateExecArgsSchema, + type McpToolDefinition, + McpToolDefinitionSchema, + PiBashExecArgsSchema, + PiEditExecArgsSchema, + PiEditReplacementSchema, + PiFindExecArgsSchema, + PiGrepExecArgsSchema, + PiLsExecArgsSchema, + PiReadExecArgsSchema, + PiWriteExecArgsSchema, + PostToolUseRequestQuerySchema, + ReadArgsSchema, + ReadMcpResourceExecArgsSchema, + RecordScreenArgsSchema, + ShellAllowlistPrecheckArgsSchema, + ShellArgsSchema, + SmartModeClassifierArgsSchema, + SubagentArgsSchema, + SubagentAwaitArgsSchema, + ToolCallSchema, + WebFetchAllowlistPrecheckArgsSchema, +} from "@oh-my-pi/pi-catalog/discovery/cursor-gen/agent_pb"; + +/** + * Drive one `ExecServerMessage` through the real dispatcher and decode every + * frame it wrote back. + * + * The frames are decoded from the actual wire bytes rather than intercepted as + * objects: the whole point of these cases is what the SERVER receives, and an + * `ExecClientMessage` whose oneof never got set still looks like a populated + * object in memory. + */ +async function dispatchExec( + message: ExecServerMessage, + options: { execHandlers?: CursorExecHandlers; requestContextTools?: McpToolDefinition[] } = {}, +): Promise<{ frames: AgentClientMessage[]; output: AssistantMessage; results: ToolResultMessage[] }> { + const output = cursorAssistantMessage(); + const stream = new AssistantMessageEventStream(); + const state = newBlockState(); + const written: Buffer[] = []; + const h2Request = { + write: (chunk: Buffer) => { + written.push(chunk); + return true; + }, + } as unknown as Parameters[5]; + const results: ToolResultMessage[] = []; + + await handleServerMessage( + create(AgentServerMessageSchema, { message: { case: "execServerMessage", value: message } }), + output, + stream, + state, + new Map(), + h2Request, + options.execHandlers, + result => { + results.push(result); + return result; + }, + { sawTokenDelta: false }, + options.requestContextTools ?? [], + ); + + return { frames: written.map(decodeClientFrame), output, results }; +} + +/** Strip the 5-byte Connect envelope (flags + big-endian length) and decode. */ +function decodeClientFrame(frame: Buffer): AgentClientMessage { + const length = frame.readUInt32BE(1); + return fromBinary(AgentClientMessageSchema, frame.subarray(5, 5 + length)); +} + +function buildExecMessage(message: ExecServerMessage["message"]): ExecServerMessage { + return create(ExecServerMessageSchema, { id: 7, execId: "exec-modern", message }); +} + +function mcpTool(name: string, providerIdentifier: string) { + return create(McpToolDefinitionSchema, { name, toolName: name, providerIdentifier, description: `${name} tool` }); +} + +function cursorAssistantMessage(): AssistantMessage { + return { + role: "assistant", + content: [], + api: "cursor-agent", + provider: "cursor", + model: "cursor-composer-2.5", + usage: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + totalTokens: 0, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, + }, + stopReason: "stop", + timestamp: 0, + }; +} + +function newBlockState(overrides: Partial = {}): BlockState { + let textBlock: BlockState["currentTextBlock"] = null; + let thinkingBlock: BlockState["currentThinkingBlock"] = null; + let toolCall: ToolCallState | null = null; + return { + get currentTextBlock() { + return textBlock; + }, + get currentThinkingBlock() { + return thinkingBlock; + }, + get currentToolCall() { + return toolCall; + }, + openToolCalls: new Map(), + resolvedMcpToolCallIds: new Set(), + firstTokenTime: undefined, + setTextBlock: b => { + textBlock = b; + }, + setThinkingBlock: b => { + thinkingBlock = b; + }, + setToolCall: t => { + toolCall = t; + }, + setFirstTokenTime: () => {}, + ...overrides, + }; +} + +function toolResult(text: string, extra?: Partial): ToolResultMessage { + return { + role: "toolResult", + toolCallId: "unused", + toolName: "unused", + content: [{ type: "text", text }], + isError: false, + timestamp: 1, + ...extra, + }; +} + +/** The single `execClientMessage` a dispatch produced, or a failure if it produced anything else. */ +function soleResult(frames: AgentClientMessage[]) { + expect(frames).toHaveLength(1); + const frame = frames[0].message; + if (frame.case !== "execClientMessage") throw new Error(`expected execClientMessage, got ${frame.case}`); + return frame.value.message; +} + +describe("Cursor modern exec protocol activation", () => { + it("advertises the client build whose schema includes modern exec frames", () => { + expect(CURSOR_CLIENT_VERSION).toBe("cli-2026.07.23-e383d2b"); + }); +}); + +describe("Cursor stream teardown", () => { + it("keeps whole arguments on a block still open when the transport ends", async () => { + // A connect-SCM block arrives with complete `arguments` and never feeds + // the streamed partial-JSON buffer. `parseStreamingJson(undefined)` + // returns `{}`, so reparsing every open block on teardown erased exactly + // those args — the interaction then rebuilt as an empty call. + const wire = create(ToolCallSchema, { + tool: { + case: "connectScmToolCall", + value: create(ConnectScmToolCallSchema, { + args: create(ConnectScmArgsSchema, { + toolCallId: "inner-id", + target: { + case: "github", + value: create(ConnectScmGithubSchema, { + repository: create(ConnectScmGithubRepositorySchema, { owner: "can1357", repo: "oh-my-pi" }), + }), + }, + }), + }), + }, + }); + const toolCall = fromBinary(ToolCallSchema, toBinary(ToolCallSchema, wire)); + + const output = cursorAssistantMessage(); + const stream = new AssistantMessageEventStream(); + const state = newBlockState(); + + // Start the call and leave it open: the transport dies before completion. + processInteractionUpdate( + { message: { case: "toolCallStarted", value: { callId: "envelope-a", toolCall } } }, + output, + stream, + state, + { sawTokenDelta: false }, + ); + + const block = output.content.find((b): b is ToolCallState => b.type === "toolCall"); + if (!block) throw new Error("expected an open tool-call block"); + const argsBeforeTeardown = block.arguments; + expect(argsBeforeTeardown).not.toEqual({}); + + flushOpenToolCalls(output, stream, state); + + expect(block.arguments).toEqual(argsBeforeTeardown); + // The block is closed, so no live card is left animating. + expect(state.openToolCalls.size).toBe(0); + expect(state.currentToolCall).toBeNull(); + }); + + it("salvages a truncated streamed argument buffer into partial arguments", async () => { + // The other half of the same contract: blocks that *do* stream their args + // must still be reparsed on teardown, or a cut-short call renders with no + // arguments at all. + const output = cursorAssistantMessage(); + const stream = new AssistantMessageEventStream(); + const state = newBlockState(); + + processInteractionUpdate( + { + message: { + case: "toolCallStarted", + value: { + callId: "envelope-mcp", + toolCall: { tool: { case: "mcpToolCall", value: { args: { toolCallId: "mcp-1" } } } }, + }, + }, + }, + output, + stream, + state, + { sawTokenDelta: false }, + ); + + const block = output.content.find((b): b is ToolCallState => b.type === "toolCall"); + if (!block) throw new Error("expected an open tool-call block"); + setStreamingPartialJson(block, '{"path":"/repo/a.ts"'); + + flushOpenToolCalls(output, stream, state); + + expect(block.arguments).toEqual({ path: "/repo/a.ts" }); + }); + + it("pairs a server-owned call the transport cut short", async () => { + // `connect_scm` and native todo blocks are stamped `kCursorExecResolved` + // at start, so `agent-loop.ts` synthesizes no placeholder for them and + // only their completion frame pairs a result. A transport that dies + // first left the call unpaired, and `buildSessionContext` strips a + // dangling call — the whole interaction vanished from replay. + const output = cursorAssistantMessage(); + const stream = new AssistantMessageEventStream(); + const paired: ToolResultMessage[] = []; + const state = newBlockState({ onToolResult: result => void paired.push(result) }); + + processInteractionUpdate( + { + message: { + case: "toolCallStarted", + value: { + callId: "envelope-todo", + toolCall: { tool: { case: "updateTodosToolCall", value: { args: { todos: [] } } } }, + }, + }, + }, + output, + stream, + state, + { sawTokenDelta: false }, + ); + + const block = output.content.find((b): b is ToolCallState => b.type === "toolCall"); + if (!block) throw new Error("expected an open tool-call block"); + expect(paired).toHaveLength(0); + + flushOpenToolCalls(output, stream, state); + + expect(paired).toHaveLength(1); + expect(paired[0].toolCallId).toBe(block.id); + expect(paired[0].isError).toBe(true); + }); + + it("leaves an exec-settled MCP call to the dispatch that owns its result", async () => { + // MCP blocks are also marked resolved, but by the exec dispatch, which + // already emitted their result and is awaited before teardown. Pairing + // one here too would file a duplicate against the same `toolCallId`. + const output = cursorAssistantMessage(); + const stream = new AssistantMessageEventStream(); + const paired: ToolResultMessage[] = []; + const state = newBlockState({ onToolResult: result => void paired.push(result) }); + state.resolvedMcpToolCallIds.add("mcp-1"); + + processInteractionUpdate( + { + message: { + case: "toolCallStarted", + value: { + callId: "envelope-mcp", + toolCall: { tool: { case: "mcpToolCall", value: { args: { toolCallId: "mcp-1" } } } }, + }, + }, + }, + output, + stream, + state, + { sawTokenDelta: false }, + ); + + flushOpenToolCalls(output, stream, state); + + expect(paired).toEqual([]); + }); +}); + +describe("Cursor modern exec frames: failure channel", () => { + it("throws on a frame whose oneof this build does not model, instead of stranding the exec id", async () => { + // A oneof number absent from agent.proto decodes into unknown fields and + // leaves `message.case` unset. The old code returned silently, so the + // server waited on a reply that never arrived. + const message = create(ExecServerMessageSchema, { id: 11, execId: "exec-unknown" }); + + const { frames } = await dispatchExec(message); + + const control = frames[0].message; + expect(control.case).toBe("execClientControlMessage"); + if (control.case !== "execClientControlMessage") throw new Error("unreachable"); + expect(control.value.message.case).toBe("throw"); + if (control.value.message.case !== "throw") throw new Error("unreachable"); + expect(control.value.message.value.id).toBe(11); + expect(control.value.message.value.errorCode).toBe("unknown_exec_variant"); + + // A throw must be followed by a stream close, or the exec stays open. + const close = frames[1].message; + expect(close.case).toBe("execClientControlMessage"); + if (close.case !== "execClientControlMessage") throw new Error("unreachable"); + expect(close.value.message.case).toBe("streamClose"); + }); + + it("throws for gitDiffRequest, whose response type has no error variant", async () => { + // `GetDiffResponse` models five output formats and no failure, so any + // in-band answer claims a diff was computed. + const { frames } = await dispatchExec( + buildExecMessage({ + case: "gitDiffRequest", + value: create(GetDiffRequestSchema, { cwd: "/repo", ref: "HEAD" }), + }), + ); + + const control = frames[0].message; + if (control.case !== "execClientControlMessage") throw new Error(`got ${control.case}`); + expect(control.value.message.case).toBe("throw"); + if (control.value.message.case !== "throw") throw new Error("unreachable"); + expect(control.value.message.value.errorCode).toBe("exec_variant_unsupported"); + }); +}); + +describe("Cursor modern exec frames: no answer carries an unset oneof", () => { + // A result whose `result` oneof is unset is indistinguishable from "the tool + // ran and produced nothing". Every one of these frames was previously + // answered that way, or is newly handled and must not regress into it. + const cases: [name: string, message: ExecServerMessage, resultCase: string][] = [ + [ + "listMcpResourcesExecArgs", + buildExecMessage({ + case: "listMcpResourcesExecArgs", + value: create(ListMcpResourcesExecArgsSchema, {}), + }), + "success", + ], + [ + "readMcpResourceExecArgs", + buildExecMessage({ + case: "readMcpResourceExecArgs", + value: create(ReadMcpResourceExecArgsSchema, { uri: "file:///nope" }), + }), + "notFound", + ], + [ + "recordScreenArgs", + buildExecMessage({ case: "recordScreenArgs", value: create(RecordScreenArgsSchema, {}) }), + "failure", + ], + [ + "computerUseArgs", + buildExecMessage({ case: "computerUseArgs", value: create(ComputerUseArgsSchema, {}) }), + "error", + ], + [ + "subagentArgs", + buildExecMessage({ + case: "subagentArgs", + value: create(SubagentArgsSchema, { toolCallId: "c1", subagentType: "explore", prompt: "go" }), + }), + "error", + ], + [ + "subagentAwaitArgs", + buildExecMessage({ + case: "subagentAwaitArgs", + value: create(SubagentAwaitArgsSchema, { agentId: "agent-1", timeoutMs: 10 }), + }), + "notFound", + ], + [ + "smartModeClassifierArgs", + buildExecMessage({ + case: "smartModeClassifierArgs", + value: create(SmartModeClassifierArgsSchema, { toolCallId: "c1" }), + }), + "error", + ], + [ + "canvasDiagnosticsArgs", + buildExecMessage({ + case: "canvasDiagnosticsArgs", + value: create(CanvasDiagnosticsArgsSchema, { path: "/a.ts", toolCallId: "c1" }), + }), + "error", + ], + [ + "conversationSearchArgs", + buildExecMessage({ + case: "conversationSearchArgs", + value: create(ConversationSearchArgsSchema, { query: "chess", toolCallId: "c1" }), + }), + "error", + ], + [ + "agentStoreConflictArgs", + buildExecMessage({ + case: "agentStoreConflictArgs", + value: create(AgentStoreConflictArgsSchema, {}), + }), + "error", + ], + ]; + + for (const [name, message, resultCase] of cases) { + it(`answers ${name} with a set '${resultCase}' variant`, async () => { + const { frames } = await dispatchExec(message); + const answer = soleResult(frames); + expect(answer.case).toBeDefined(); + const value = answer.value; + expect(value).toHaveProperty("result"); + const result = (value as { result: { case?: string } }).result; + expect(result.case).toBe(resultCase); + }); + } + + it("reports the requested uri back on a not-found MCP resource read", async () => { + const { frames } = await dispatchExec( + buildExecMessage({ + case: "readMcpResourceExecArgs", + value: create(ReadMcpResourceExecArgsSchema, { uri: "mcp://server/thing" }), + }), + ); + const answer = soleResult(frames); + if (answer.case !== "readMcpResourceExecResult") throw new Error(`got ${answer.case}`); + if (answer.value.result.case !== "notFound") throw new Error("expected notFound"); + expect(answer.value.result.value.uri).toBe("mcp://server/thing"); + }); + + it("echoes the requested path on a canvas diagnostics error", async () => { + const { frames } = await dispatchExec( + buildExecMessage({ + case: "canvasDiagnosticsArgs", + value: create(CanvasDiagnosticsArgsSchema, { path: "/canvas/a.ts", toolCallId: "c1" }), + }), + ); + const answer = soleResult(frames); + if (answer.case !== "canvasDiagnosticsResult") throw new Error(`got ${answer.case}`); + if (answer.value.result.case !== "error") throw new Error("expected error"); + expect(answer.value.result.value.path).toBe("/canvas/a.ts"); + }); + + it("reports the awaited agent id back as not found", async () => { + const { frames } = await dispatchExec( + buildExecMessage({ + case: "subagentAwaitArgs", + value: create(SubagentAwaitArgsSchema, { agentId: "agent-42", timeoutMs: 5 }), + }), + ); + const answer = soleResult(frames); + if (answer.case !== "subagentAwaitResult") throw new Error(`got ${answer.case}`); + if (answer.value.result.case !== "notFound") throw new Error("expected notFound"); + expect(answer.value.result.value.agentId).toBe("agent-42"); + }); +}); + +describe("Cursor MCP resource frames answer from the host's servers", () => { + it("returns the resources the host advertises, with their server names", async () => { + // The empty catalog above is the no-handler fallback. A host holding live + // MCP connections must answer from them, or its resources are invisible + // to Cursor even though the same session is connected to the servers. + const { frames } = await dispatchExec( + buildExecMessage({ case: "listMcpResourcesExecArgs", value: create(ListMcpResourcesExecArgsSchema, {}) }), + { + execHandlers: { + listMcpResources: async () => [ + { uri: "docs://readme", name: "README", mimeType: "text/markdown", server: "docs" }, + ], + }, + }, + ); + const answer = soleResult(frames); + if (answer.case !== "listMcpResourcesExecResult") throw new Error(`got ${answer.case}`); + if (answer.value.result.case !== "success") throw new Error(`got ${answer.value.result.case}`); + expect(answer.value.result.value.resources).toHaveLength(1); + const [resource] = answer.value.result.value.resources; + expect(resource.uri).toBe("docs://readme"); + // Cursor addresses the follow-up read by this name. + expect(resource.server).toBe("docs"); + expect(resource.mimeType).toBe("text/markdown"); + }); + + it("passes the frame's server filter through to the host", async () => { + let sawFilter: string | undefined; + await dispatchExec( + buildExecMessage({ + case: "listMcpResourcesExecArgs", + value: create(ListMcpResourcesExecArgsSchema, { server: "issues" }), + }), + { + execHandlers: { + listMcpResources: async ({ server }) => { + sawFilter = server; + return []; + }, + }, + }, + ); + expect(sawFilter).toBe("issues"); + }); + + it("answers a read with the host's text content", async () => { + const { frames } = await dispatchExec( + buildExecMessage({ + case: "readMcpResourceExecArgs", + value: create(ReadMcpResourceExecArgsSchema, { server: "docs", uri: "docs://readme" }), + }), + { + execHandlers: { + readMcpResource: async ({ uri }) => ({ uri, mimeType: "text/markdown", text: "# Title" }), + }, + }, + ); + const answer = soleResult(frames); + if (answer.case !== "readMcpResourceExecResult") throw new Error(`got ${answer.case}`); + if (answer.value.result.case !== "success") throw new Error(`got ${answer.value.result.case}`); + expect(answer.value.result.value.content).toEqual({ case: "text", value: "# Title" }); + }); + + it("forwards a download request and answers with the path, not the content", async () => { + // `download_path` is a different contract: the host writes the resource + // to that workspace-relative path and the model is told where it landed. + // Returning content anyway would put the payload back in context, which + // is exactly what the download mode exists to avoid. + let sawDownloadPath: string | undefined; + const { frames } = await dispatchExec( + buildExecMessage({ + case: "readMcpResourceExecArgs", + value: create(ReadMcpResourceExecArgsSchema, { + server: "files", + uri: "files://logo", + downloadPath: "assets/logo.png", + }), + }), + { + execHandlers: { + readMcpResource: async ({ uri, downloadPath }) => { + sawDownloadPath = downloadPath; + // A host that also has the bytes on hand must still not have + // them forwarded: the download path is the whole answer. + return { uri, mimeType: "image/png", downloadPath, text: "inline payload" }; + }, + }, + }, + ); + expect(sawDownloadPath).toBe("assets/logo.png"); + const answer = soleResult(frames); + if (answer.case !== "readMcpResourceExecResult") throw new Error(`got ${answer.case}`); + if (answer.value.result.case !== "success") throw new Error(`got ${answer.value.result.case}`); + expect(answer.value.result.value.downloadPath).toBe("assets/logo.png"); + expect(answer.value.result.value.content.case).toBeUndefined(); + }); + + it("distinguishes a missing resource from a failing host", async () => { + // `null` is "no such server or uri", which is `not_found`. A throw is a + // real failure and must not masquerade as a missing resource — the model + // would retry a different uri instead of surfacing the fault. + const missing = await dispatchExec( + buildExecMessage({ + case: "readMcpResourceExecArgs", + value: create(ReadMcpResourceExecArgsSchema, { server: "docs", uri: "docs://gone" }), + }), + { execHandlers: { readMcpResource: async () => null } }, + ); + const missingAnswer = soleResult(missing.frames); + if (missingAnswer.case !== "readMcpResourceExecResult") throw new Error(`got ${missingAnswer.case}`); + expect(missingAnswer.value.result.case).toBe("notFound"); + + const broken = await dispatchExec( + buildExecMessage({ + case: "readMcpResourceExecArgs", + value: create(ReadMcpResourceExecArgsSchema, { server: "docs", uri: "docs://readme" }), + }), + { + execHandlers: { + readMcpResource: async () => { + throw new Error("server disconnected"); + }, + }, + }, + ); + const brokenAnswer = soleResult(broken.frames); + if (brokenAnswer.case !== "readMcpResourceExecResult") throw new Error(`got ${brokenAnswer.case}`); + if (brokenAnswer.value.result.case !== "error") throw new Error(`got ${brokenAnswer.value.result.case}`); + expect(brokenAnswer.value.result.value.error).toContain("server disconnected"); + }); + + it("records a resource read as a paired transcript block", async () => { + // The read runs locally and, in download mode, writes a workspace file. + // An exec frame with no synthesized block is invisible in the UI, and an + // unpaired call is stripped by `buildSessionContext` — taking the whole + // interaction out of every rebuilt transcript. + const { output, results } = await dispatchExec( + buildExecMessage({ + case: "readMcpResourceExecArgs", + value: create(ReadMcpResourceExecArgsSchema, { + server: "docs", + uri: "docs://readme", + downloadPath: "assets/readme.md", + }), + }), + { + execHandlers: { + readMcpResource: async ({ uri, downloadPath }) => ({ uri, mimeType: "text/markdown", downloadPath }), + }, + }, + ); + + const blocks = output.content.filter((block): block is ToolCallState => block.type === "toolCall"); + expect(blocks).toHaveLength(1); + // Not `read`: this is a remote MCP operation, and the name drives + // rendering and prune semantics. + expect(blocks[0].name).toBe("read_mcp_resource"); + expect(blocks[0].arguments).toEqual({ + server: "docs", + uri: "docs://readme", + download_path: "assets/readme.md", + }); + // Paired under the same id, and a success is not filed as a failure. + expect(results.map(r => r.toolCallId)).toEqual([blocks[0].id]); + expect(results[0].isError).toBe(false); + expect(results[0].content).toEqual([{ type: "text", text: "Downloaded docs://readme to assets/readme.md" }]); + }); + + it("pairs a failed resource read as an error, still under one block", async () => { + // A refused download (no write grant, a path outside the workspace) and + // a dead server both land here. The block must resolve — an unpaired + // call strips the interaction — and must resolve as an error, or the + // transcript shows a read that never happened as having succeeded. + const { output, results } = await dispatchExec( + buildExecMessage({ + case: "readMcpResourceExecArgs", + value: create(ReadMcpResourceExecArgsSchema, { server: "docs", uri: "docs://readme" }), + }), + { + execHandlers: { + readMcpResource: async () => { + throw new Error("Refusing to download outside the workspace: ../escape"); + }, + }, + }, + ); + + const blocks = output.content.filter((block): block is ToolCallState => block.type === "toolCall"); + expect(blocks).toHaveLength(1); + expect(results.map(r => r.toolCallId)).toEqual([blocks[0].id]); + expect(results[0].isError).toBe(true); + expect(results[0].content).toEqual([ + { type: "text", text: "Refusing to download outside the workspace: ../escape" }, + ]); + }); + + it("leaves no block when no handler ran", async () => { + // Without a handler the frame is answered from a fixed `not_found` and + // nothing executed, so a block would claim work that never happened. + const { output, results } = await dispatchExec( + buildExecMessage({ + case: "readMcpResourceExecArgs", + value: create(ReadMcpResourceExecArgsSchema, { server: "docs", uri: "docs://readme" }), + }), + ); + expect(output.content.filter(block => block.type === "toolCall")).toHaveLength(0); + expect(results).toHaveLength(0); + }); + + it("records a handled listing as a block with a paired result", async () => { + // The model consumes this catalog: without a block and a paired result + // the listing is invisible in the UI and stripped from rebuilt history. + const { output, results } = await dispatchExec( + buildExecMessage({ + case: "listMcpResourcesExecArgs", + value: create(ListMcpResourcesExecArgsSchema, { server: "docs" }), + }), + { + execHandlers: { + listMcpResources: async () => [ + { uri: "docs://readme", name: "README", mimeType: "text/markdown", server: "docs" }, + ], + }, + }, + ); + const blocks = output.content.filter((block): block is ToolCallState => block.type === "toolCall"); + expect(blocks).toHaveLength(1); + expect(blocks[0].name).toBe("list_mcp_resources"); + expect(results.map(r => r.toolCallId)).toEqual([blocks[0].id]); + expect(results[0].isError).toBe(false); + }); + + it("leaves no block for a listing no handler answered", async () => { + // The no-handler fallback is a fixed empty catalog: nothing executed, so + // a block would claim work that never happened. + const { output, results } = await dispatchExec( + buildExecMessage({ case: "listMcpResourcesExecArgs", value: create(ListMcpResourcesExecArgsSchema, {}) }), + ); + expect(output.content.filter(block => block.type === "toolCall")).toHaveLength(0); + expect(results).toHaveLength(0); + }); + + it("reports a failing list as an error, not an empty catalog", async () => { + // An empty success says "asked, none exist" — a lie when the lookup + // failed, and one the model cannot retry. + const { frames } = await dispatchExec( + buildExecMessage({ case: "listMcpResourcesExecArgs", value: create(ListMcpResourcesExecArgsSchema, {}) }), + { + execHandlers: { + listMcpResources: async () => { + throw new Error("registry unavailable"); + }, + }, + }, + ); + const answer = soleResult(frames); + if (answer.case !== "listMcpResourcesExecResult") throw new Error(`got ${answer.case}`); + if (answer.value.result.case !== "error") throw new Error(`got ${answer.value.result.case}`); + expect(answer.value.result.value.error).toContain("registry unavailable"); + }); +}); + +describe("Cursor modern exec frames: status and precheck answers", () => { + it("reports NOT_FOUND for force-background requests, since nothing runs in the background", async () => { + const shell = await dispatchExec( + buildExecMessage({ + case: "forceBackgroundShellArgs", + value: create(ForceBackgroundShellArgsSchema, { toolCallId: "c1" }), + }), + ); + const shellAnswer = soleResult(shell.frames); + if (shellAnswer.case !== "forceBackgroundShellResult") throw new Error(`got ${shellAnswer.case}`); + expect(shellAnswer.value.status).toBe(ForceBackgroundShellStatus.NOT_FOUND); + + const subagent = await dispatchExec( + buildExecMessage({ + case: "forceBackgroundSubagentArgs", + value: create(ForceBackgroundSubagentArgsSchema, { toolCallId: "c1" }), + }), + ); + const subagentAnswer = soleResult(subagent.frames); + if (subagentAnswer.case !== "forceBackgroundSubagentResult") throw new Error(`got ${subagentAnswer.case}`); + expect(subagentAnswer.value.status).toBe(ForceBackgroundSubagentStatus.NOT_FOUND); + }); + + it("declines every allowlist precheck, since no allowlist is configured", async () => { + // `true` here would grant an approval bypass that was never configured. + const shell = await dispatchExec( + buildExecMessage({ + case: "shellAllowlistPrecheckArgs", + value: create(ShellAllowlistPrecheckArgsSchema, { command: "rm -rf /", workingDirectory: "/" }), + }), + ); + const shellAnswer = soleResult(shell.frames); + if (shellAnswer.case !== "shellAllowlistPrecheckResult") throw new Error(`got ${shellAnswer.case}`); + expect(shellAnswer.value.allowlisted).toBe(false); + + const mcp = await dispatchExec( + buildExecMessage({ + case: "mcpAllowlistPrecheckArgs", + value: create(McpAllowlistPrecheckArgsSchema, { providerIdentifier: "pi-agent", toolName: "task" }), + }), + ); + const mcpAnswer = soleResult(mcp.frames); + if (mcpAnswer.case !== "mcpAllowlistPrecheckResult") throw new Error(`got ${mcpAnswer.case}`); + expect(mcpAnswer.value.allowlisted).toBe(false); + + const web = await dispatchExec( + buildExecMessage({ + case: "webFetchAllowlistPrecheckArgs", + value: create(WebFetchAllowlistPrecheckArgsSchema, { url: "https://example.com" }), + }), + ); + const webAnswer = soleResult(web.frames); + if (webAnswer.case !== "webFetchAllowlistPrecheckResult") throw new Error(`got ${webAnswer.case}`); + expect(webAnswer.value.allowlisted).toBe(false); + }); +}); + +describe("Cursor modern exec frames: hooks", () => { + it("answers a hook query with the matching response case and an empty payload", async () => { + // The request and response oneofs are parallel; a response of the wrong + // case is read as a different hook entirely. + const { frames } = await dispatchExec( + buildExecMessage({ + case: "executeHookArgs", + value: create(ExecuteHookArgsSchema, { + request: create(ExecuteHookRequestSchema, { + request: { case: "postToolUse", value: create(PostToolUseRequestQuerySchema, {}) }, + }), + }), + }), + ); + + const answer = soleResult(frames); + if (answer.case !== "executeHookResult") throw new Error(`got ${answer.case}`); + expect(answer.value.response?.response.case).toBe("postToolUse"); + if (answer.value.response?.response.case !== "postToolUse") throw new Error("unreachable"); + // Every field is optional; this client runs no hooks, so it contributes none. + expect(answer.value.response.response.value.additionalContext).toBeUndefined(); + }); + + it("maps every modelled hook request onto its parallel response case", async () => { + // The request and response oneofs are parallel by field number. A single + // mis-wired branch answers a different hook than the one asked, which the + // server reads as a stalled or misrouted hook — invisible unless every + // variant is exercised. + const requestCases = [ + "preCompact", + "subagentStart", + "subagentStop", + "preToolUse", + "postToolUse", + "postToolUseFailure", + "beforeSubmitPrompt", + "afterAgentResponse", + "afterAgentThought", + "stop", + ] as const; + + for (const requestCase of requestCases) { + const request = create(ExecuteHookRequestSchema, { + request: { case: requestCase, value: {} }, + } as never); + const { frames } = await dispatchExec( + buildExecMessage({ case: "executeHookArgs", value: create(ExecuteHookArgsSchema, { request }) }), + ); + + const answer = soleResult(frames); + if (answer.case !== "executeHookResult") throw new Error(`${requestCase}: got ${answer.case}`); + expect(answer.value.response?.response.case).toBe(requestCase); + } + }); + + it("throws for a hook request whose case this build does not model", async () => { + const { frames } = await dispatchExec( + buildExecMessage({ + case: "executeHookArgs", + value: create(ExecuteHookArgsSchema, { request: create(ExecuteHookRequestSchema, {}) }), + }), + ); + + const control = frames[0].message; + if (control.case !== "execClientControlMessage") throw new Error(`got ${control.case}`); + expect(control.value.message.case).toBe("throw"); + if (control.value.message.case !== "throw") throw new Error("unreachable"); + expect(control.value.message.value.errorCode).toBe("unknown_hook_request"); + }); +}); + +describe("Cursor modern exec frames: MCP state", () => { + it("regroups the advertised tool catalog by provider identifier", async () => { + const { frames } = await dispatchExec( + buildExecMessage({ case: "mcpStateExecArgs", value: create(McpStateExecArgsSchema, {}) }), + { + requestContextTools: [ + mcpTool("task", "pi-agent"), + mcpTool("hub", "pi-agent"), + mcpTool("mcp__fixture_report", "fixture"), + ], + }, + ); + + const answer = soleResult(frames); + if (answer.case !== "mcpStateExecResult") throw new Error(`got ${answer.case}`); + if (answer.value.result.case !== "success") throw new Error("expected success"); + const servers = answer.value.result.value.servers; + expect(servers.map(server => server.serverIdentifier).sort()).toEqual(["fixture", "pi-agent"]); + const piAgent = servers.find(server => server.serverIdentifier === "pi-agent"); + expect(piAgent?.tools.map(t => t.name)).toEqual(["task", "hub"]); + }); + + it("restricts the answer to the requested server identifiers", async () => { + const { frames } = await dispatchExec( + buildExecMessage({ + case: "mcpStateExecArgs", + value: create(McpStateExecArgsSchema, { serverIdentifiers: ["fixture"] }), + }), + { requestContextTools: [mcpTool("task", "pi-agent"), mcpTool("mcp__fixture_report", "fixture")] }, + ); + + const answer = soleResult(frames); + if (answer.case !== "mcpStateExecResult") throw new Error(`got ${answer.case}`); + if (answer.value.result.case !== "success") throw new Error("expected success"); + expect(answer.value.result.value.servers.map(s => s.serverIdentifier)).toEqual(["fixture"]); + }); +}); + +describe("Cursor modern exec frames: redacted read", () => { + it("refuses redactedRead rather than serving the unredacted file", async () => { + // The frame exists so the client can strip secrets first. No redaction is + // implemented, so answering with a plain read would leak exactly what the + // frame withholds. + const readHandler: CursorExecHandlers = { + async read() { + return toolResult("SECRET=hunter2"); + }, + }; + const { frames } = await dispatchExec( + buildExecMessage({ + case: "redactedReadArgs", + value: create(ReadArgsSchema, { path: "/repo/.env", toolCallId: "c1" }), + }), + { execHandlers: readHandler }, + ); + + const answer = soleResult(frames); + if (answer.case !== "redactedReadResult") throw new Error(`got ${answer.case}`); + expect(answer.value.result.case).toBe("error"); + if (answer.value.result.case !== "error") throw new Error("unreachable"); + expect(answer.value.result.value.error).toContain("redaction"); + expect(JSON.stringify(answer.value)).not.toContain("hunter2"); + }); +}); + +describe("Cursor modern exec frames: Pi tools", () => { + it("round-trips piRead through its handler into a typed success", async () => { + const { frames, output, results } = await dispatchExec( + buildExecMessage({ + case: "piReadArgs", + value: create(PiReadExecArgsSchema, { path: "/repo/a.ts", offset: 5, limit: 20 }), + }), + { + execHandlers: { + async piRead(call) { + expect(call.args.path).toBe("/repo/a.ts"); + expect(call.args.offset).toBe(5); + // The Pi frames carry no tool_call_id; the dispatcher mints one. + expect(call.toolCallId).toBeTruthy(); + // Like the real bridge, the handler files its result under the id + // it was handed. + return toolResult("line one\nline two", { toolCallId: call.toolCallId, toolName: "read" }); + }, + }, + }, + ); + + const answer = soleResult(frames); + if (answer.case !== "piReadResult") throw new Error(`got ${answer.case}`); + if (answer.value.result.case !== "success") throw new Error("expected success"); + expect(answer.value.result.value.output).toBe("line one\nline two"); + + // The synthesized block and its paired result must share the minted id, or + // the call is stripped as dangling on replay. + const blocks = output.content.filter((block): block is ToolCallState => block.type === "toolCall"); + expect(blocks).toHaveLength(1); + expect(blocks[0].name).toBe("read"); + expect(results.map(r => r.toolCallId)).toEqual([blocks[0].id]); + // The displayed args must be the operation that actually runs. The bridge + // composes offset/limit into `read`'s `:raw:N+K` selector, so a block + // showing the bare path claims a whole-file read that never happened. + expect(blocks[0].arguments).toEqual({ path: "/repo/a.ts:raw:5+20" }); + }); + + it("maps a failing Pi handler onto the frame's own error variant", async () => { + const { frames } = await dispatchExec( + buildExecMessage({ case: "piReadArgs", value: create(PiReadExecArgsSchema, { path: "/nope" }) }), + { + execHandlers: { + async piRead(call) { + return toolResult("ENOENT: /nope", { isError: true, toolCallId: call.toolCallId }); + }, + }, + }, + ); + + const answer = soleResult(frames); + if (answer.case !== "piReadResult") throw new Error(`got ${answer.case}`); + if (answer.value.result.case !== "error") throw new Error("expected error"); + expect(answer.value.result.value.error).toBe("ENOENT: /nope"); + }); + + it("rejects a Pi frame with no handler installed instead of faking a success", async () => { + const { frames } = await dispatchExec( + buildExecMessage({ case: "piGrepArgs", value: create(PiGrepExecArgsSchema, { pattern: "foo" }) }), + ); + + const answer = soleResult(frames); + if (answer.case !== "piGrepResult") throw new Error(`got ${answer.case}`); + expect(answer.value.result.case).toBe("error"); + }); + + it("answers an unavailable edit or write with the protocol's rejected variant", async () => { + // These two results model refusal and failure as separate oneof cases. + // A denial answered as `error` reads as "the tool ran and broke", which + // invites a retry of an operation that was never permitted. + const edit = soleResult( + ( + await dispatchExec( + buildExecMessage({ + case: "piEditArgs", + value: create(PiEditExecArgsSchema, { + path: "/a.ts", + edits: [create(PiEditReplacementSchema, { oldText: "x", newText: "y" })], + }), + }), + ) + ).frames, + ); + if (edit.case !== "piEditResult") throw new Error(`got ${edit.case}`); + expect(edit.value.result.case).toBe("rejected"); + + const write = soleResult( + ( + await dispatchExec( + buildExecMessage({ + case: "piWriteArgs", + value: create(PiWriteExecArgsSchema, { path: "/a.ts", content: "x" }), + }), + ) + ).frames, + ); + if (write.case !== "piWriteResult") throw new Error(`got ${write.case}`); + expect(write.value.result.case).toBe("rejected"); + }); + + it("carries a truncation summary onto the Pi success payload", async () => { + const { frames } = await dispatchExec( + buildExecMessage({ case: "piBashArgs", value: create(PiBashExecArgsSchema, { command: "yes" }) }), + { + execHandlers: { + async piBash() { + return toolResult("out", { + details: { + truncation: { + truncated: true, + truncatedBy: "lines", + totalLines: 5000, + outputLines: 300, + outputBytes: 4096, + }, + }, + }); + }, + }, + }, + ); + + const answer = soleResult(frames); + if (answer.case !== "piBashResult") throw new Error(`got ${answer.case}`); + if (answer.value.result.case !== "success") throw new Error("expected success"); + expect(answer.value.result.value.truncation?.truncated).toBe(true); + expect(answer.value.result.value.truncation?.truncatedBy).toBe("lines"); + expect(answer.value.result.value.truncation?.totalLines).toBe(5000); + }); + + it("reads truncation from the `details.meta.truncation` shape real Bash results use", async () => { + // `BashTool` files its summary under `details.meta.truncation` + // (`TruncationMeta`), which carries no `truncated` flag — its presence is + // the signal. Reading only the top-level `TruncationResult` shape handed + // Cursor clipped output with no notice that it was clipped. + const { frames } = await dispatchExec( + buildExecMessage({ case: "piBashArgs", value: create(PiBashExecArgsSchema, { command: "yes" }) }), + { + execHandlers: { + async piBash() { + return toolResult("out", { + details: { + meta: { + truncation: { + direction: "head", + truncatedBy: "bytes", + totalLines: 5000, + totalBytes: 120000, + outputLines: 300, + outputBytes: 4096, + }, + }, + }, + }); + }, + }, + }, + ); + + const answer = soleResult(frames); + if (answer.case !== "piBashResult") throw new Error(`got ${answer.case}`); + if (answer.value.result.case !== "success") throw new Error("expected success"); + expect(answer.value.result.value.truncation?.truncated).toBe(true); + expect(answer.value.result.value.truncation?.truncatedBy).toBe("bytes"); + expect(answer.value.result.value.truncation?.totalLines).toBe(5000); + expect(answer.value.result.value.truncation?.outputBytes).toBe(4096); + }); + + it("omits truncation entirely when nothing was truncated", async () => { + // `optional PiTruncation` — a zeroed message would claim the output was + // trimmed to nothing. + const { frames } = await dispatchExec( + buildExecMessage({ case: "piBashArgs", value: create(PiBashExecArgsSchema, { command: "echo hi" }) }), + { + execHandlers: { + async piBash() { + return toolResult("hi", { details: { truncation: { truncated: false, totalLines: 1 } } }); + }, + }, + }, + ); + + const answer = soleResult(frames); + if (answer.case !== "piBashResult") throw new Error(`got ${answer.case}`); + if (answer.value.result.case !== "success") throw new Error("expected success"); + expect(answer.value.result.value.truncation).toBeUndefined(); + }); + + it("passes edit replacements through to the handler and surfaces diff/patch", async () => { + const { frames, output } = await dispatchExec( + buildExecMessage({ + case: "piEditArgs", + value: create(PiEditExecArgsSchema, { + path: "/repo/a.ts", + edits: [create(PiEditReplacementSchema, { oldText: "before", newText: "after" })], + }), + }), + { + execHandlers: { + async piEdit(call) { + expect(call.args.edits[0].oldText).toBe("before"); + return toolResult("edited", { details: { diff: "-before\n+after", patch: "@@" } }); + }, + }, + }, + ); + + const answer = soleResult(frames); + if (answer.case !== "piEditResult") throw new Error(`got ${answer.case}`); + if (answer.value.result.case !== "success") throw new Error("expected success"); + expect(answer.value.result.value.diff).toBe("-before\n+after"); + expect(answer.value.result.value.patch).toBe("@@"); + + // The synthesized display block must use the local edit tool's snake_case + // replace schema, or the rebuilt transcript renders empty edits. + const block = output.content.find((b): b is ToolCallState => b.type === "toolCall"); + expect(block?.name).toBe("edit"); + expect(block?.arguments).toEqual({ path: "/repo/a.ts", edits: [{ old_text: "before", new_text: "after" }] }); + }); + + it("answers each remaining Pi frame with a populated success payload", async () => { + // The outer result case alone is not proof: an unset inner oneof reads as + // "the tool ran and produced nothing", and dropped output or limit + // metadata is invisible at the discriminator level. + const handlers: CursorExecHandlers = { + async piWrite() { + return toolResult("wrote"); + }, + async piGrep() { + return toolResult("a.ts:1:hit", { details: { perFileLimitReached: 20, linesTruncated: true } }); + }, + async piFind() { + return toolResult("a.ts", { details: { resultLimitReached: 200 } }); + }, + async piLs() { + return toolResult("a.ts\nb.ts", { details: { resultLimitReached: 500 } }); + }, + }; + + const write = soleResult( + ( + await dispatchExec( + buildExecMessage({ + case: "piWriteArgs", + value: create(PiWriteExecArgsSchema, { path: "/a.ts", content: "x" }), + }), + { execHandlers: handlers }, + ) + ).frames, + ); + if (write.case !== "piWriteResult") throw new Error(`got ${write.case}`); + if (write.value.result.case !== "success") throw new Error("expected success"); + expect(write.value.result.value.output).toBe("wrote"); + + const grep = soleResult( + ( + await dispatchExec( + buildExecMessage({ case: "piGrepArgs", value: create(PiGrepExecArgsSchema, { pattern: "hit" }) }), + { execHandlers: handlers }, + ) + ).frames, + ); + if (grep.case !== "piGrepResult") throw new Error(`got ${grep.case}`); + if (grep.value.result.case !== "success") throw new Error("expected success"); + expect(grep.value.result.value.output).toBe("a.ts:1:hit"); + expect(grep.value.result.value.matchLimitReached).toBe(20); + expect(grep.value.result.value.linesTruncated).toBe(true); + + const find = soleResult( + ( + await dispatchExec( + buildExecMessage({ case: "piFindArgs", value: create(PiFindExecArgsSchema, { pattern: "*.ts" }) }), + { execHandlers: handlers }, + ) + ).frames, + ); + if (find.case !== "piFindResult") throw new Error(`got ${find.case}`); + if (find.value.result.case !== "success") throw new Error("expected success"); + expect(find.value.result.value.output).toBe("a.ts"); + expect(find.value.result.value.resultLimitReached).toBe(200); + + const ls = soleResult( + ( + await dispatchExec(buildExecMessage({ case: "piLsArgs", value: create(PiLsExecArgsSchema, {}) }), { + execHandlers: handlers, + }) + ).frames, + ); + if (ls.case !== "piLsResult") throw new Error(`got ${ls.case}`); + if (ls.value.result.case !== "success") throw new Error("expected success"); + expect(ls.value.result.value.output).toBe("a.ts\nb.ts"); + expect(ls.value.result.value.entryLimitReached).toBe(500); + }); + + it("serves miniSweAgentBash from the existing shell handler under its own frame", async () => { + // Frame 52 carries the same ShellArgs/ShellResult pair as `shellArgs`, so + // the shell handler answers it unchanged — under result field 55. + const { frames } = await dispatchExec( + buildExecMessage({ + case: "miniSweAgentBashArgs", + value: create(ShellArgsSchema, { command: "echo hi", workingDirectory: "/repo", toolCallId: "c1" }), + }), + { + execHandlers: { + async shell(args) { + expect(args.command).toBe("echo hi"); + return toolResult("hi", { toolCallId: "c1", toolName: "bash" }); + }, + }, + }, + ); + + const answer = soleResult(frames); + expect(answer.case).toBe("miniSweAgentBashResult"); + if (answer.case !== "miniSweAgentBashResult") throw new Error("unreachable"); + expect(answer.value.result.case).toBe("success"); + }); +}); + +describe("Cursor modern exec frames: server-resolved tool calls leave a paired block", () => { + // `buildSessionContext` strips a `toolCall` with no matching `toolResult`, + // taking the whole interaction out of every rebuilt transcript. Both of these + // variants are refused, but a refusal still has to settle its block. + + it("pairs the conversationSearch block the exec frame synthesizes", async () => { + const { output, results } = await dispatchExec( + buildExecMessage({ + case: "conversationSearchArgs", + value: create(ConversationSearchArgsSchema, { query: "chess", toolCallId: "call-search-1", limit: 5 }), + }), + ); + + const blocks = output.content.filter((block): block is ToolCallState => block.type === "toolCall"); + expect(blocks).toHaveLength(1); + expect(blocks[0]).toMatchObject({ id: "call-search-1", name: "search_conversations" }); + // Resolved => agent-loop emits no placeholder, so the pair must come from here. + expect(blocks[0][kCursorExecResolved]).toBe(true); + expect(results.map(result => result.toolCallId)).toEqual(["call-search-1"]); + expect(results[0].isError).toBe(true); + }); + + /** + * Drive a streamed `connect_scm` pair through the interaction decoder. + * + * The `ToolCall` is built with the generated schema and re-decoded from its + * own wire bytes, so the `target` / `result` oneofs carry the real + * `{ case, value }` shape the server sends — a hand-shaped literal would + * silently pass against a selector reading the wrong field. + */ + function runConnectScm( + args: { toolCallId?: string; repository?: { owner: string; repo: string } }, + result: { case: "success" | "error" | "rejected"; value: Record } | undefined, + options: { envelopeId?: string; completionCarriesCall?: boolean } = {}, + ): { output: AssistantMessage; results: ToolResultMessage[]; resultsAfterStart: number } { + const wire = create(ToolCallSchema, { + tool: { + case: "connectScmToolCall", + value: create(ConnectScmToolCallSchema, { + args: create(ConnectScmArgsSchema, { + toolCallId: args.toolCallId ?? "", + target: args.repository + ? { + case: "github", + value: create(ConnectScmGithubSchema, { + repository: create(ConnectScmGithubRepositorySchema, args.repository), + }), + } + : { case: undefined }, + }), + result: result + ? create(ConnectScmResultSchema, { + result: + result.case === "success" + ? { case: "success", value: create(ConnectScmSuccessSchema, {}) } + : result.case === "error" + ? { case: "error", value: create(ConnectScmErrorSchema, result.value) } + : { case: "rejected", value: create(ConnectScmRejectedSchema, result.value) }, + }) + : undefined, + }), + }, + }); + const toolCall = fromBinary(ToolCallSchema, toBinary(ToolCallSchema, wire)); + + const output = cursorAssistantMessage(); + const stream = new AssistantMessageEventStream(); + const state = newBlockState(); + const results: ToolResultMessage[] = []; + state.onToolResult = toolResultMessage => { + results.push(toolResultMessage); + return toolResultMessage; + }; + + const usage = { sawTokenDelta: false }; + processInteractionUpdate( + { message: { case: "toolCallStarted", value: { callId: options.envelopeId ?? "", toolCall } } }, + output, + stream, + state, + usage, + ); + const resultsAfterStart = results.length; + processInteractionUpdate( + { + message: { + case: "toolCallCompleted", + value: options.completionCarriesCall === false ? {} : { toolCall }, + }, + }, + output, + stream, + state, + usage, + ); + return { output, results, resultsAfterStart }; + } + + function soleBlock(output: AssistantMessage): ToolCallState { + const blocks = output.content.filter((block): block is ToolCallState => block.type === "toolCall"); + expect(blocks).toHaveLength(1); + return blocks[0]; + } + + it("reads the connectScm repository out of the target oneof, not a flat field", async () => { + const { output, results } = runConnectScm( + { toolCallId: "call-scm-1", repository: { owner: "can1357", repo: "oh-my-pi" } }, + { case: "success", value: {} }, + ); + + const block = soleBlock(output); + expect(block).toMatchObject({ + id: "call-scm-1", + name: "connect_scm", + arguments: { owner: "can1357", repo: "oh-my-pi" }, + }); + // Resolved => agent-loop runs no local tool, so the decoder owes the pair. + expect(block[kCursorExecResolved]).toBe(true); + expect(results.map(result => result.toolCallId)).toEqual(["call-scm-1"]); + }); + + it("settles connectScm from the completion's result, never from the announcement", async () => { + // The start frame carries no verdict. Answering there would persist a + // fabricated outcome before the server reported one. + const success = runConnectScm({ toolCallId: "c1" }, { case: "success", value: {} }); + const failed = runConnectScm({ toolCallId: "c2" }, { case: "error", value: { error: "token expired" } }); + const rejected = runConnectScm({ toolCallId: "c3" }, { case: "rejected", value: { reason: "user declined" } }); + + // The bug this guards: settling at start persisted a hard-coded failure + // for every call, including the ones the server went on to accept. + expect([success, failed, rejected].map(run => run.resultsAfterStart)).toEqual([0, 0, 0]); + expect(success.results.map(r => r.isError)).toEqual([false]); + expect(failed.results[0].isError).toBe(true); + expect(failed.results[0].content).toEqual([{ type: "text", text: "token expired" }]); + expect(rejected.results[0].isError).toBe(true); + expect(rejected.results[0].content).toEqual([{ type: "text", text: "user declined" }]); + }); + + it("still pairs connectScm when the completion carries no tool call at all", async () => { + // An unpaired resolved block is stripped along with its whole interaction, + // so a resultless completion must settle as a failure rather than stay open. + const { output, results } = runConnectScm({ toolCallId: "c4" }, undefined, { + completionCarriesCall: false, + }); + + expect(soleBlock(output).id).toBe("c4"); + expect(results.map(result => result.toolCallId)).toEqual(["c4"]); + expect(results[0].isError).toBe(true); + }); + + it("falls back to the envelope call id when connectScm args carry none", async () => { + const { output, results } = runConnectScm( + { repository: { owner: "o", repo: "r" } }, + { case: "success", value: {} }, + { + envelopeId: "envelope-id", + }, + ); + + expect(soleBlock(output).id).toBe("envelope-id"); + expect(results.map(result => result.toolCallId)).toEqual(["envelope-id"]); + }); + + it("ignores a completion whose envelope id belongs to a different call", async () => { + // Server-resolved and parallel calls interleave: a completion for an + // unrelated call would otherwise close whichever block is current and pair + // it with the wrong verdict — here, marking a pending connect as rejected. + // + // Correlation is on the envelope id, which is deliberately NOT the block + // id: the block persists under the inner `tool_call_id` the transcript + // files it under. + const wire = create(ToolCallSchema, { + tool: { + case: "connectScmToolCall", + value: create(ConnectScmToolCallSchema, { + args: create(ConnectScmArgsSchema, { toolCallId: "inner-id" }), + result: create(ConnectScmResultSchema, { + result: { case: "rejected", value: create(ConnectScmRejectedSchema, { reason: "other call" }) }, + }), + }), + }, + }); + const toolCall = fromBinary(ToolCallSchema, toBinary(ToolCallSchema, wire)); + + const output = cursorAssistantMessage(); + const stream = new AssistantMessageEventStream(); + const state = newBlockState(); + const results: ToolResultMessage[] = []; + state.onToolResult = result => { + results.push(result); + return result; + }; + const usage = { sawTokenDelta: false }; + + processInteractionUpdate( + { message: { case: "toolCallStarted", value: { callId: "envelope-a", toolCall } } }, + output, + stream, + state, + usage, + ); + processInteractionUpdate( + { message: { case: "toolCallCompleted", value: { callId: "envelope-b", toolCall } } }, + output, + stream, + state, + usage, + ); + + // The block stays open and unpaired; only its own completion settles it. + expect(results).toEqual([]); + + processInteractionUpdate( + { message: { case: "toolCallCompleted", value: { callId: "envelope-a", toolCall } } }, + output, + stream, + state, + usage, + ); + + expect(results.map(result => result.toolCallId)).toEqual(["inner-id"]); + expect(results[0].content).toEqual([{ type: "text", text: "other call" }]); + }); + + it("settles two interleaved calls independently, each with its own result", async () => { + // `start A, start B, complete A, complete B` is legal wire order. With a + // single "current block" slot, B's start orphaned A: A's completion then + // settled B, and A was never paired — so the transcript rebuild dropped + // the whole interaction. + function scmCall(toolCallId: string, reason: string) { + const wire = create(ToolCallSchema, { + tool: { + case: "connectScmToolCall", + value: create(ConnectScmToolCallSchema, { + args: create(ConnectScmArgsSchema, { toolCallId }), + result: create(ConnectScmResultSchema, { + result: { case: "rejected", value: create(ConnectScmRejectedSchema, { reason }) }, + }), + }), + }, + }); + return fromBinary(ToolCallSchema, toBinary(ToolCallSchema, wire)); + } + + const callA = scmCall("inner-a", "reason A"); + const callB = scmCall("inner-b", "reason B"); + + const output = cursorAssistantMessage(); + const stream = new AssistantMessageEventStream(); + const state = newBlockState(); + const results: ToolResultMessage[] = []; + state.onToolResult = result => { + results.push(result); + return result; + }; + const usage = { sawTokenDelta: false }; + const send = (updateCase: string, callId: string, toolCall: unknown) => + processInteractionUpdate( + { message: { case: updateCase, value: { callId, toolCall } } }, + output, + stream, + state, + usage, + ); + + send("toolCallStarted", "envelope-a", callA); + send("toolCallStarted", "envelope-b", callB); + send("toolCallCompleted", "envelope-a", callA); + send("toolCallCompleted", "envelope-b", callB); + + // Both blocks exist, and each is paired with its OWN verdict. + const blocks = output.content.filter((block): block is ToolCallState => block.type === "toolCall"); + expect(blocks.map(block => block.id)).toEqual(["inner-a", "inner-b"]); + expect(results.map(result => result.toolCallId)).toEqual(["inner-a", "inner-b"]); + expect(results.map(result => result.content)).toEqual([ + [{ type: "text", text: "reason A" }], + [{ type: "text", text: "reason B" }], + ]); + }); + + it("does not double-create a block for a Pi call the exec channel already synthesized", async () => { + // Modern builds stream a `pi_*_tool_call` envelope alongside the exec + // frame. The exec side owns that block; a second one here would render the + // same call twice and leave the duplicate unpaired. + const output = cursorAssistantMessage(); + const stream = new AssistantMessageEventStream(); + const state = newBlockState(); + + processInteractionUpdate( + { + message: { + case: "toolCallStarted", + value: { + callId: "call-pi-read", + toolCall: { tool: { case: "piReadToolCall", value: { args: { path: "/a.ts" } } } }, + }, + }, + }, + output, + stream, + state, + { sawTokenDelta: false }, + ); + + expect(output.content.filter(block => block.type === "toolCall")).toHaveLength(0); + }); +}); + +describe("Cursor legacy read frame: range reporting", () => { + it("reports rangeApplied only when the frame asked for a window", async () => { + // The field reports whether the frame's window was actually composed + // onto the read. Reporting true for an unranged whole-file read, or + // false for a paginated one, misdescribes what the client did. + const handlers: CursorExecHandlers = { + async read() { + return toolResult("line1\nline2"); + }, + }; + + const ranged = await dispatchExec( + buildExecMessage({ + case: "readArgs", + value: create(ReadArgsSchema, { path: "/repo/big.ts", toolCallId: "c1", offset: 5, limit: 20 }), + }), + { execHandlers: handlers }, + ); + const rangedAnswer = soleResult(ranged.frames); + if (rangedAnswer.case !== "readResult") throw new Error(`got ${rangedAnswer.case}`); + if (rangedAnswer.value.result.case !== "success") throw new Error(`got ${rangedAnswer.value.result.case}`); + expect(rangedAnswer.value.result.value.rangeApplied).toBe(true); + + const whole = await dispatchExec( + buildExecMessage({ + case: "readArgs", + value: create(ReadArgsSchema, { path: "/repo/big.ts", toolCallId: "c2" }), + }), + { execHandlers: handlers }, + ); + const wholeAnswer = soleResult(whole.frames); + if (wholeAnswer.case !== "readResult") throw new Error(`got ${wholeAnswer.case}`); + if (wholeAnswer.value.result.case !== "success") throw new Error(`got ${wholeAnswer.value.result.case}`); + expect(wholeAnswer.value.result.value.rangeApplied).toBe(false); + }); + + it("carries the composed selector into the synthesized call", async () => { + // A bare path beside a ranged result makes the slice look like the whole + // file in every rebuilt transcript. + const { output } = await dispatchExec( + buildExecMessage({ + case: "readArgs", + value: create(ReadArgsSchema, { path: "/repo/big.ts", toolCallId: "c1", offset: 5, limit: 20 }), + }), + { + execHandlers: { + async read() { + return toolResult("line1\nline2"); + }, + }, + }, + ); + const call = output.content.find(block => block.type === "toolCall"); + expect(call?.arguments).toMatchObject({ path: "/repo/big.ts:raw:5+20" }); + }); + it("records a zero-line read as zero lines, not the whole file", async () => { + // `limit: 0` composes no selector — nothing reads zero lines — and the + // frame is answered with empty output directly. A bare path in the block + // would replay as a whole-file read that came back empty. + const { output } = await dispatchExec( + buildExecMessage({ + case: "readArgs", + value: create(ReadArgsSchema, { path: "/repo/big.ts", toolCallId: "c1", offset: 5, limit: 0 }), + }), + { + execHandlers: { + async read() { + return toolResult(""); + }, + }, + }, + ); + const call = output.content.find(block => block.type === "toolCall"); + expect(call?.arguments).toMatchObject({ path: "/repo/big.ts:raw:5+0" }); + }); +}); + +describe("Cursor legacy grep frame: offset reporting", () => { + it("echoes the frame's offset back as offsetApplied", async () => { + // The field reports the offset this client actually applied, so it must + // track the frame's request rather than being left unset. + const handlers: CursorExecHandlers = { + async grep() { + return toolResult("a.ts:1:needle"); + }, + }; + + const paged = await dispatchExec( + buildExecMessage({ + case: "grepArgs", + value: create(GrepArgsSchema, { pattern: "needle", path: "src", toolCallId: "c1", offset: 20 }), + }), + { execHandlers: handlers }, + ); + const pagedAnswer = soleResult(paged.frames); + if (pagedAnswer.case !== "grepResult") throw new Error(`got ${pagedAnswer.case}`); + if (pagedAnswer.value.result.case !== "success") throw new Error(`got ${pagedAnswer.value.result.case}`); + const pagedUnion = pagedAnswer.value.result.value.workspaceResults.src?.result; + if (pagedUnion?.case !== "content") throw new Error(`got ${pagedUnion?.case}`); + expect(pagedUnion.value.offsetApplied).toBe(20); + + const first = await dispatchExec( + buildExecMessage({ + case: "grepArgs", + value: create(GrepArgsSchema, { pattern: "needle", path: "src", toolCallId: "c2" }), + }), + { execHandlers: handlers }, + ); + const firstAnswer = soleResult(first.frames); + if (firstAnswer.case !== "grepResult") throw new Error(`got ${firstAnswer.case}`); + if (firstAnswer.value.result.case !== "success") throw new Error(`got ${firstAnswer.value.result.case}`); + const firstUnion = firstAnswer.value.result.value.workspaceResults.src?.result; + if (firstUnion?.case !== "content") throw new Error(`got ${firstUnion?.case}`); + // Absent, not 0: no offset was requested, so none was applied. + expect(firstUnion.value.offsetApplied).toBeUndefined(); + }); + + it("carries the executed page into the synthesized call", async () => { + // The block is what a reloaded transcript replays. Showing an unskipped + // search beside a result taken from a later file window tells the next + // turn the wrong thing about what was searched. + const { output } = await dispatchExec( + buildExecMessage({ + case: "grepArgs", + value: create(GrepArgsSchema, { pattern: "needle", path: "src", toolCallId: "c1", offset: 20 }), + }), + { + execHandlers: { + async grep() { + return toolResult("a.ts:1:needle"); + }, + }, + }, + ); + const call = output.content.find(block => block.type === "toolCall"); + expect(call?.arguments).toMatchObject({ pattern: "needle", path: "src", skip: 20 }); + }); +}); + +describe("Cursor MCP frame: approval-only probes", () => { + it("answers from the host's policy without running the tool", async () => { + // The server sends this to resolve a permission decision BEFORE the real + // call. Running the tool to answer it fires a side effect the user has + // not been asked about, and fires it again when the real frame arrives. + // The verdict comes from the host's policy instead. + let invocations = 0; + const handlers: CursorExecHandlers = { + async mcp() { + invocations += 1; + return toolResult("ran"); + }, + }; + + const { frames, output, results } = await dispatchExec( + buildExecMessage({ + case: "mcpArgs", + value: create(McpArgsSchema, { + name: "deploy", + toolName: "deploy", + toolCallId: "c1", + providerIdentifier: "ops", + smartModeApprovalOnly: true, + }), + }), + { execHandlers: handlers }, + ); + + expect(invocations).toBe(0); + const answer = soleResult(frames); + if (answer.case !== "mcpResult") throw new Error(`got ${answer.case}`); + // No preflight handler is registered here: with nothing to decide + // against, the probe cannot be approved. + expect(answer.value.result.case).toBe("rejected"); + // Nothing ran, so nothing may appear in the transcript either. + expect(output.content.filter(block => block.type === "toolCall")).toHaveLength(0); + expect(results).toHaveLength(0); + }); + + it("approves a probe the host's policy allows", async () => { + // A definite allow must still be approved, or a permitted call loses the + // fast path on every turn. + let invocations = 0; + const { frames, output } = await dispatchExec( + buildExecMessage({ + case: "mcpArgs", + value: create(McpArgsSchema, { + name: "lookup", + toolName: "lookup", + toolCallId: "c1", + providerIdentifier: "ops", + smartModeApprovalOnly: true, + }), + }), + { + execHandlers: { + async mcp() { + invocations += 1; + return toolResult("ran"); + }, + async mcpApprovalPreflight() { + return true; + }, + }, + }, + ); + + // Approved, but still not executed: the call itself comes later. + expect(invocations).toBe(0); + const answer = soleResult(frames); + if (answer.case !== "mcpResult") throw new Error(`got ${answer.case}`); + expect(answer.value.result.case).toBe("approved"); + expect(output.content.filter(block => block.type === "toolCall")).toHaveLength(0); + }); + + it("refuses a probe the host declines to approve", async () => { + // A pending prompt resolves to false: it can only be answered at + // execution time, and approving on the user's behalf pre-authorizes a + // call they never saw. + const { frames } = await dispatchExec( + buildExecMessage({ + case: "mcpArgs", + value: create(McpArgsSchema, { + name: "deploy", + toolName: "deploy", + toolCallId: "c1", + providerIdentifier: "ops", + smartModeApprovalOnly: true, + }), + }), + { + execHandlers: { + async mcp() { + return toolResult("ran"); + }, + async mcpApprovalPreflight() { + return false; + }, + }, + }, + ); + const answer = soleResult(frames); + if (answer.case !== "mcpResult") throw new Error(`got ${answer.case}`); + expect(answer.value.result.case).toBe("rejected"); + }); + + it("still runs an ordinary MCP frame", async () => { + // The guard keys on the flag alone: a normal call must be unaffected. + let invocations = 0; + const handlers: CursorExecHandlers = { + async mcp() { + invocations += 1; + return toolResult("ran"); + }, + }; + + const { frames } = await dispatchExec( + buildExecMessage({ + case: "mcpArgs", + value: create(McpArgsSchema, { + name: "deploy", + toolName: "deploy", + toolCallId: "c2", + providerIdentifier: "ops", + }), + }), + { execHandlers: handlers }, + ); + + expect(invocations).toBe(1); + const answer = soleResult(frames); + if (answer.case !== "mcpResult") throw new Error(`got ${answer.case}`); + expect(answer.value.result.case).toBe("success"); + }); +}); + +describe("Cursor exec answers: what the result claims about the work", () => { + it("reports the file's own line count for a windowed read", async () => { + // `total_lines` derived from the payload is the file's length only when + // the payload is the file. Under a composed window it is the window's, so + // a 20-line page of a 100-line file answered `total_lines: 20` — which a + // paginating server reads as "you have the whole thing". + const { frames } = await dispatchExec( + buildExecMessage({ + case: "readArgs", + value: create(ReadArgsSchema, { path: "/repo/big.ts", toolCallId: "c1", offset: 5, limit: 20 }), + }), + { + execHandlers: { + async read() { + return toolResult("line5\nline6\nline7", { + // The shape `read` records a composed range in: the flat + // `truncation.totalLines` counts from the window's start, + // `meta.truncation.totalLines` counts the file. + details: { + truncation: { truncated: true, totalLines: 97 }, + meta: { truncation: { totalLines: 101 } }, + fileSize: 4096, + }, + }); + }, + }, + }, + ); + const answer = soleResult(frames); + if (answer.case !== "readResult") throw new Error(`got ${answer.case}`); + if (answer.value.result.case !== "success") throw new Error(`got ${answer.value.result.case}`); + expect(answer.value.result.value.totalLines).toBe(101); + expect(answer.value.result.value.rangeApplied).toBe(true); + expect(answer.value.result.value.fileSize).toBe(4096n); + }); + + it("counts the payload when the read returned the file whole", async () => { + // No window, no recorded total: the payload IS the file, so counting it + // is exact and the fallback must stay. + const { frames } = await dispatchExec( + buildExecMessage({ + case: "readArgs", + value: create(ReadArgsSchema, { path: "/repo/small.ts", toolCallId: "c1" }), + }), + { + execHandlers: { + async read() { + return toolResult("a\nb\nc"); + }, + }, + }, + ); + const answer = soleResult(frames); + if (answer.case !== "readResult") throw new Error(`got ${answer.case}`); + if (answer.value.result.case !== "success") throw new Error(`got ${answer.value.result.case}`); + expect(answer.value.result.value.totalLines).toBe(3); + }); + + it("signals the native grep backend's internal cap", async () => { + // `GrepTool` folds that ceiling into the flat `details.truncated` alone — + // no `details.truncation`, no `perFileLimitReached`. Forwarding only the + // latter two answered a clipped search as an unqualified success, which + // is the one truncation a caller can neither detect nor page around. + const { frames } = await dispatchExec( + buildExecMessage({ case: "piGrepArgs", value: create(PiGrepExecArgsSchema, { pattern: "hit" }) }), + { + execHandlers: { + async piGrep() { + return toolResult("a.ts:1:hit", { details: { truncated: true } }); + }, + }, + }, + ); + const answer = soleResult(frames); + if (answer.case !== "piGrepResult") throw new Error(`got ${answer.case}`); + if (answer.value.result.case !== "success") throw new Error(`got ${answer.value.result.case}`); + expect(answer.value.result.value.truncation?.truncated).toBe(true); + expect(answer.value.result.value.truncation?.truncatedBy).toBe("matches"); + }); + + it("leaves an untruncated grep unqualified", async () => { + // The flat flag is the only signal consulted, so a search that hit no cap + // must not acquire one. + const { frames } = await dispatchExec( + buildExecMessage({ case: "piGrepArgs", value: create(PiGrepExecArgsSchema, { pattern: "hit" }) }), + { + execHandlers: { + async piGrep() { + return toolResult("a.ts:1:hit", { details: { truncated: false } }); + }, + }, + }, + ); + const answer = soleResult(frames); + if (answer.case !== "piGrepResult") throw new Error(`got ${answer.case}`); + if (answer.value.result.case !== "success") throw new Error(`got ${answer.value.result.case}`); + expect(answer.value.result.value.truncation).toBeUndefined(); + }); + + it("does not restate a cap that already reported itself", async () => { + // `perFileLimitReached` carries the count; adding a second, countless + // truncation record for the same event tells the server two caps fired. + const { frames } = await dispatchExec( + buildExecMessage({ case: "piGrepArgs", value: create(PiGrepExecArgsSchema, { pattern: "hit" }) }), + { + execHandlers: { + async piGrep() { + return toolResult("a.ts:1:hit", { details: { truncated: true, perFileLimitReached: 20 } }); + }, + }, + }, + ); + const answer = soleResult(frames); + if (answer.case !== "piGrepResult") throw new Error(`got ${answer.case}`); + if (answer.value.result.case !== "success") throw new Error(`got ${answer.value.result.case}`); + expect(answer.value.result.value.matchLimitReached).toBe(20); + expect(answer.value.result.value.truncation).toBeUndefined(); + }); + + it("records the scoped grep's context and cap in the synthesized call", async () => { + // Neither field is expressible in the `grep` schema, so the bridge serves + // them by building a scoped tool. A block that omits them replays a + // context-widened, capped search as an ordinary grep. + const { output } = await dispatchExec( + buildExecMessage({ + case: "piGrepArgs", + value: create(PiGrepExecArgsSchema, { pattern: "hit", context: 3, limit: 50 }), + }), + { + execHandlers: { + async piGrep() { + return toolResult("a.ts:1:hit"); + }, + }, + }, + ); + const blocks = output.content.filter((block): block is ToolCallState => block.type === "toolCall"); + expect(blocks).toHaveLength(1); + expect(blocks[0].arguments).toMatchObject({ pattern: "hit", context: 3, limit: 50 }); + }); + + it("names the MCP resources it listed in the paired result", async () => { + // Rebuilt history is serialized from the paired result, so recording only + // a count leaves the model aware it once saw N resources and unable to + // name the URIs a follow-up read needs. + const { results } = await dispatchExec( + buildExecMessage({ + case: "listMcpResourcesExecArgs", + value: create(ListMcpResourcesExecArgsSchema, { server: "docs" }), + }), + { + execHandlers: { + listMcpResources: async () => [ + { uri: "docs://readme", name: "README", mimeType: "text/markdown", server: "docs" }, + { uri: "docs://changelog", server: "docs" }, + ], + }, + }, + ); + expect(results).toHaveLength(1); + const text = results[0].content.map(part => (part.type === "text" ? part.text : "")).join(""); + expect(text).toContain("docs://readme"); + expect(text).toContain("README"); + expect(text).toContain("text/markdown"); + // A resource the server described with nothing but a URI still gets named. + expect(text).toContain("docs://changelog"); + }); + + it("says so plainly when a server advertises nothing", async () => { + const { results } = await dispatchExec( + buildExecMessage({ + case: "listMcpResourcesExecArgs", + value: create(ListMcpResourcesExecArgsSchema, { server: "docs" }), + }), + { execHandlers: { listMcpResources: async () => [] } }, + ); + expect(results).toHaveLength(1); + const text = results[0].content.map(part => (part.type === "text" ? part.text : "")).join(""); + expect(text).toBe("No MCP resources available"); + expect(results[0].isError).toBe(false); + }); +}); diff --git a/packages/ai/test/cursor-mcp-tool-catalog.test.ts b/packages/ai/test/cursor-mcp-tool-catalog.test.ts index 1c8dda9a2..286883ec3 100644 --- a/packages/ai/test/cursor-mcp-tool-catalog.test.ts +++ b/packages/ai/test/cursor-mcp-tool-catalog.test.ts @@ -44,4 +44,16 @@ describe("cursor buildMcpToolDefinitions", () => { const names = buildMcpToolDefinitions([tool("read"), tool("write"), tool("bash")]).map(def => def.name); expect(names).toEqual([]); }); + + it("advertises lsp, which Cursor has no native equivalent for", () => { + // `lsp` is deliberately absent from the native-filtered set: Cursor's own + // tools cover none of definition/references/rename, so filtering it out + // leaves the model with no way to reach them at all. + const defs = buildMcpToolDefinitions([tool("read"), tool("bash"), tool("lsp")]); + + const lspDef = defs.find(def => def.name === "lsp"); + expect(lspDef).toBeDefined(); + expect(lspDef?.providerIdentifier).toBe("pi-agent"); + expect(lspDef?.toolName).toBe("lsp"); + }); }); diff --git a/packages/ai/test/cursor-streaming-args.test.ts b/packages/ai/test/cursor-streaming-args.test.ts index 9f74c743a..4101a7457 100644 --- a/packages/ai/test/cursor-streaming-args.test.ts +++ b/packages/ai/test/cursor-streaming-args.test.ts @@ -58,6 +58,7 @@ function newHarness(): Harness { get currentToolCall() { return toolCall; }, + openToolCalls: new Map(), resolvedMcpToolCallIds: new Set(), firstTokenTime: undefined, setTextBlock: b => { diff --git a/packages/ai/test/cursor-terminal-error.test.ts b/packages/ai/test/cursor-terminal-error.test.ts index fc986e809..c46694bbb 100644 --- a/packages/ai/test/cursor-terminal-error.test.ts +++ b/packages/ai/test/cursor-terminal-error.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect, it } from "bun:test"; import * as http2 from "node:http2"; import { create, toBinary } from "@bufbuild/protobuf"; import { streamCursor } from "@oh-my-pi/pi-ai/providers/cursor"; -import type { Context, Model } from "@oh-my-pi/pi-ai/types"; +import type { Context, CursorToolResultHandler, Model, ToolResultMessage } from "@oh-my-pi/pi-ai/types"; import { buildModel } from "@oh-my-pi/pi-catalog/build"; import { AgentServerMessageSchema, @@ -10,7 +10,11 @@ import { InteractionUpdateSchema, ReadArgsSchema, TextDeltaUpdateSchema, + ToolCallSchema, + ToolCallStartedUpdateSchema, TurnEndedUpdateSchema, + UpdateTodosArgsSchema, + UpdateTodosToolCallSchema, } from "@oh-my-pi/pi-catalog/discovery/cursor-gen/agent_pb"; const CONNECT_END_STREAM_FLAG = 0b00000010; @@ -23,7 +27,8 @@ type Scenario = | { kind: "hang-after-turn" } | { kind: "exec-in-final-chunk"; responseFinished: PromiseWithResolvers } | { kind: "exec-then-transport-error"; responseFinished: PromiseWithResolvers } - | { kind: "exec-then-hang" }; + | { kind: "exec-then-hang" } + | { kind: "todo-start-then-death" }; let server: http2.Http2Server | undefined; const sessions = new Set(); @@ -104,6 +109,36 @@ function execAndTurnEndedFrame(): Buffer { return Buffer.concat([execRequestFrame(), turnEndedFrame()]); } +/** + * A native `update_todos` call announcement. Cursor runs these server-side, so + * the block is stamped resolved at start and only its `toolCallCompleted` + * frame pairs a result — nothing downstream synthesizes one. + */ +function todoStartFrame(): Buffer { + const message = create(AgentServerMessageSchema, { + message: { + case: "interactionUpdate", + value: create(InteractionUpdateSchema, { + message: { + case: "toolCallStarted", + value: create(ToolCallStartedUpdateSchema, { + callId: "todo-envelope", + toolCall: create(ToolCallSchema, { + tool: { + case: "updateTodosToolCall", + value: create(UpdateTodosToolCallSchema, { + args: create(UpdateTodosArgsSchema, { todos: [] }), + }), + }, + }), + }), + }, + }), + }, + }); + return frameConnectMessage(toBinary(AgentServerMessageSchema, message)); +} + async function startServer(): Promise { server = http2.createServer(); server.on("session", session => { @@ -150,6 +185,16 @@ async function startServer(): Promise { return; } + if (scenario.kind === "todo-start-then-death") { + // The server announces a native todo call, then the stream dies + // without `turnEnded` and without the call's completion frame. This + // is the real interrupted-call shape: `settleH2` rejects, so the + // success-path flush never runs. + stream.write(todoStartFrame()); + stream.end(); + return; + } + if (scenario.kind === "exec-in-final-chunk") { const { responseFinished } = scenario; // Resolves once the server has flushed the whole response, so the test @@ -226,8 +271,15 @@ const context: Context = { messages: [{ role: "user", content: "terminal lifecycle", timestamp: 1 }], }; -async function collectStream(model: Model<"cursor-agent">, options?: { signal?: AbortSignal }) { - const stream = streamCursor(model, context, { apiKey: "test-token", signal: options?.signal }); +async function collectStream( + model: Model<"cursor-agent">, + options?: { signal?: AbortSignal; onToolResult?: CursorToolResultHandler }, +) { + const stream = streamCursor(model, context, { + apiKey: "test-token", + signal: options?.signal, + onToolResult: options?.onToolResult, + }); const eventTypes: string[] = []; for await (const event of stream) { eventTypes.push(event.type); @@ -303,6 +355,37 @@ describe("Cursor terminal lifecycle after turnEnded", () => { expect(result.errorMessage).toContain("Cursor stream ended before turnEnded"); }); + it("pairs and closes a server-owned call the dying stream left open", async () => { + // The failure this guards: a native todo block is stamped resolved at + // start, so `agent-loop.ts` synthesizes no placeholder for it and only + // its completion frame pairs a result. When the transport dies first the + // call went unpaired and its card stayed animating — and + // `buildSessionContext` strips a dangling call, so the interaction + // vanished from every rebuilt transcript. + // + // This must run against the real terminal-error path: `settleH2` rejects + // on a stream that ends before `turnEnded`, so the success path's flush + // is never reached. + scenario = { kind: "todo-start-then-death" }; + const baseUrl = await startServer(); + const paired: ToolResultMessage[] = []; + const { eventTypes, result } = await collectStream(makeModel(baseUrl), { + onToolResult: toolResult => void paired.push(toolResult), + }); + + expect(eventTypes.at(-1)).toBe("error"); + expect(result.stopReason).toBe("error"); + + const call = result.content.find(block => block.type === "toolCall"); + if (!call) throw new Error("expected the announced todo call in the output"); + // Closed, so no live card is left animating. + expect(eventTypes).toContain("toolcall_end"); + // Paired, so replay keeps the interaction. + expect(paired).toHaveLength(1); + expect(paired[0].toolCallId).toBe(call.id); + expect(paired[0].isError).toBe(true); + }); + it("aborts without emitting done when the signal fires", async () => { scenario = { kind: "hang-after-turn" }; const baseUrl = await startServer(); diff --git a/packages/ai/test/cursor-todo-bridge.test.ts b/packages/ai/test/cursor-todo-bridge.test.ts index 1f184e9d0..bff4d27cf 100644 --- a/packages/ai/test/cursor-todo-bridge.test.ts +++ b/packages/ai/test/cursor-todo-bridge.test.ts @@ -89,6 +89,7 @@ function newHarness(): Harness { get currentToolCall() { return toolCall; }, + openToolCalls: new Map(), resolvedMcpToolCallIds: new Set(), firstTokenTime: undefined, setTextBlock: b => { diff --git a/packages/ai/test/exa-login.test.ts b/packages/ai/test/exa-login.test.ts new file mode 100644 index 000000000..39770d744 --- /dev/null +++ b/packages/ai/test/exa-login.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from "bun:test"; +import { loginExa } from "@oh-my-pi/pi-ai/registry/exa"; + +describe("exa login", () => { + it("opens Exa API-key settings and returns a trimmed key without validation requests", async () => { + let authUrl: string | undefined; + let authInstructions: string | undefined; + let promptMessage: string | undefined; + let promptPlaceholder: string | undefined; + + const apiKey = await loginExa({ + onAuth: info => { + authUrl = info.url; + authInstructions = info.instructions; + }, + onPrompt: async prompt => { + promptMessage = prompt.message; + promptPlaceholder = prompt.placeholder; + return " exa-test-key "; + }, + fetch: () => { + throw new Error("Exa login must not make a network request"); + }, + }); + + expect(authUrl).toBe("https://dashboard.exa.ai/api-keys"); + expect(authInstructions).toBe("Create or copy your API key from the Exa dashboard."); + expect(promptMessage).toBe("Paste your Exa API key"); + expect(promptPlaceholder).toBe("API key"); + expect(apiKey).toBe("exa-test-key"); + }); +}); diff --git a/packages/ai/test/google-tool-schema.test.ts b/packages/ai/test/google-tool-schema.test.ts index efc086080..2370aab9d 100644 --- a/packages/ai/test/google-tool-schema.test.ts +++ b/packages/ai/test/google-tool-schema.test.ts @@ -585,14 +585,26 @@ describe("normalizeSchemaForGoogle parity with python-genai process_schema", () expect(sanitized).toEqual({ type: "string", enum: ["FOO"] }); }); - // Mirrors python-genai test_schema.py::test_process_schema_forbids_non_string_const - // We deviate intentionally: rather than raise on non-string const we accept - // the value as a singleton enum. Google's Schema proto accepts numeric enums - // and we prefer permissive normalization over surfacing a transformer-level error. - it("accepts non-string const as a singleton enum (intentional deviation from upstream raise)", () => { - const sanitized = normalizeSchemaForGoogle({ type: "integer", const: 123 }) as Record; - expect(sanitized.enum).toEqual([123]); - expect(sanitized.type).toBe("integer"); + // Mirrors python-genai test_schema.py::test_process_schema_forbids_non_string_const. + // Google enum fields accept strings only, so normalization drops the numeric + // singleton enum while preserving the integer type constraint. + it("omits a non-string const enum while preserving its type", () => { + const sanitized = normalizeSchemaForGoogle({ type: "integer", const: 123 }); + expect(sanitized).toEqual({ type: "integer" }); + }); + + it("drops negations whose non-string enums cannot be represented", () => { + const sanitized = normalizeSchemaForGoogle({ not: { enum: [1] } }); + expect(sanitized).toEqual({}); + expect( + normalizeSchemaForGoogle({ + not: { type: "object", properties: { value: { enum: [1] } } }, + }), + ).toEqual({}); + }); + + it("drops negations containing snake-case combiners with non-string enums", () => { + expect(normalizeSchemaForGoogle({ not: { any_of: [{ const: 1 }] } })).toEqual({}); }); // Mirrors python-genai test_schema.py::test_process_schema_order_properties_propagates_into_defs diff --git a/packages/ai/test/provider-registry.test.ts b/packages/ai/test/provider-registry.test.ts index a23b3010f..79a8efb58 100644 --- a/packages/ai/test/provider-registry.test.ts +++ b/packages/ai/test/provider-registry.test.ts @@ -47,7 +47,7 @@ describe("provider registry auth surface", () => { expect(getEnvApiKey("umans")).toBe("umans-env"); Bun.env.LLAMA_CPP_API_KEY = "llama-env"; expect(getEnvApiKey("llama.cpp")).toBe("llama-env"); - // Legacy search-tool key preserved (not a registry provider def). + // Exa is derived from the provider registry's `envKeys` definition. expect(getEnvApiKey("exa")).toBe("exa-env"); }); @@ -65,6 +65,7 @@ describe("provider registry auth surface", () => { const ids = getOAuthProviders().map(provider => provider.id); expect(ids).toContain("zenmux"); expect(ids).toContain("kagi"); + expect(ids).toContain("exa"); expect(ids).toContain("umans"); expect(ids).toContain("llama.cpp"); // openai has no interactive login flow. diff --git a/packages/ai/test/schema-normalization.test.ts b/packages/ai/test/schema-normalization.test.ts index f9fa3ddc1..d079ebf71 100644 --- a/packages/ai/test/schema-normalization.test.ts +++ b/packages/ai/test/schema-normalization.test.ts @@ -203,7 +203,58 @@ describe("upgradeJsonSchemaTo202012", () => { // --------------------------------------------------------------------------- describe("normalizeSchemaForGoogle", () => { - it("sets object type when converting an object const to an enum entry", () => { + it("preserves string enums and removes enums Google cannot represent", () => { + const sanitized = normalizeSchemaForGoogle({ + type: "object", + properties: { + valid: { type: "string", enum: ["draft", "published"] }, + numeric: { type: "number", enum: [1, 2] }, + mixed: { enum: ["draft", 1] }, + }, + }) as { properties: Record }; + + expect(sanitized.properties).toEqual({ + valid: { type: "string", enum: ["draft", "published"] }, + numeric: { type: "number" }, + mixed: {}, + }); + }); + + it("preserves enum keys inside object-valued defaults", () => { + expect( + normalizeSchemaForGoogle({ + type: "object", + default: { enum: [1], value: 2 }, + }), + ).toEqual({ + type: "object", + default: { enum: [1], value: 2 }, + properties: {}, + }); + }); + + it("keeps CCA incompatibility passes out of literal defaults", () => { + const literal = { + nullable: true, + allOf: [{ type: "object" }], + oneOf: [{ type: "string" }, { type: "number" }], + }; + expect( + normalizeSchemaForCCA({ + type: "object", + properties: { + value: { oneOf: [{ type: "string" }, { type: "string" }] }, + }, + default: literal, + }), + ).toEqual({ + type: "object", + properties: { value: { type: "string" } }, + default: literal, + }); + }); + + it("sets object type while removing an object-valued enum converted from const", () => { const sanitized = normalizeSchemaForGoogle({ const: { a: 1 }, }); @@ -211,11 +262,10 @@ describe("normalizeSchemaForGoogle", () => { expect(sanitized).toEqual({ type: "object", properties: {}, - enum: [{ a: 1 }], }); }); - it("deduplicates a deep-equal object const against an existing enum entry", () => { + it("removes an object-valued enum after deduplicating a deep-equal const", () => { const sanitized = normalizeSchemaForGoogle({ type: "object", enum: [{ a: 1 }], @@ -225,11 +275,10 @@ describe("normalizeSchemaForGoogle", () => { expect(sanitized).toEqual({ type: "object", properties: {}, - enum: [{ a: 1 }], }); }); - it("does not stamp a wrong scalar type when const variants span multiple primitive types", () => { + it("removes an enum when const variants span multiple primitive types", () => { const sanitized = normalizeSchemaForGoogle({ anyOf: [ { const: "A", type: "string" }, @@ -238,18 +287,18 @@ describe("normalizeSchemaForGoogle", () => { ], }) as Record; - expect(sanitized.enum).toEqual(["A", 1, true]); + expect(sanitized.enum).toBeUndefined(); expect(sanitized.type).toBeUndefined(); }); - it("collapses inferred null type to nullable when const is null", () => { + it("collapses inferred null type to nullable while removing its enum", () => { // After python-genai parity (handle_null_fields), bare `type: 'null'` is // folded into `nullable: true` so the schema is OpenAPI-compatible. const sanitized = normalizeSchemaForGoogle({ const: null }) as Record; expect(sanitized.type).toBeUndefined(); expect(sanitized.nullable).toBe(true); - expect(sanitized.enum).toEqual([null]); + expect(sanitized.enum).toBeUndefined(); }); it("coerces a boolean subschema literally named additionalProperties inside properties", () => { @@ -1277,6 +1326,21 @@ describe("normalizeSchemaForMoonshot", () => { expect(props.limit).toEqual({ type: "integer", default: 10 }); }); + it("normalizes schema-valued additionalProperties without walking literal payload objects", () => { + const literal = { oneOf: [{ const: "literal-a" }, { const: "literal-b" }] }; + const normalized = normalizeSchemaForMoonshot({ + type: "object", + additionalProperties: { oneOf: [{ const: 1 }, { const: 2 }] }, + default: literal, + }); + + expect(normalized).toEqual({ + type: "object", + additionalProperties: { type: "number", enum: [1, 2] }, + default: literal, + }); + }); + it("coerces boolean subschemas to MFJS object forms without changing boolean keywords", () => { expect( normalizeSchemaForMoonshot({ diff --git a/packages/ai/test/umans-usage.test.ts b/packages/ai/test/umans-usage.test.ts new file mode 100644 index 000000000..e75b1738d --- /dev/null +++ b/packages/ai/test/umans-usage.test.ts @@ -0,0 +1,225 @@ +import { describe, expect, it } from "bun:test"; +import type { FetchImpl } from "@oh-my-pi/pi-ai/types"; +import { umansUsageProvider } from "../src/usage/umans"; + +const DEFAULT_BASE_URL = "https://api.code.umans.ai"; + +function umansPayload(overrides: Record = {}): Record { + return { + plan: { display_name: "Code Max" }, + limits: { + requests: { limit: 200, hard_cap: 400, burst_pct: 1.0, window_seconds: 18000 }, + concurrency: { limit: 4, hard_cap: 8, burst_pct: 1.0 }, + }, + usage: { + requests_in_window: 48, + remaining_requests: 152, + concurrent_sessions: 1, + tokens_in: 1_200_000, + tokens_out: 340_000, + priority: { low: false, boxed_until: null, reason: null }, + }, + ...overrides, + }; +} + +function fakeFetch(payload: unknown, status = 200): FetchImpl { + const fn = async () => + new Response(JSON.stringify(payload), { + status, + headers: { "content-type": "application/json" }, + }); + return fn as unknown as typeof fetch; +} + +function fetchRecorder( + calls: Array<{ url: string; headers: Record }>, + payload: unknown, + status = 200, +): FetchImpl { + const fn = async (input: string | URL | Request, init?: RequestInit) => { + calls.push({ + url: String(input), + headers: (init?.headers as Record) ?? {}, + }); + return new Response(JSON.stringify(payload), { + status, + headers: { "content-type": "application/json" }, + }); + }; + return fn as unknown as typeof fetch; +} + +describe("umans usage provider", () => { + it("parses the rolling 5h request window into a UsageLimit with used/remaining/fraction", async () => { + const report = await umansUsageProvider.fetchUsage( + { + provider: "umans", + credential: { type: "api_key", apiKey: "sk-test", accountId: "acct-1", email: "u@example.com" }, + }, + { fetch: fakeFetch(umansPayload()) }, + ); + expect(report).not.toBeNull(); + const requests = report?.limits.find(l => l.id === "umans:requests"); + expect(requests).toBeDefined(); + expect(requests?.amount.used).toBe(48); + expect(requests?.amount.limit).toBe(200); + expect(requests?.amount.remaining).toBe(152); + expect(requests?.amount.usedFraction).toBeCloseTo(0.24, 5); + expect(requests?.amount.remainingFraction).toBeCloseTo(0.76, 5); + expect(requests?.amount.unit).toBe("requests"); + // Rolling window: no fabricated reset timestamp. + expect(requests?.window?.resetsAt).toBeUndefined(); + expect(requests?.window?.durationMs).toBe(18000_000); + expect(requests?.window?.label).toBe("rolling 5h"); + }); + + it("emits a concurrency limit from limits.concurrency", async () => { + const report = await umansUsageProvider.fetchUsage( + { + provider: "umans", + credential: { type: "api_key", apiKey: "sk-test" }, + }, + { fetch: fakeFetch(umansPayload()) }, + ); + const concurrency = report?.limits.find(l => l.id === "umans:concurrency"); + expect(concurrency).toBeDefined(); + expect(concurrency?.amount.used).toBe(1); + expect(concurrency?.amount.limit).toBe(4); + expect(concurrency?.amount.unit).toBe("requests"); + }); + + it("sends Authorization: Bearer to the default base URL", async () => { + const calls: Array<{ url: string; headers: Record }> = []; + await umansUsageProvider.fetchUsage( + { + provider: "umans", + credential: { type: "api_key", apiKey: "sk-test" }, + }, + { fetch: fetchRecorder(calls, umansPayload()) }, + ); + expect(calls).toHaveLength(1); + expect(calls[0]?.url).toBe(`${DEFAULT_BASE_URL}/v1/usage`); + expect(calls[0]?.headers.authorization).toBe("Bearer sk-test"); + }); + + it("honors a custom baseUrl from params", async () => { + const calls: Array<{ url: string; headers: Record }> = []; + await umansUsageProvider.fetchUsage( + { + provider: "umans", + credential: { type: "api_key", apiKey: "sk-test" }, + baseUrl: "https://custom.umans.example", + }, + { fetch: fetchRecorder(calls, umansPayload()) }, + ); + expect(calls[0]?.url).toBe("https://custom.umans.example/v1/usage"); + }); + + it("strips a trailing /v1 from a custom baseUrl", async () => { + const calls: Array<{ url: string; headers: Record }> = []; + await umansUsageProvider.fetchUsage( + { + provider: "umans", + credential: { type: "api_key", apiKey: "sk-test" }, + baseUrl: "https://api.code.umans.ai/v1", + }, + { fetch: fetchRecorder(calls, umansPayload()) }, + ); + expect(calls[0]?.url).toBe("https://api.code.umans.ai/v1/usage"); + }); + + it("preserves a path-mounted gateway prefix while stripping /v1", async () => { + const calls: Array<{ url: string; headers: Record }> = []; + await umansUsageProvider.fetchUsage( + { + provider: "umans", + credential: { type: "api_key", apiKey: "sk-test" }, + baseUrl: "https://gateway.example/team/umans/v1", + }, + { fetch: fetchRecorder(calls, umansPayload()) }, + ); + expect(calls[0]?.url).toBe("https://gateway.example/team/umans/v1/usage"); + }); + + it("surfaces priority.low as a provider note", async () => { + const payload = umansPayload({ + usage: { + requests_in_window: 250, + remaining_requests: 0, + concurrent_sessions: 1, + tokens_in: 0, + tokens_out: 0, + priority: { low: true, boxed_until: "2026-06-27T12:00:00Z", reason: "burst" }, + }, + }); + const report = await umansUsageProvider.fetchUsage( + { + provider: "umans", + credential: { type: "api_key", apiKey: "sk-test" }, + }, + { fetch: fakeFetch(payload) }, + ); + expect(report?.notes).toContain("Requests deprioritized after a rate-limit burst."); + }); + + it("throws on a 401 auth failure so checkCredentials flags the bad key", async () => { + await expect( + umansUsageProvider.fetchUsage( + { + provider: "umans", + credential: { type: "api_key", apiKey: "sk-test" }, + }, + { fetch: fakeFetch({ message: "unauthorized" }, 401) }, + ), + ).rejects.toThrow(/401/); + }); + + it("throws on a 403 auth failure so checkCredentials flags the bad key", async () => { + await expect( + umansUsageProvider.fetchUsage( + { + provider: "umans", + credential: { type: "api_key", apiKey: "sk-test" }, + }, + { fetch: fakeFetch({ message: "forbidden" }, 403) }, + ), + ).rejects.toThrow(/403/); + }); + + it("returns null on a transient non-auth HTTP failure (500)", async () => { + const report = await umansUsageProvider.fetchUsage( + { + provider: "umans", + credential: { type: "api_key", apiKey: "sk-test" }, + }, + { fetch: fakeFetch({ message: "internal server error" }, 500) }, + ); + expect(report).toBeNull(); + }); + + it("returns null when supports() is called for a different provider or credential type", () => { + expect(umansUsageProvider.supports?.({ provider: "zai", credential: { type: "api_key", apiKey: "x" } })).toBe( + false, + ); + expect( + umansUsageProvider.supports?.({ provider: "umans", credential: { type: "oauth", accessToken: "x" } }), + ).toBe(false); + expect(umansUsageProvider.supports?.({ provider: "umans", credential: { type: "api_key", apiKey: "x" } })).toBe( + true, + ); + }); + + it("includes plan display name and account identity in metadata", async () => { + const report = await umansUsageProvider.fetchUsage( + { + provider: "umans", + credential: { type: "api_key", apiKey: "sk-test", accountId: "acct-42", email: "dev@example.com" }, + }, + { fetch: fakeFetch(umansPayload({ plan: { display_name: "Code Pro" } })) }, + ); + expect(report?.metadata?.plan).toBe("Code Pro"); + expect(report?.metadata?.accountId).toBe("acct-42"); + expect(report?.metadata?.email).toBe("dev@example.com"); + }); +}); diff --git a/packages/ai/test/xai-login.test.ts b/packages/ai/test/xai-login.test.ts new file mode 100644 index 000000000..1cdd2ca1d --- /dev/null +++ b/packages/ai/test/xai-login.test.ts @@ -0,0 +1,98 @@ +import { Database } from "bun:sqlite"; +import { afterEach, describe, expect, test, vi } from "bun:test"; +import { AuthStorage, SqliteAuthCredentialStore } from "@oh-my-pi/pi-ai/auth-storage"; +import { getOAuthProviders } from "@oh-my-pi/pi-ai/registry/oauth"; +import { getEnvApiKey } from "@oh-my-pi/pi-ai/stream"; +import type { FetchImpl } from "@oh-my-pi/pi-ai/types"; + +const originalXaiApiKey = Bun.env.XAI_API_KEY; + +afterEach(() => { + if (originalXaiApiKey === undefined) { + delete Bun.env.XAI_API_KEY; + } else { + Bun.env.XAI_API_KEY = originalXaiApiKey; + } + vi.restoreAllMocks(); +}); + +describe("xAI API login wiring", () => { + test("registers xAI API in the OAuth provider selector", () => { + const provider = getOAuthProviders().find(item => item.id === "xai"); + expect(provider).toBeDefined(); + expect(provider?.name).toBe("xAI API"); + expect(provider?.available).toBe(true); + }); + + test("resolves XAI_API_KEY from environment", () => { + Bun.env.XAI_API_KEY = "xai-env-key"; + expect(getEnvApiKey("xai")).toBe("xai-env-key"); + }); + + test("AuthStorage.login('xai') validates against /models and stores the pasted key", async () => { + const fetchCalls: Array<{ url: string; init: RequestInit | undefined }> = []; + const fetchMock: FetchImpl = vi.fn(async (input: string | URL | Request, init?: RequestInit) => { + let url: string; + if (typeof input === "string") { + url = input; + } else if (input instanceof URL) { + url = input.toString(); + } else { + url = input.url; + } + fetchCalls.push({ url, init }); + if (url === "https://api.x.ai/v1/models") { + return new Response(JSON.stringify({ data: [{ id: "grok-4" }] }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + } + throw new Error(`unexpected fetch: ${url}`); + }); + + const store = new SqliteAuthCredentialStore(new Database(":memory:")); + const storage = new AuthStorage(store); + await storage.reload(); + + await storage.login("xai", { + onAuth: () => {}, + onPrompt: async () => "xai-validated", + fetch: fetchMock, + }); + + const credential = await storage.get("xai"); + expect(credential).toEqual({ type: "api_key", key: "xai-validated", source: "login" }); + + const modelsCall = fetchCalls.find(call => call.url.endsWith("/v1/models")); + expect(modelsCall).toBeDefined(); + const headers = new Headers(modelsCall?.init?.headers); + expect(headers.get("Authorization")).toBe("Bearer xai-validated"); + + store.close(); + }); + + test("AuthStorage.login('xai') rejects keys that fail /models validation", async () => { + const fetchMock: FetchImpl = vi.fn( + async () => + new Response("Unauthorized", { + status: 401, + headers: { "Content-Type": "text/plain" }, + }), + ); + + const store = new SqliteAuthCredentialStore(new Database(":memory:")); + const storage = new AuthStorage(store); + await storage.reload(); + + await expect( + storage.login("xai", { + onAuth: () => {}, + onPrompt: async () => "xai-bogus", + fetch: fetchMock, + }), + ).rejects.toThrow(/xAI API key validation failed \(401\)/); + + expect(await storage.get("xai")).toBeUndefined(); + store.close(); + }); +}); diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index 59e7a4f7f..828694bdb 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Added + +- Regenerated the Cursor agent protobufs (`discovery/cursor-gen/agent_pb.ts`) against the modern `agent.proto`, adding the message and enum families current Cursor CLI builds emit: Pi tool exec frames, hook queries and responses, subagents, allowlist prechecks, MCP state, smart-mode classification, canvas diagnostics, conversation search, agent-store conflicts and git diff. Purely additive — no existing exported symbol changed shape. + ### Fixed - Fixed an issue where LM Studio first turns failed with a 400 Invalid tool_choice error when a named tool was forced, by using the supported tool_choice: "required" string selector. diff --git a/packages/catalog/package.json b/packages/catalog/package.json index 1f4b40bae..599c8a1ad 100644 --- a/packages/catalog/package.json +++ b/packages/catalog/package.json @@ -30,7 +30,8 @@ "test": "bun test --parallel", "fix": "biome check --write --unsafe .", "fmt": "biome format --write .", - "gen:models": "bun scripts/generate-models.ts" + "gen:models": "bun scripts/generate-models.ts", + "gen:cursor-proto": "protoc --plugin=protoc-gen-es=../../node_modules/.bin/protoc-gen-es --es_out=src/discovery/cursor-gen --es_opt=target=ts -I ../ai/src/providers/cursor/proto ../ai/src/providers/cursor/proto/agent.proto" }, "dependencies": { "@bufbuild/protobuf": "catalog:", diff --git a/packages/catalog/src/discovery/cursor-gen/agent_pb.ts b/packages/catalog/src/discovery/cursor-gen/agent_pb.ts index 050b61686..21e4d8244 100644 --- a/packages/catalog/src/discovery/cursor-gen/agent_pb.ts +++ b/packages/catalog/src/discovery/cursor-gen/agent_pb.ts @@ -1,66 +1,62 @@ -// @generated by protoc-gen-es v2.10.2 with parameter "target=ts" +// @generated by protoc-gen-es v2.13.0 with parameter "target=ts" // @generated from file agent.proto (package agent.v1, syntax proto3) /* eslint-disable */ -import type { Message } from "@bufbuild/protobuf"; import type { GenEnum, GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2"; import { enumDesc, fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2"; +import type { Message } from "@bufbuild/protobuf"; /** * Describes the file agent.proto. */ -export const file_agent: GenFile = - /*@__PURE__*/ - fileDesc( - "CgthZ2VudC5wcm90bxIIYWdlbnQudjEicgoOR2xvYlRvb2xSZXN1bHQSLAoHc3VjY2VzcxgBIAEoCzIZLmFnZW50LnYxLkdsb2JUb29sU3VjY2Vzc0gAEigKBWVycm9yGAIgASgLMhcuYWdlbnQudjEuR2xvYlRvb2xFcnJvckgAQggKBnJlc3VsdCIeCg1HbG9iVG9vbEVycm9yEg0KBWVycm9yGAEgASgJIokBCg9HbG9iVG9vbFN1Y2Nlc3MSDwoHcGF0dGVybhgBIAEoCRIMCgRwYXRoGAIgASgJEg0KBWZpbGVzGAMgAygJEhMKC3RvdGFsX2ZpbGVzGAQgASgFEhgKEGNsaWVudF90cnVuY2F0ZWQYBSABKAgSGQoRcmlwZ3JlcF90cnVuY2F0ZWQYBiABKAgiRgoMR2xvYlRvb2xDYWxsEgwKBGFyZ3MYASABKAwSKAoGcmVzdWx0GAIgASgLMhguYWdlbnQudjEuR2xvYlRvb2xSZXN1bHQibQoRUmVhZExpbnRzVG9vbENhbGwSKQoEYXJncxgBIAEoCzIbLmFnZW50LnYxLlJlYWRMaW50c1Rvb2xBcmdzEi0KBnJlc3VsdBgCIAEoCzIdLmFnZW50LnYxLlJlYWRMaW50c1Rvb2xSZXN1bHQiIgoRUmVhZExpbnRzVG9vbEFyZ3MSDQoFcGF0aHMYASADKAkigQEKE1JlYWRMaW50c1Rvb2xSZXN1bHQSMQoHc3VjY2VzcxgBIAEoCzIeLmFnZW50LnYxLlJlYWRMaW50c1Rvb2xTdWNjZXNzSAASLQoFZXJyb3IYAiABKAsyHC5hZ2VudC52MS5SZWFkTGludHNUb29sRXJyb3JIAEIICgZyZXN1bHQiewoUUmVhZExpbnRzVG9vbFN1Y2Nlc3MSMwoQZmlsZV9kaWFnbm9zdGljcxgBIAMoCzIZLmFnZW50LnYxLkZpbGVEaWFnbm9zdGljcxITCgt0b3RhbF9maWxlcxgCIAEoBRIZChF0b3RhbF9kaWFnbm9zdGljcxgDIAEoBSJpCg9GaWxlRGlhZ25vc3RpY3MSDAoEcGF0aBgBIAEoCRItCgtkaWFnbm9zdGljcxgCIAMoCzIYLmFnZW50LnYxLkRpYWdub3N0aWNJdGVtEhkKEWRpYWdub3N0aWNzX2NvdW50GAMgASgFIqsBCg5EaWFnbm9zdGljSXRlbRIuCghzZXZlcml0eRgBIAEoDjIcLmFnZW50LnYxLkRpYWdub3N0aWNTZXZlcml0eRIoCgVyYW5nZRgCIAEoCzIZLmFnZW50LnYxLkRpYWdub3N0aWNSYW5nZRIPCgdtZXNzYWdlGAMgASgJEg4KBnNvdXJjZRgEIAEoCRIMCgRjb2RlGAUgASgJEhAKCGlzX3N0YWxlGAYgASgIIlUKD0RpYWdub3N0aWNSYW5nZRIhCgVzdGFydBgBIAEoCzISLmFnZW50LnYxLlBvc2l0aW9uEh8KA2VuZBgCIAEoCzISLmFnZW50LnYxLlBvc2l0aW9uIisKElJlYWRMaW50c1Rvb2xFcnJvchIVCg1lcnJvcl9tZXNzYWdlGAEgASgJIh0KDE1jcFRvb2xFcnJvchINCgVlcnJvchgBIAEoCSLSAQoNTWNwVG9vbFJlc3VsdBInCgdzdWNjZXNzGAEgASgLMhQuYWdlbnQudjEuTWNwU3VjY2Vzc0gAEicKBWVycm9yGAIgASgLMhYuYWdlbnQudjEuTWNwVG9vbEVycm9ySAASKQoIcmVqZWN0ZWQYAyABKAsyFS5hZ2VudC52MS5NY3BSZWplY3RlZEgAEjoKEXBlcm1pc3Npb25fZGVuaWVkGAQgASgLMh0uYWdlbnQudjEuTWNwUGVybWlzc2lvbkRlbmllZEgAQggKBnJlc3VsdCJXCgtNY3BUb29sQ2FsbBIfCgRhcmdzGAEgASgLMhEuYWdlbnQudjEuTWNwQXJncxInCgZyZXN1bHQYAiABKAsyFy5hZ2VudC52MS5NY3BUb29sUmVzdWx0Im0KEVNlbVNlYXJjaFRvb2xDYWxsEikKBGFyZ3MYASABKAsyGy5hZ2VudC52MS5TZW1TZWFyY2hUb29sQXJncxItCgZyZXN1bHQYAiABKAsyHS5hZ2VudC52MS5TZW1TZWFyY2hUb29sUmVzdWx0IlMKEVNlbVNlYXJjaFRvb2xBcmdzEg0KBXF1ZXJ5GAEgASgJEhoKEnRhcmdldF9kaXJlY3RvcmllcxgCIAMoCRITCgtleHBsYW5hdGlvbhgDIAEoCSKBAQoTU2VtU2VhcmNoVG9vbFJlc3VsdBIxCgdzdWNjZXNzGAEgASgLMh4uYWdlbnQudjEuU2VtU2VhcmNoVG9vbFN1Y2Nlc3NIABItCgVlcnJvchgCIAEoCzIcLmFnZW50LnYxLlNlbVNlYXJjaFRvb2xFcnJvckgAQggKBnJlc3VsdCI9ChRTZW1TZWFyY2hUb29sU3VjY2VzcxIPCgdyZXN1bHRzGAEgASgJEhQKDGNvZGVfcmVzdWx0cxgCIAMoDCIrChJTZW1TZWFyY2hUb29sRXJyb3ISFQoNZXJyb3JfbWVzc2FnZRgBIAEoCSKCAQoYTGlzdE1jcFJlc291cmNlc1Rvb2xDYWxsEjAKBGFyZ3MYASABKAsyIi5hZ2VudC52MS5MaXN0TWNwUmVzb3VyY2VzRXhlY0FyZ3MSNAoGcmVzdWx0GAIgASgLMiQuYWdlbnQudjEuTGlzdE1jcFJlc291cmNlc0V4ZWNSZXN1bHQifwoXUmVhZE1jcFJlc291cmNlVG9vbENhbGwSLwoEYXJncxgBIAEoCzIhLmFnZW50LnYxLlJlYWRNY3BSZXNvdXJjZUV4ZWNBcmdzEjMKBnJlc3VsdBgCIAEoCzIjLmFnZW50LnYxLlJlYWRNY3BSZXNvdXJjZUV4ZWNSZXN1bHQiWQoNRmV0Y2hUb29sQ2FsbBIhCgRhcmdzGAEgASgLMhMuYWdlbnQudjEuRmV0Y2hBcmdzEiUKBnJlc3VsdBgCIAEoCzIVLmFnZW50LnYxLkZldGNoUmVzdWx0Im4KFFJlY29yZFNjcmVlblRvb2xDYWxsEigKBGFyZ3MYASABKAsyGi5hZ2VudC52MS5SZWNvcmRTY3JlZW5BcmdzEiwKBnJlc3VsdBgCIAEoCzIcLmFnZW50LnYxLlJlY29yZFNjcmVlblJlc3VsdCJ3ChdXcml0ZVNoZWxsU3RkaW5Ub29sQ2FsbBIrCgRhcmdzGAEgASgLMh0uYWdlbnQudjEuV3JpdGVTaGVsbFN0ZGluQXJncxIvCgZyZXN1bHQYAiABKAsyHy5hZ2VudC52MS5Xcml0ZVNoZWxsU3RkaW5SZXN1bHQisQEKC1JlZmxlY3RBcmdzEiIKGnVuZXhwZWN0ZWRfYWN0aW9uX291dGNvbWVzGAEgASgJEh0KFXJlbGV2YW50X2luc3RydWN0aW9ucxgCIAEoCRIZChFzY2VuYXJpb19hbmFseXNpcxgDIAEoCRIaChJjcml0aWNhbF9zeW50aGVzaXMYBCABKAkSEgoKbmV4dF9zdGVwcxgFIAEoCRIUCgx0b29sX2NhbGxfaWQYBiABKAkibwoNUmVmbGVjdFJlc3VsdBIrCgdzdWNjZXNzGAEgASgLMhguYWdlbnQudjEuUmVmbGVjdFN1Y2Nlc3NIABInCgVlcnJvchgCIAEoCzIWLmFnZW50LnYxLlJlZmxlY3RFcnJvckgAQggKBnJlc3VsdCIQCg5SZWZsZWN0U3VjY2VzcyIdCgxSZWZsZWN0RXJyb3ISDQoFZXJyb3IYASABKAkiXwoPUmVmbGVjdFRvb2xDYWxsEiMKBGFyZ3MYASABKAsyFS5hZ2VudC52MS5SZWZsZWN0QXJncxInCgZyZXN1bHQYAiABKAsyFy5hZ2VudC52MS5SZWZsZWN0UmVzdWx0IlkKF1N0YXJ0R3JpbmRFeGVjdXRpb25BcmdzEhgKC2V4cGxhbmF0aW9uGAEgASgJSACIAQESFAoMdG9vbF9jYWxsX2lkGAIgASgJQg4KDF9leHBsYW5hdGlvbiKTAQoZU3RhcnRHcmluZEV4ZWN1dGlvblJlc3VsdBI3CgdzdWNjZXNzGAEgASgLMiQuYWdlbnQudjEuU3RhcnRHcmluZEV4ZWN1dGlvblN1Y2Nlc3NIABIzCgVlcnJvchgCIAEoCzIiLmFnZW50LnYxLlN0YXJ0R3JpbmRFeGVjdXRpb25FcnJvckgAQggKBnJlc3VsdCIcChpTdGFydEdyaW5kRXhlY3V0aW9uU3VjY2VzcyIpChhTdGFydEdyaW5kRXhlY3V0aW9uRXJyb3ISDQoFZXJyb3IYASABKAkigwEKG1N0YXJ0R3JpbmRFeGVjdXRpb25Ub29sQ2FsbBIvCgRhcmdzGAEgASgLMiEuYWdlbnQudjEuU3RhcnRHcmluZEV4ZWN1dGlvbkFyZ3MSMwoGcmVzdWx0GAIgASgLMiMuYWdlbnQudjEuU3RhcnRHcmluZEV4ZWN1dGlvblJlc3VsdCJYChZTdGFydEdyaW5kUGxhbm5pbmdBcmdzEhgKC2V4cGxhbmF0aW9uGAEgASgJSACIAQESFAoMdG9vbF9jYWxsX2lkGAIgASgJQg4KDF9leHBsYW5hdGlvbiKQAQoYU3RhcnRHcmluZFBsYW5uaW5nUmVzdWx0EjYKB3N1Y2Nlc3MYASABKAsyIy5hZ2VudC52MS5TdGFydEdyaW5kUGxhbm5pbmdTdWNjZXNzSAASMgoFZXJyb3IYAiABKAsyIS5hZ2VudC52MS5TdGFydEdyaW5kUGxhbm5pbmdFcnJvckgAQggKBnJlc3VsdCIbChlTdGFydEdyaW5kUGxhbm5pbmdTdWNjZXNzIigKF1N0YXJ0R3JpbmRQbGFubmluZ0Vycm9yEg0KBWVycm9yGAEgASgJIoABChpTdGFydEdyaW5kUGxhbm5pbmdUb29sQ2FsbBIuCgRhcmdzGAEgASgLMiAuYWdlbnQudjEuU3RhcnRHcmluZFBsYW5uaW5nQXJncxIyCgZyZXN1bHQYAiABKAsyIi5hZ2VudC52MS5TdGFydEdyaW5kUGxhbm5pbmdSZXN1bHQinAEKCFRhc2tBcmdzEhMKC2Rlc2NyaXB0aW9uGAEgASgJEg4KBnByb21wdBgCIAEoCRItCg1zdWJhZ2VudF90eXBlGAMgASgLMhYuYWdlbnQudjEuU3ViYWdlbnRUeXBlEhIKBW1vZGVsGAQgASgJSACIAQESEwoGcmVzdW1lGAUgASgJSAGIAQFCCAoGX21vZGVsQgkKB19yZXN1bWUiqgEKC1Rhc2tTdWNjZXNzEjYKEmNvbnZlcnNhdGlvbl9zdGVwcxgBIAMoCzIaLmFnZW50LnYxLkNvbnZlcnNhdGlvblN0ZXASFQoIYWdlbnRfaWQYAiABKAlIAIgBARIVCg1pc19iYWNrZ3JvdW5kGAMgASgIEhgKC2R1cmF0aW9uX21zGAQgASgESAGIAQFCCwoJX2FnZW50X2lkQg4KDF9kdXJhdGlvbl9tcyIaCglUYXNrRXJyb3ISDQoFZXJyb3IYASABKAkiZgoKVGFza1Jlc3VsdBIoCgdzdWNjZXNzGAEgASgLMhUuYWdlbnQudjEuVGFza1N1Y2Nlc3NIABIkCgVlcnJvchgCIAEoCzITLmFnZW50LnYxLlRhc2tFcnJvckgAQggKBnJlc3VsdCJWCgxUYXNrVG9vbENhbGwSIAoEYXJncxgBIAEoCzISLmFnZW50LnYxLlRhc2tBcmdzEiQKBnJlc3VsdBgCIAEoCzIULmFnZW50LnYxLlRhc2tSZXN1bHQiTAoRVGFza1Rvb2xDYWxsRGVsdGESNwoSaW50ZXJhY3Rpb25fdXBkYXRlGAEgASgLMhsuYWdlbnQudjEuSW50ZXJhY3Rpb25VcGRhdGUiyw8KCFRvb2xDYWxsEjIKD3NoZWxsX3Rvb2xfY2FsbBgBIAEoCzIXLmFnZW50LnYxLlNoZWxsVG9vbENhbGxIABI0ChBkZWxldGVfdG9vbF9jYWxsGAMgASgLMhguYWdlbnQudjEuRGVsZXRlVG9vbENhbGxIABIwCg5nbG9iX3Rvb2xfY2FsbBgEIAEoCzIWLmFnZW50LnYxLkdsb2JUb29sQ2FsbEgAEjAKDmdyZXBfdG9vbF9jYWxsGAUgASgLMhYuYWdlbnQudjEuR3JlcFRvb2xDYWxsSAASMAoOcmVhZF90b29sX2NhbGwYCCABKAsyFi5hZ2VudC52MS5SZWFkVG9vbENhbGxIABI/ChZ1cGRhdGVfdG9kb3NfdG9vbF9jYWxsGAkgASgLMh0uYWdlbnQudjEuVXBkYXRlVG9kb3NUb29sQ2FsbEgAEjsKFHJlYWRfdG9kb3NfdG9vbF9jYWxsGAogASgLMhsuYWdlbnQudjEuUmVhZFRvZG9zVG9vbENhbGxIABIwCg5lZGl0X3Rvb2xfY2FsbBgMIAEoCzIWLmFnZW50LnYxLkVkaXRUb29sQ2FsbEgAEiwKDGxzX3Rvb2xfY2FsbBgNIAEoCzIULmFnZW50LnYxLkxzVG9vbENhbGxIABI7ChRyZWFkX2xpbnRzX3Rvb2xfY2FsbBgOIAEoCzIbLmFnZW50LnYxLlJlYWRMaW50c1Rvb2xDYWxsSAASLgoNbWNwX3Rvb2xfY2FsbBgPIAEoCzIVLmFnZW50LnYxLk1jcFRvb2xDYWxsSAASOwoUc2VtX3NlYXJjaF90b29sX2NhbGwYECABKAsyGy5hZ2VudC52MS5TZW1TZWFyY2hUb29sQ2FsbEgAEj0KFWNyZWF0ZV9wbGFuX3Rvb2xfY2FsbBgRIAEoCzIcLmFnZW50LnYxLkNyZWF0ZVBsYW5Ub29sQ2FsbEgAEjsKFHdlYl9zZWFyY2hfdG9vbF9jYWxsGBIgASgLMhsuYWdlbnQudjEuV2ViU2VhcmNoVG9vbENhbGxIABIwCg50YXNrX3Rvb2xfY2FsbBgTIAEoCzIWLmFnZW50LnYxLlRhc2tUb29sQ2FsbEgAEkoKHGxpc3RfbWNwX3Jlc291cmNlc190b29sX2NhbGwYFCABKAsyIi5hZ2VudC52MS5MaXN0TWNwUmVzb3VyY2VzVG9vbENhbGxIABJIChtyZWFkX21jcF9yZXNvdXJjZV90b29sX2NhbGwYFSABKAsyIS5hZ2VudC52MS5SZWFkTWNwUmVzb3VyY2VUb29sQ2FsbEgAEkYKGmFwcGx5X2FnZW50X2RpZmZfdG9vbF9jYWxsGBYgASgLMiAuYWdlbnQudjEuQXBwbHlBZ2VudERpZmZUb29sQ2FsbEgAEj8KFmFza19xdWVzdGlvbl90b29sX2NhbGwYFyABKAsyHS5hZ2VudC52MS5Bc2tRdWVzdGlvblRvb2xDYWxsSAASMgoPZmV0Y2hfdG9vbF9jYWxsGBggASgLMhcuYWdlbnQudjEuRmV0Y2hUb29sQ2FsbEgAEj0KFXN3aXRjaF9tb2RlX3Rvb2xfY2FsbBgZIAEoCzIcLmFnZW50LnYxLlN3aXRjaE1vZGVUb29sQ2FsbEgAEjsKFGV4YV9zZWFyY2hfdG9vbF9jYWxsGBogASgLMhsuYWdlbnQudjEuRXhhU2VhcmNoVG9vbENhbGxIABI5ChNleGFfZmV0Y2hfdG9vbF9jYWxsGBsgASgLMhouYWdlbnQudjEuRXhhRmV0Y2hUb29sQ2FsbEgAEkMKGGdlbmVyYXRlX2ltYWdlX3Rvb2xfY2FsbBgcIAEoCzIfLmFnZW50LnYxLkdlbmVyYXRlSW1hZ2VUb29sQ2FsbEgAEkEKF3JlY29yZF9zY3JlZW5fdG9vbF9jYWxsGB0gASgLMh4uYWdlbnQudjEuUmVjb3JkU2NyZWVuVG9vbENhbGxIABI/ChZjb21wdXRlcl91c2VfdG9vbF9jYWxsGB4gASgLMh0uYWdlbnQudjEuQ29tcHV0ZXJVc2VUb29sQ2FsbEgAEkgKG3dyaXRlX3NoZWxsX3N0ZGluX3Rvb2xfY2FsbBgfIAEoCzIhLmFnZW50LnYxLldyaXRlU2hlbGxTdGRpblRvb2xDYWxsSAASNgoRcmVmbGVjdF90b29sX2NhbGwYICABKAsyGS5hZ2VudC52MS5SZWZsZWN0VG9vbENhbGxIABJOCh5zZXR1cF92bV9lbnZpcm9ubWVudF90b29sX2NhbGwYISABKAsyJC5hZ2VudC52MS5TZXR1cFZtRW52aXJvbm1lbnRUb29sQ2FsbEgAEjoKE3RydW5jYXRlZF90b29sX2NhbGwYIiABKAsyGy5hZ2VudC52MS5UcnVuY2F0ZWRUb29sQ2FsbEgAElAKH3N0YXJ0X2dyaW5kX2V4ZWN1dGlvbl90b29sX2NhbGwYIyABKAsyJS5hZ2VudC52MS5TdGFydEdyaW5kRXhlY3V0aW9uVG9vbENhbGxIABJOCh5zdGFydF9ncmluZF9wbGFubmluZ190b29sX2NhbGwYJCABKAsyJC5hZ2VudC52MS5TdGFydEdyaW5kUGxhbm5pbmdUb29sQ2FsbEgAQgYKBHRvb2wiFwoVVHJ1bmNhdGVkVG9vbENhbGxBcmdzIhoKGFRydW5jYXRlZFRvb2xDYWxsU3VjY2VzcyInChZUcnVuY2F0ZWRUb29sQ2FsbEVycm9yEg0KBWVycm9yGAEgASgJIo0BChdUcnVuY2F0ZWRUb29sQ2FsbFJlc3VsdBI1CgdzdWNjZXNzGAEgASgLMiIuYWdlbnQudjEuVHJ1bmNhdGVkVG9vbENhbGxTdWNjZXNzSAASMQoFZXJyb3IYAiABKAsyIC5hZ2VudC52MS5UcnVuY2F0ZWRUb29sQ2FsbEVycm9ySABCCAoGcmVzdWx0IpQBChFUcnVuY2F0ZWRUb29sQ2FsbBIdChVvcmlnaW5hbF9zdGVwX2Jsb2JfaWQYASABKAwSLQoEYXJncxgCIAEoCzIfLmFnZW50LnYxLlRydW5jYXRlZFRvb2xDYWxsQXJncxIxCgZyZXN1bHQYAyABKAsyIS5hZ2VudC52MS5UcnVuY2F0ZWRUb29sQ2FsbFJlc3VsdCLRAQoNVG9vbENhbGxEZWx0YRI9ChVzaGVsbF90b29sX2NhbGxfZGVsdGEYASABKAsyHC5hZ2VudC52MS5TaGVsbFRvb2xDYWxsRGVsdGFIABI7ChR0YXNrX3Rvb2xfY2FsbF9kZWx0YRgCIAEoCzIbLmFnZW50LnYxLlRhc2tUb29sQ2FsbERlbHRhSAASOwoUZWRpdF90b29sX2NhbGxfZGVsdGEYAyABKAsyGy5hZ2VudC52MS5FZGl0VG9vbENhbGxEZWx0YUgAQgcKBWRlbHRhIrYBChBDb252ZXJzYXRpb25TdGVwEjcKEWFzc2lzdGFudF9tZXNzYWdlGAEgASgLMhouYWdlbnQudjEuQXNzaXN0YW50TWVzc2FnZUgAEicKCXRvb2xfY2FsbBgCIAEoCzISLmFnZW50LnYxLlRvb2xDYWxsSAASNQoQdGhpbmtpbmdfbWVzc2FnZRgDIAEoCzIZLmFnZW50LnYxLlRoaW5raW5nTWVzc2FnZUgAQgkKB21lc3NhZ2UigQQKEkNvbnZlcnNhdGlvbkFjdGlvbhI6ChN1c2VyX21lc3NhZ2VfYWN0aW9uGAEgASgLMhsuYWdlbnQudjEuVXNlck1lc3NhZ2VBY3Rpb25IABIvCg1yZXN1bWVfYWN0aW9uGAIgASgLMhYuYWdlbnQudjEuUmVzdW1lQWN0aW9uSAASLwoNY2FuY2VsX2FjdGlvbhgDIAEoCzIWLmFnZW50LnYxLkNhbmNlbEFjdGlvbkgAEjUKEHN1bW1hcml6ZV9hY3Rpb24YBCABKAsyGS5hZ2VudC52MS5TdW1tYXJpemVBY3Rpb25IABI8ChRzaGVsbF9jb21tYW5kX2FjdGlvbhgFIAEoCzIcLmFnZW50LnYxLlNoZWxsQ29tbWFuZEFjdGlvbkgAEjYKEXN0YXJ0X3BsYW5fYWN0aW9uGAYgASgLMhkuYWdlbnQudjEuU3RhcnRQbGFuQWN0aW9uSAASOgoTZXhlY3V0ZV9wbGFuX2FjdGlvbhgHIAEoCzIbLmFnZW50LnYxLkV4ZWN1dGVQbGFuQWN0aW9uSAASWgokYXN5bmNfYXNrX3F1ZXN0aW9uX2NvbXBsZXRpb25fYWN0aW9uGAggASgLMiouYWdlbnQudjEuQXN5bmNBc2tRdWVzdGlvbkNvbXBsZXRpb25BY3Rpb25IAEIICgZhY3Rpb24ivwEKEVVzZXJNZXNzYWdlQWN0aW9uEisKDHVzZXJfbWVzc2FnZRgBIAEoCzIVLmFnZW50LnYxLlVzZXJNZXNzYWdlEjEKD3JlcXVlc3RfY29udGV4dBgCIAEoCzIYLmFnZW50LnYxLlJlcXVlc3RDb250ZXh0EikKHHNlbmRfdG9faW50ZXJhY3Rpb25fbGlzdGVuZXIYAyABKAhIAIgBAUIfCh1fc2VuZF90b19pbnRlcmFjdGlvbl9saXN0ZW5lciIOCgxDYW5jZWxBY3Rpb24iQQoMUmVzdW1lQWN0aW9uEjEKD3JlcXVlc3RfY29udGV4dBgCIAEoCzIYLmFnZW50LnYxLlJlcXVlc3RDb250ZXh0IqABCiBBc3luY0Fza1F1ZXN0aW9uQ29tcGxldGlvbkFjdGlvbhIdChVvcmlnaW5hbF90b29sX2NhbGxfaWQYASABKAkSMAoNb3JpZ2luYWxfYXJncxgCIAEoCzIZLmFnZW50LnYxLkFza1F1ZXN0aW9uQXJncxIrCgZyZXN1bHQYAyABKAsyGy5hZ2VudC52MS5Bc2tRdWVzdGlvblJlc3VsdCIRCg9TdW1tYXJpemVBY3Rpb24iVAoSU2hlbGxDb21tYW5kQWN0aW9uEi0KDXNoZWxsX2NvbW1hbmQYASABKAsyFi5hZ2VudC52MS5TaGVsbENvbW1hbmQSDwoHZXhlY19pZBgCIAEoCSKCAQoPU3RhcnRQbGFuQWN0aW9uEisKDHVzZXJfbWVzc2FnZRgBIAEoCzIVLmFnZW50LnYxLlVzZXJNZXNzYWdlEjEKD3JlcXVlc3RfY29udGV4dBgCIAEoCzIYLmFnZW50LnYxLlJlcXVlc3RDb250ZXh0Eg8KB2lzX3NwZWMYAyABKAgi4gEKEUV4ZWN1dGVQbGFuQWN0aW9uEjEKD3JlcXVlc3RfY29udGV4dBgBIAEoCzIYLmFnZW50LnYxLlJlcXVlc3RDb250ZXh0Ei0KBHBsYW4YAiABKAsyGi5hZ2VudC52MS5Db252ZXJzYXRpb25QbGFuSACIAQESGgoNcGxhbl9maWxlX3VyaRgDIAEoCUgBiAEBEh4KEXBsYW5fZmlsZV9jb250ZW50GAQgASgJSAKIAQFCBwoFX3BsYW5CEAoOX3BsYW5fZmlsZV91cmlCFAoSX3BsYW5fZmlsZV9jb250ZW50IugCCgtVc2VyTWVzc2FnZRIMCgR0ZXh0GAEgASgJEhIKCm1lc3NhZ2VfaWQYAiABKAkSOAoQc2VsZWN0ZWRfY29udGV4dBgDIAEoCzIZLmFnZW50LnYxLlNlbGVjdGVkQ29udGV4dEgAiAEBEgwKBG1vZGUYBCABKAUSHQoQaXNfc2ltdWxhdGVkX21zZxgFIAEoCEgBiAEBEh8KEmJlc3Rfb2Zfbl9ncm91cF9pZBgGIAEoCUgCiAEBEigKG3RyeV91c2VfYmVzdF9vZl9uX3Byb21vdGlvbhgHIAEoCEgDiAEBEhYKCXJpY2hfdGV4dBgIIAEoCUgEiAEBQhMKEV9zZWxlY3RlZF9jb250ZXh0QhMKEV9pc19zaW11bGF0ZWRfbXNnQhUKE19iZXN0X29mX25fZ3JvdXBfaWRCHgocX3RyeV91c2VfYmVzdF9vZl9uX3Byb21vdGlvbkIMCgpfcmljaF90ZXh0IiAKEEFzc2lzdGFudE1lc3NhZ2USDAoEdGV4dBgBIAEoCSI0Cg9UaGlua2luZ01lc3NhZ2USDAoEdGV4dBgBIAEoCRITCgtkdXJhdGlvbl9tcxgCIAEoDSIfCgxTaGVsbENvbW1hbmQSDwoHY29tbWFuZBgBIAEoCSJACgtTaGVsbE91dHB1dBIOCgZzdGRvdXQYASABKAkSDgoGc3RkZXJyGAIgASgJEhEKCWV4aXRfY29kZRgDIAEoBSKiAQoQQ29udmVyc2F0aW9uVHVybhJCChdhZ2VudF9jb252ZXJzYXRpb25fdHVybhgBIAEoCzIfLmFnZW50LnYxLkFnZW50Q29udmVyc2F0aW9uVHVybkgAEkIKF3NoZWxsX2NvbnZlcnNhdGlvbl90dXJuGAIgASgLMh8uYWdlbnQudjEuU2hlbGxDb252ZXJzYXRpb25UdXJuSABCBgoEdHVybiIgChBDb252ZXJzYXRpb25QbGFuEgwKBHBsYW4YASABKAkivQEKGUNvbnZlcnNhdGlvblR1cm5TdHJ1Y3R1cmUSSwoXYWdlbnRfY29udmVyc2F0aW9uX3R1cm4YASABKAsyKC5hZ2VudC52MS5BZ2VudENvbnZlcnNhdGlvblR1cm5TdHJ1Y3R1cmVIABJLChdzaGVsbF9jb252ZXJzYXRpb25fdHVybhgCIAEoCzIoLmFnZW50LnYxLlNoZWxsQ29udmVyc2F0aW9uVHVyblN0cnVjdHVyZUgAQgYKBHR1cm4ilwEKFUFnZW50Q29udmVyc2F0aW9uVHVybhIrCgx1c2VyX21lc3NhZ2UYASABKAsyFS5hZ2VudC52MS5Vc2VyTWVzc2FnZRIpCgVzdGVwcxgCIAMoCzIaLmFnZW50LnYxLkNvbnZlcnNhdGlvblN0ZXASFwoKcmVxdWVzdF9pZBgDIAEoCUgAiAEBQg0KC19yZXF1ZXN0X2lkIm0KHkFnZW50Q29udmVyc2F0aW9uVHVyblN0cnVjdHVyZRIUCgx1c2VyX21lc3NhZ2UYASABKAwSDQoFc3RlcHMYAiADKAwSFwoKcmVxdWVzdF9pZBgDIAEoCUgAiAEBQg0KC19yZXF1ZXN0X2lkInMKFVNoZWxsQ29udmVyc2F0aW9uVHVybhItCg1zaGVsbF9jb21tYW5kGAEgASgLMhYuYWdlbnQudjEuU2hlbGxDb21tYW5kEisKDHNoZWxsX291dHB1dBgCIAEoCzIVLmFnZW50LnYxLlNoZWxsT3V0cHV0Ik0KHlNoZWxsQ29udmVyc2F0aW9uVHVyblN0cnVjdHVyZRIVCg1zaGVsbF9jb21tYW5kGAEgASgMEhQKDHNoZWxsX291dHB1dBgCIAEoDCImChNDb252ZXJzYXRpb25TdW1tYXJ5Eg8KB3N1bW1hcnkYASABKAkieAoaQ29udmVyc2F0aW9uU3VtbWFyeUFyY2hpdmUSGwoTc3VtbWFyaXplZF9tZXNzYWdlcxgBIAMoDBIPCgdzdW1tYXJ5GAIgASgJEhMKC3dpbmRvd190YWlsGAMgASgNEhcKD3N1bW1hcnlfbWVzc2FnZRgEIAEoDCJDChhDb252ZXJzYXRpb25Ub2tlbkRldGFpbHMSEwoLdXNlZF90b2tlbnMYASABKA0SEgoKbWF4X3Rva2VucxgCIAEoDSJfCglGaWxlU3RhdGUSFAoHY29udGVudBgBIAEoCUgAiAEBEhwKD2luaXRpYWxfY29udGVudBgCIAEoCUgBiAEBQgoKCF9jb250ZW50QhIKEF9pbml0aWFsX2NvbnRlbnQiaAoSRmlsZVN0YXRlU3RydWN0dXJlEhQKB2NvbnRlbnQYASABKAxIAIgBARIcCg9pbml0aWFsX2NvbnRlbnQYAiABKAxIAYgBAUIKCghfY29udGVudEISChBfaW5pdGlhbF9jb250ZW50IjcKClN0ZXBUaW1pbmcSEwoLZHVyYXRpb25fbXMYASABKAQSFAoMdGltZXN0YW1wX21zGAIgASgEIvYEChFDb252ZXJzYXRpb25TdGF0ZRIhChlyb290X3Byb21wdF9tZXNzYWdlc19qc29uGAEgAygJEikKBXR1cm5zGAggAygLMhouYWdlbnQudjEuQ29udmVyc2F0aW9uVHVybhIhCgV0b2RvcxgDIAMoCzISLmFnZW50LnYxLlRvZG9JdGVtEhoKEnBlbmRpbmdfdG9vbF9jYWxscxgEIAMoCRI5Cg10b2tlbl9kZXRhaWxzGAUgASgLMiIuYWdlbnQudjEuQ29udmVyc2F0aW9uVG9rZW5EZXRhaWxzEjMKB3N1bW1hcnkYBiABKAsyHS5hZ2VudC52MS5Db252ZXJzYXRpb25TdW1tYXJ5SACIAQESLQoEcGxhbhgHIAEoCzIaLmFnZW50LnYxLkNvbnZlcnNhdGlvblBsYW5IAYgBARJCCg9zdW1tYXJ5X2FyY2hpdmUYCSABKAsyJC5hZ2VudC52MS5Db252ZXJzYXRpb25TdW1tYXJ5QXJjaGl2ZUgCiAEBEkAKC2ZpbGVfc3RhdGVzGAogAygLMisuYWdlbnQudjEuQ29udmVyc2F0aW9uU3RhdGUuRmlsZVN0YXRlc0VudHJ5Ej4KEHN1bW1hcnlfYXJjaGl2ZXMYCyADKAsyJC5hZ2VudC52MS5Db252ZXJzYXRpb25TdW1tYXJ5QXJjaGl2ZRpGCg9GaWxlU3RhdGVzRW50cnkSCwoDa2V5GAEgASgJEiIKBXZhbHVlGAIgASgLMhMuYWdlbnQudjEuRmlsZVN0YXRlOgI4AUIKCghfc3VtbWFyeUIHCgVfcGxhbkISChBfc3VtbWFyeV9hcmNoaXZlIscBChZTdWJhZ2VudFBlcnNpc3RlZFN0YXRlEkAKEmNvbnZlcnNhdGlvbl9zdGF0ZRgBIAEoCzIkLmFnZW50LnYxLkNvbnZlcnNhdGlvblN0YXRlU3RydWN0dXJlEhwKFGNyZWF0ZWRfdGltZXN0YW1wX21zGAIgASgEEh4KFmxhc3RfdXNlZF90aW1lc3RhbXBfbXMYAyABKAQSLQoNc3ViYWdlbnRfdHlwZRgEIAEoCzIWLmFnZW50LnYxLlN1YmFnZW50VHlwZSK3BwoaQ29udmVyc2F0aW9uU3RhdGVTdHJ1Y3R1cmUSEQoJdHVybnNfb2xkGAIgAygMEiEKGXJvb3RfcHJvbXB0X21lc3NhZ2VzX2pzb24YASADKAwSDQoFdHVybnMYCCADKAwSDQoFdG9kb3MYAyADKAwSGgoScGVuZGluZ190b29sX2NhbGxzGAQgAygJEjkKDXRva2VuX2RldGFpbHMYBSABKAsyIi5hZ2VudC52MS5Db252ZXJzYXRpb25Ub2tlbkRldGFpbHMSFAoHc3VtbWFyeRgGIAEoDEgAiAEBEhEKBHBsYW4YByABKAxIAYgBARIfChdwcmV2aW91c193b3Jrc3BhY2VfdXJpcxgJIAMoCRIRCgRtb2RlGAogASgFSAKIAQESHAoPc3VtbWFyeV9hcmNoaXZlGAsgASgMSAOIAQESSQoLZmlsZV9zdGF0ZXMYDCADKAsyNC5hZ2VudC52MS5Db252ZXJzYXRpb25TdGF0ZVN0cnVjdHVyZS5GaWxlU3RhdGVzRW50cnkSTgoOZmlsZV9zdGF0ZXNfdjIYDyADKAsyNi5hZ2VudC52MS5Db252ZXJzYXRpb25TdGF0ZVN0cnVjdHVyZS5GaWxlU3RhdGVzVjJFbnRyeRIYChBzdW1tYXJ5X2FyY2hpdmVzGA0gAygMEioKDHR1cm5fdGltaW5ncxgOIAMoCzIULmFnZW50LnYxLlN0ZXBUaW1pbmcSUQoPc3ViYWdlbnRfc3RhdGVzGBAgAygLMjguYWdlbnQudjEuQ29udmVyc2F0aW9uU3RhdGVTdHJ1Y3R1cmUuU3ViYWdlbnRTdGF0ZXNFbnRyeRIaChJzZWxmX3N1bW1hcnlfY291bnQYESABKA0SEgoKcmVhZF9wYXRocxgSIAMoCRoxCg9GaWxlU3RhdGVzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgMOgI4ARpRChFGaWxlU3RhdGVzVjJFbnRyeRILCgNrZXkYASABKAkSKwoFdmFsdWUYAiABKAsyHC5hZ2VudC52MS5GaWxlU3RhdGVTdHJ1Y3R1cmU6AjgBGlcKE1N1YmFnZW50U3RhdGVzRW50cnkSCwoDa2V5GAEgASgJEi8KBXZhbHVlGAIgASgLMiAuYWdlbnQudjEuU3ViYWdlbnRQZXJzaXN0ZWRTdGF0ZToCOAFCCgoIX3N1bW1hcnlCBwoFX3BsYW5CBwoFX21vZGVCEgoQX3N1bW1hcnlfYXJjaGl2ZSIRCg9UaGlua2luZ0RldGFpbHMiSAoRQXBpS2V5Q3JlZGVudGlhbHMSDwoHYXBpX2tleRgBIAEoCRIVCghiYXNlX3VybBgCIAEoCUgAiAEBQgsKCV9iYXNlX3VybCJJChBBenVyZUNyZWRlbnRpYWxzEg8KB2FwaV9rZXkYASABKAkSEAoIYmFzZV91cmwYAiABKAkSEgoKZGVwbG95bWVudBgDIAEoCSJ6ChJCZWRyb2NrQ3JlZGVudGlhbHMSEgoKYWNjZXNzX2tleRgBIAEoCRISCgpzZWNyZXRfa2V5GAIgASgJEg4KBnJlZ2lvbhgDIAEoCRIaCg1zZXNzaW9uX3Rva2VuGAQgASgJSACIAQFCEAoOX3Nlc3Npb25fdG9rZW4isQMKDE1vZGVsRGV0YWlscxIQCghtb2RlbF9pZBgBIAEoCRIYChBkaXNwbGF5X21vZGVsX2lkGAMgASgJEhQKDGRpc3BsYXlfbmFtZRgEIAEoCRIaChJkaXNwbGF5X25hbWVfc2hvcnQYBSABKAkSDwoHYWxpYXNlcxgGIAMoCRI4ChB0aGlua2luZ19kZXRhaWxzGAIgASgLMhkuYWdlbnQudjEuVGhpbmtpbmdEZXRhaWxzSAGIAQESFQoIbWF4X21vZGUYByABKAhIAogBARI6ChNhcGlfa2V5X2NyZWRlbnRpYWxzGAggASgLMhsuYWdlbnQudjEuQXBpS2V5Q3JlZGVudGlhbHNIABI3ChFhenVyZV9jcmVkZW50aWFscxgJIAEoCzIaLmFnZW50LnYxLkF6dXJlQ3JlZGVudGlhbHNIABI7ChNiZWRyb2NrX2NyZWRlbnRpYWxzGAogASgLMhwuYWdlbnQudjEuQmVkcm9ja0NyZWRlbnRpYWxzSABCDQoLY3JlZGVudGlhbHNCEwoRX3RoaW5raW5nX2RldGFpbHNCCwoJX21heF9tb2RlIrcCCg5SZXF1ZXN0ZWRNb2RlbBIQCghtb2RlbF9pZBgBIAEoCRIQCghtYXhfbW9kZRgCIAEoCBJACgpwYXJhbWV0ZXJzGAMgAygLMiwuYWdlbnQudjEuUmVxdWVzdGVkTW9kZWxfTW9kZWxQYXJhbWV0ZXJieXRlcxI6ChNhcGlfa2V5X2NyZWRlbnRpYWxzGAQgASgLMhsuYWdlbnQudjEuQXBpS2V5Q3JlZGVudGlhbHNIABI3ChFhenVyZV9jcmVkZW50aWFscxgFIAEoCzIaLmFnZW50LnYxLkF6dXJlQ3JlZGVudGlhbHNIABI7ChNiZWRyb2NrX2NyZWRlbnRpYWxzGAYgASgLMhwuYWdlbnQudjEuQmVkcm9ja0NyZWRlbnRpYWxzSABCDQoLY3JlZGVudGlhbHMiPwoiUmVxdWVzdGVkTW9kZWxfTW9kZWxQYXJhbWV0ZXJieXRlcxIKCgJpZBgBIAEoCRINCgV2YWx1ZRgCIAEoCSK5BAoPQWdlbnRSdW5SZXF1ZXN0EkAKEmNvbnZlcnNhdGlvbl9zdGF0ZRgBIAEoCzIkLmFnZW50LnYxLkNvbnZlcnNhdGlvblN0YXRlU3RydWN0dXJlEiwKBmFjdGlvbhgCIAEoCzIcLmFnZW50LnYxLkNvbnZlcnNhdGlvbkFjdGlvbhItCg1tb2RlbF9kZXRhaWxzGAMgASgLMhYuYWdlbnQudjEuTW9kZWxEZXRhaWxzEjYKD3JlcXVlc3RlZF9tb2RlbBgJIAEoCzIYLmFnZW50LnYxLlJlcXVlc3RlZE1vZGVsSACIAQESJQoJbWNwX3Rvb2xzGAQgASgLMhIuYWdlbnQudjEuTWNwVG9vbHMSHAoPY29udmVyc2F0aW9uX2lkGAUgASgJSAGIAQESRAoXbWNwX2ZpbGVfc3lzdGVtX29wdGlvbnMYBiABKAsyHi5hZ2VudC52MS5NY3BGaWxlU3lzdGVtT3B0aW9uc0gCiAEBEjIKDXNraWxsX29wdGlvbnMYByABKAsyFi5hZ2VudC52MS5Ta2lsbE9wdGlvbnNIA4gBARIhChRjdXN0b21fc3lzdGVtX3Byb21wdBgIIAEoCUgEiAEBQhIKEF9yZXF1ZXN0ZWRfbW9kZWxCEgoQX2NvbnZlcnNhdGlvbl9pZEIaChhfbWNwX2ZpbGVfc3lzdGVtX29wdGlvbnNCEAoOX3NraWxsX29wdGlvbnNCFwoVX2N1c3RvbV9zeXN0ZW1fcHJvbXB0Ih8KD1RleHREZWx0YVVwZGF0ZRIMCgR0ZXh0GAEgASgJImYKFVRvb2xDYWxsU3RhcnRlZFVwZGF0ZRIPCgdjYWxsX2lkGAEgASgJEiUKCXRvb2xfY2FsbBgCIAEoCzISLmFnZW50LnYxLlRvb2xDYWxsEhUKDW1vZGVsX2NhbGxfaWQYAyABKAkiaAoXVG9vbENhbGxDb21wbGV0ZWRVcGRhdGUSDwoHY2FsbF9pZBgBIAEoCRIlCgl0b29sX2NhbGwYAiABKAsyEi5hZ2VudC52MS5Ub29sQ2FsbBIVCg1tb2RlbF9jYWxsX2lkGAMgASgJIm8KE1Rvb2xDYWxsRGVsdGFVcGRhdGUSDwoHY2FsbF9pZBgBIAEoCRIwCg90b29sX2NhbGxfZGVsdGEYAiABKAsyFy5hZ2VudC52MS5Ub29sQ2FsbERlbHRhEhUKDW1vZGVsX2NhbGxfaWQYAyABKAkifwoVUGFydGlhbFRvb2xDYWxsVXBkYXRlEg8KB2NhbGxfaWQYASABKAkSJQoJdG9vbF9jYWxsGAIgASgLMhIuYWdlbnQudjEuVG9vbENhbGwSFwoPYXJnc190ZXh0X2RlbHRhGAMgASgJEhUKDW1vZGVsX2NhbGxfaWQYBCABKAkiIwoTVGhpbmtpbmdEZWx0YVVwZGF0ZRIMCgR0ZXh0GAEgASgJIjcKF1RoaW5raW5nQ29tcGxldGVkVXBkYXRlEhwKFHRoaW5raW5nX2R1cmF0aW9uX21zGAEgASgFIiIKEFRva2VuRGVsdGFVcGRhdGUSDgoGdG9rZW5zGAEgASgFIiAKDVN1bW1hcnlVcGRhdGUSDwoHc3VtbWFyeRgBIAEoCSIWChRTdW1tYXJ5U3RhcnRlZFVwZGF0ZSIRCg9IZWFydGJlYXRVcGRhdGUiGAoWU3VtbWFyeUNvbXBsZXRlZFVwZGF0ZSLXAQoWU2hlbGxPdXRwdXREZWx0YVVwZGF0ZRItCgZzdGRvdXQYASABKAsyGy5hZ2VudC52MS5TaGVsbFN0cmVhbVN0ZG91dEgAEi0KBnN0ZGVychgCIAEoCzIbLmFnZW50LnYxLlNoZWxsU3RyZWFtU3RkZXJySAASKQoEZXhpdBgDIAEoCzIZLmFnZW50LnYxLlNoZWxsU3RyZWFtRXhpdEgAEisKBXN0YXJ0GAQgASgLMhouYWdlbnQudjEuU2hlbGxTdHJlYW1TdGFydEgAQgcKBWV2ZW50IhEKD1R1cm5FbmRlZFVwZGF0ZSJIChlVc2VyTWVzc2FnZUFwcGVuZGVkVXBkYXRlEisKDHVzZXJfbWVzc2FnZRgBIAEoCzIVLmFnZW50LnYxLlVzZXJNZXNzYWdlIiQKEVN0ZXBTdGFydGVkVXBkYXRlEg8KB3N0ZXBfaWQYASABKAQiQAoTU3RlcENvbXBsZXRlZFVwZGF0ZRIPCgdzdGVwX2lkGAEgASgEEhgKEHN0ZXBfZHVyYXRpb25fbXMYAiABKAMi7wcKEUludGVyYWN0aW9uVXBkYXRlEi8KCnRleHRfZGVsdGEYASABKAsyGS5hZ2VudC52MS5UZXh0RGVsdGFVcGRhdGVIABI8ChFwYXJ0aWFsX3Rvb2xfY2FsbBgHIAEoCzIfLmFnZW50LnYxLlBhcnRpYWxUb29sQ2FsbFVwZGF0ZUgAEjgKD3Rvb2xfY2FsbF9kZWx0YRgPIAEoCzIdLmFnZW50LnYxLlRvb2xDYWxsRGVsdGFVcGRhdGVIABI8ChF0b29sX2NhbGxfc3RhcnRlZBgCIAEoCzIfLmFnZW50LnYxLlRvb2xDYWxsU3RhcnRlZFVwZGF0ZUgAEkAKE3Rvb2xfY2FsbF9jb21wbGV0ZWQYAyABKAsyIS5hZ2VudC52MS5Ub29sQ2FsbENvbXBsZXRlZFVwZGF0ZUgAEjcKDnRoaW5raW5nX2RlbHRhGAQgASgLMh0uYWdlbnQudjEuVGhpbmtpbmdEZWx0YVVwZGF0ZUgAEj8KEnRoaW5raW5nX2NvbXBsZXRlZBgFIAEoCzIhLmFnZW50LnYxLlRoaW5raW5nQ29tcGxldGVkVXBkYXRlSAASRAoVdXNlcl9tZXNzYWdlX2FwcGVuZGVkGAYgASgLMiMuYWdlbnQudjEuVXNlck1lc3NhZ2VBcHBlbmRlZFVwZGF0ZUgAEjEKC3Rva2VuX2RlbHRhGAggASgLMhouYWdlbnQudjEuVG9rZW5EZWx0YVVwZGF0ZUgAEioKB3N1bW1hcnkYCSABKAsyFy5hZ2VudC52MS5TdW1tYXJ5VXBkYXRlSAASOQoPc3VtbWFyeV9zdGFydGVkGAogASgLMh4uYWdlbnQudjEuU3VtbWFyeVN0YXJ0ZWRVcGRhdGVIABI9ChFzdW1tYXJ5X2NvbXBsZXRlZBgLIAEoCzIgLmFnZW50LnYxLlN1bW1hcnlDb21wbGV0ZWRVcGRhdGVIABI+ChJzaGVsbF9vdXRwdXRfZGVsdGEYDCABKAsyIC5hZ2VudC52MS5TaGVsbE91dHB1dERlbHRhVXBkYXRlSAASLgoJaGVhcnRiZWF0GA0gASgLMhkuYWdlbnQudjEuSGVhcnRiZWF0VXBkYXRlSAASLwoKdHVybl9lbmRlZBgOIAEoCzIZLmFnZW50LnYxLlR1cm5FbmRlZFVwZGF0ZUgAEjMKDHN0ZXBfc3RhcnRlZBgQIAEoCzIbLmFnZW50LnYxLlN0ZXBTdGFydGVkVXBkYXRlSAASNwoOc3RlcF9jb21wbGV0ZWQYESABKAsyHS5hZ2VudC52MS5TdGVwQ29tcGxldGVkVXBkYXRlSABCCQoHbWVzc2FnZSKaBAoQSW50ZXJhY3Rpb25RdWVyeRIKCgJpZBgBIAEoDRJDChh3ZWJfc2VhcmNoX3JlcXVlc3RfcXVlcnkYAiABKAsyHy5hZ2VudC52MS5XZWJTZWFyY2hSZXF1ZXN0UXVlcnlIABJPCh5hc2tfcXVlc3Rpb25faW50ZXJhY3Rpb25fcXVlcnkYAyABKAsyJS5hZ2VudC52MS5Bc2tRdWVzdGlvbkludGVyYWN0aW9uUXVlcnlIABJFChlzd2l0Y2hfbW9kZV9yZXF1ZXN0X3F1ZXJ5GAQgASgLMiAuYWdlbnQudjEuU3dpdGNoTW9kZVJlcXVlc3RRdWVyeUgAEkMKGGV4YV9zZWFyY2hfcmVxdWVzdF9xdWVyeRgFIAEoCzIfLmFnZW50LnYxLkV4YVNlYXJjaFJlcXVlc3RRdWVyeUgAEkEKF2V4YV9mZXRjaF9yZXF1ZXN0X3F1ZXJ5GAYgASgLMh4uYWdlbnQudjEuRXhhRmV0Y2hSZXF1ZXN0UXVlcnlIABJFChljcmVhdGVfcGxhbl9yZXF1ZXN0X3F1ZXJ5GAcgASgLMiAuYWdlbnQudjEuQ3JlYXRlUGxhblJlcXVlc3RRdWVyeUgAEkUKGXNldHVwX3ZtX2Vudmlyb25tZW50X2FyZ3MYCCABKAsyIC5hZ2VudC52MS5TZXR1cFZtRW52aXJvbm1lbnRBcmdzSABCBwoFcXVlcnkixgQKE0ludGVyYWN0aW9uUmVzcG9uc2USCgoCaWQYASABKA0SSQobd2ViX3NlYXJjaF9yZXF1ZXN0X3Jlc3BvbnNlGAIgASgLMiIuYWdlbnQudjEuV2ViU2VhcmNoUmVxdWVzdFJlc3BvbnNlSAASVQohYXNrX3F1ZXN0aW9uX2ludGVyYWN0aW9uX3Jlc3BvbnNlGAMgASgLMiguYWdlbnQudjEuQXNrUXVlc3Rpb25JbnRlcmFjdGlvblJlc3BvbnNlSAASSwocc3dpdGNoX21vZGVfcmVxdWVzdF9yZXNwb25zZRgEIAEoCzIjLmFnZW50LnYxLlN3aXRjaE1vZGVSZXF1ZXN0UmVzcG9uc2VIABJJChtleGFfc2VhcmNoX3JlcXVlc3RfcmVzcG9uc2UYBSABKAsyIi5hZ2VudC52MS5FeGFTZWFyY2hSZXF1ZXN0UmVzcG9uc2VIABJHChpleGFfZmV0Y2hfcmVxdWVzdF9yZXNwb25zZRgGIAEoCzIhLmFnZW50LnYxLkV4YUZldGNoUmVxdWVzdFJlc3BvbnNlSAASSwocY3JlYXRlX3BsYW5fcmVxdWVzdF9yZXNwb25zZRgHIAEoCzIjLmFnZW50LnYxLkNyZWF0ZVBsYW5SZXF1ZXN0UmVzcG9uc2VIABJJChtzZXR1cF92bV9lbnZpcm9ubWVudF9yZXN1bHQYCCABKAsyIi5hZ2VudC52MS5TZXR1cFZtRW52aXJvbm1lbnRSZXN1bHRIAEIICgZyZXN1bHQiXAobQXNrUXVlc3Rpb25JbnRlcmFjdGlvblF1ZXJ5EicKBGFyZ3MYASABKAsyGS5hZ2VudC52MS5Bc2tRdWVzdGlvbkFyZ3MSFAoMdG9vbF9jYWxsX2lkGAIgASgJIk0KHkFza1F1ZXN0aW9uSW50ZXJhY3Rpb25SZXNwb25zZRIrCgZyZXN1bHQYASABKAsyGy5hZ2VudC52MS5Bc2tRdWVzdGlvblJlc3VsdCIRCg9DbGllbnRIZWFydGJlYXQixgQKDlByZXdhcm1SZXF1ZXN0Ei0KDW1vZGVsX2RldGFpbHMYASABKAsyFi5hZ2VudC52MS5Nb2RlbERldGFpbHMSNgoPcmVxdWVzdGVkX21vZGVsGAkgASgLMhguYWdlbnQudjEuUmVxdWVzdGVkTW9kZWxIAIgBARIcCg9jb252ZXJzYXRpb25faWQYAiABKAlIAYgBARJAChJjb252ZXJzYXRpb25fc3RhdGUYAyABKAsyJC5hZ2VudC52MS5Db252ZXJzYXRpb25TdGF0ZVN0cnVjdHVyZRIlCgltY3BfdG9vbHMYBCABKAsyEi5hZ2VudC52MS5NY3BUb29scxJEChdtY3BfZmlsZV9zeXN0ZW1fb3B0aW9ucxgFIAEoCzIeLmFnZW50LnYxLk1jcEZpbGVTeXN0ZW1PcHRpb25zSAKIAQESHwoSYmVzdF9vZl9uX2dyb3VwX2lkGAYgASgJSAOIAQESKAobdHJ5X3VzZV9iZXN0X29mX25fcHJvbW90aW9uGAcgASgISASIAQESIQoUY3VzdG9tX3N5c3RlbV9wcm9tcHQYCCABKAlIBYgBAUISChBfcmVxdWVzdGVkX21vZGVsQhIKEF9jb252ZXJzYXRpb25faWRCGgoYX21jcF9maWxlX3N5c3RlbV9vcHRpb25zQhUKE19iZXN0X29mX25fZ3JvdXBfaWRCHgocX3RyeV91c2VfYmVzdF9vZl9uX3Byb21vdGlvbkIXChVfY3VzdG9tX3N5c3RlbV9wcm9tcHQiHQoPRXhlY1NlcnZlckFib3J0EgoKAmlkGAEgASgNIlEKGEV4ZWNTZXJ2ZXJDb250cm9sTWVzc2FnZRIqCgVhYm9ydBgBIAEoCzIZLmFnZW50LnYxLkV4ZWNTZXJ2ZXJBYm9ydEgAQgkKB21lc3NhZ2Ui+AMKEkFnZW50Q2xpZW50TWVzc2FnZRIwCgtydW5fcmVxdWVzdBgBIAEoCzIZLmFnZW50LnYxLkFnZW50UnVuUmVxdWVzdEgAEjoKE2V4ZWNfY2xpZW50X21lc3NhZ2UYAiABKAsyGy5hZ2VudC52MS5FeGVjQ2xpZW50TWVzc2FnZUgAEkkKG2V4ZWNfY2xpZW50X2NvbnRyb2xfbWVzc2FnZRgFIAEoCzIiLmFnZW50LnYxLkV4ZWNDbGllbnRDb250cm9sTWVzc2FnZUgAEjYKEWt2X2NsaWVudF9tZXNzYWdlGAMgASgLMhkuYWdlbnQudjEuS3ZDbGllbnRNZXNzYWdlSAASOwoTY29udmVyc2F0aW9uX2FjdGlvbhgEIAEoCzIcLmFnZW50LnYxLkNvbnZlcnNhdGlvbkFjdGlvbkgAEj0KFGludGVyYWN0aW9uX3Jlc3BvbnNlGAYgASgLMh0uYWdlbnQudjEuSW50ZXJhY3Rpb25SZXNwb25zZUgAEjUKEGNsaWVudF9oZWFydGJlYXQYByABKAsyGS5hZ2VudC52MS5DbGllbnRIZWFydGJlYXRIABIzCg9wcmV3YXJtX3JlcXVlc3QYCCABKAsyGC5hZ2VudC52MS5QcmV3YXJtUmVxdWVzdEgAQgkKB21lc3NhZ2UiogMKEkFnZW50U2VydmVyTWVzc2FnZRI5ChJpbnRlcmFjdGlvbl91cGRhdGUYASABKAsyGy5hZ2VudC52MS5JbnRlcmFjdGlvblVwZGF0ZUgAEjoKE2V4ZWNfc2VydmVyX21lc3NhZ2UYAiABKAsyGy5hZ2VudC52MS5FeGVjU2VydmVyTWVzc2FnZUgAEkkKG2V4ZWNfc2VydmVyX2NvbnRyb2xfbWVzc2FnZRgFIAEoCzIiLmFnZW50LnYxLkV4ZWNTZXJ2ZXJDb250cm9sTWVzc2FnZUgAEk4KHmNvbnZlcnNhdGlvbl9jaGVja3BvaW50X3VwZGF0ZRgDIAEoCzIkLmFnZW50LnYxLkNvbnZlcnNhdGlvblN0YXRlU3RydWN0dXJlSAASNgoRa3Zfc2VydmVyX21lc3NhZ2UYBCABKAsyGS5hZ2VudC52MS5LdlNlcnZlck1lc3NhZ2VIABI3ChFpbnRlcmFjdGlvbl9xdWVyeRgHIAEoCzIaLmFnZW50LnYxLkludGVyYWN0aW9uUXVlcnlIAEIJCgdtZXNzYWdlIigKEE5hbWVBZ2VudFJlcXVlc3QSFAoMdXNlcl9tZXNzYWdlGAEgASgJIiEKEU5hbWVBZ2VudFJlc3BvbnNlEgwKBG5hbWUYASABKAkiMgoWR2V0VXNhYmxlTW9kZWxzUmVxdWVzdBIYChBjdXN0b21fbW9kZWxfaWRzGAEgAygJIkEKF0dldFVzYWJsZU1vZGVsc1Jlc3BvbnNlEiYKBm1vZGVscxgBIAMoCzIWLmFnZW50LnYxLk1vZGVsRGV0YWlscyIeChxHZXREZWZhdWx0TW9kZWxGb3JDbGlSZXF1ZXN0IkYKHUdldERlZmF1bHRNb2RlbEZvckNsaVJlc3BvbnNlEiUKBW1vZGVsGAEgASgLMhYuYWdlbnQudjEuTW9kZWxEZXRhaWxzIh8KHUdldEFsbG93ZWRNb2RlbEludGVudHNSZXF1ZXN0IjcKHkdldEFsbG93ZWRNb2RlbEludGVudHNSZXNwb25zZRIVCg1tb2RlbF9pbnRlbnRzGAEgAygJIpcCChNJZGVFZGl0b3JzU3RhdGVGaWxlEhUKDXJlbGF0aXZlX3BhdGgYASABKAkSFQoNYWJzb2x1dGVfcGF0aBgCIAEoCRIhChRpc19jdXJyZW50bHlfZm9jdXNlZBgDIAEoCEgAiAEBEiAKE2N1cnJlbnRfbGluZV9udW1iZXIYBCABKAVIAYgBARIeChFjdXJyZW50X2xpbmVfdGV4dBgFIAEoCUgCiAEBEhcKCmxpbmVfY291bnQYBiABKAVIA4gBAUIXChVfaXNfY3VycmVudGx5X2ZvY3VzZWRCFgoUX2N1cnJlbnRfbGluZV9udW1iZXJCFAoSX2N1cnJlbnRfbGluZV90ZXh0Qg0KC19saW5lX2NvdW50IlMKE0lkZUVkaXRvcnNTdGF0ZUxpdGUSPAoVcmVjZW50bHlfdmlld2VkX2ZpbGVzGAEgAygLMh0uYWdlbnQudjEuSWRlRWRpdG9yc1N0YXRlRmlsZSJ0ChZBcHBseUFnZW50RGlmZlRvb2xDYWxsEioKBGFyZ3MYASABKAsyHC5hZ2VudC52MS5BcHBseUFnZW50RGlmZkFyZ3MSLgoGcmVzdWx0GAIgASgLMh4uYWdlbnQudjEuQXBwbHlBZ2VudERpZmZSZXN1bHQiJgoSQXBwbHlBZ2VudERpZmZBcmdzEhAKCGFnZW50X2lkGAEgASgJIoQBChRBcHBseUFnZW50RGlmZlJlc3VsdBIyCgdzdWNjZXNzGAEgASgLMh8uYWdlbnQudjEuQXBwbHlBZ2VudERpZmZTdWNjZXNzSAASLgoFZXJyb3IYAiABKAsyHS5hZ2VudC52MS5BcHBseUFnZW50RGlmZkVycm9ySABCCAoGcmVzdWx0Ik4KFUFwcGx5QWdlbnREaWZmU3VjY2VzcxI1Cg9hcHBsaWVkX2NoYW5nZXMYASADKAsyHC5hZ2VudC52MS5BcHBsaWVkQWdlbnRDaGFuZ2Ui6QEKEkFwcGxpZWRBZ2VudENoYW5nZRIMCgRwYXRoGAEgASgJEhMKC2NoYW5nZV90eXBlGAIgASgFEhsKDmJlZm9yZV9jb250ZW50GAMgASgJSACIAQESGgoNYWZ0ZXJfY29udGVudBgEIAEoCUgBiAEBEhIKBWVycm9yGAUgASgJSAKIAQESHgoRbWVzc2FnZV9mb3JfbW9kZWwYBiABKAlIA4gBAUIRCg9fYmVmb3JlX2NvbnRlbnRCEAoOX2FmdGVyX2NvbnRlbnRCCAoGX2Vycm9yQhQKEl9tZXNzYWdlX2Zvcl9tb2RlbCJbChNBcHBseUFnZW50RGlmZkVycm9yEg0KBWVycm9yGAEgASgJEjUKD2FwcGxpZWRfY2hhbmdlcxgCIAMoCzIcLmFnZW50LnYxLkFwcGxpZWRBZ2VudENoYW5nZSJrChNBc2tRdWVzdGlvblRvb2xDYWxsEicKBGFyZ3MYASABKAsyGS5hZ2VudC52MS5Bc2tRdWVzdGlvbkFyZ3MSKwoGcmVzdWx0GAIgASgLMhsuYWdlbnQudjEuQXNrUXVlc3Rpb25SZXN1bHQijwEKD0Fza1F1ZXN0aW9uQXJncxINCgV0aXRsZRgBIAEoCRI1CglxdWVzdGlvbnMYAiADKAsyIi5hZ2VudC52MS5Bc2tRdWVzdGlvbkFyZ3NfUXVlc3Rpb24SEQoJcnVuX2FzeW5jGAUgASgIEiMKG2FzeW5jX29yaWdpbmFsX3Rvb2xfY2FsbF9pZBgGIAEoCSKBAQoYQXNrUXVlc3Rpb25BcmdzX1F1ZXN0aW9uEgoKAmlkGAEgASgJEg4KBnByb21wdBgCIAEoCRIxCgdvcHRpb25zGAMgAygLMiAuYWdlbnQudjEuQXNrUXVlc3Rpb25BcmdzX09wdGlvbhIWCg5hbGxvd19tdWx0aXBsZRgEIAEoCCIzChZBc2tRdWVzdGlvbkFyZ3NfT3B0aW9uEgoKAmlkGAEgASgJEg0KBWxhYmVsGAIgASgJIhIKEEFza1F1ZXN0aW9uQXN5bmMi2wEKEUFza1F1ZXN0aW9uUmVzdWx0Ei8KB3N1Y2Nlc3MYASABKAsyHC5hZ2VudC52MS5Bc2tRdWVzdGlvblN1Y2Nlc3NIABIrCgVlcnJvchgCIAEoCzIaLmFnZW50LnYxLkFza1F1ZXN0aW9uRXJyb3JIABIxCghyZWplY3RlZBgDIAEoCzIdLmFnZW50LnYxLkFza1F1ZXN0aW9uUmVqZWN0ZWRIABIrCgVhc3luYxgEIAEoCzIaLmFnZW50LnYxLkFza1F1ZXN0aW9uQXN5bmNIAEIICgZyZXN1bHQiSgoSQXNrUXVlc3Rpb25TdWNjZXNzEjQKB2Fuc3dlcnMYASADKAsyIy5hZ2VudC52MS5Bc2tRdWVzdGlvblN1Y2Nlc3NfQW5zd2VyIk0KGUFza1F1ZXN0aW9uU3VjY2Vzc19BbnN3ZXISEwoLcXVlc3Rpb25faWQYASABKAkSGwoTc2VsZWN0ZWRfb3B0aW9uX2lkcxgCIAMoCSIpChBBc2tRdWVzdGlvbkVycm9yEhUKDWVycm9yX21lc3NhZ2UYASABKAkiJQoTQXNrUXVlc3Rpb25SZWplY3RlZBIOCgZyZWFzb24YASABKAkiiQIKGEJhY2tncm91bmRTaGVsbFNwYXduQXJncxIPCgdjb21tYW5kGAEgASgJEhkKEXdvcmtpbmdfZGlyZWN0b3J5GAIgASgJEhQKDHRvb2xfY2FsbF9pZBgDIAEoCRI7Cg5wYXJzaW5nX3Jlc3VsdBgEIAEoCzIjLmFnZW50LnYxLlNoZWxsQ29tbWFuZFBhcnNpbmdSZXN1bHQSNAoOc2FuZGJveF9wb2xpY3kYBSABKAsyFy5hZ2VudC52MS5TYW5kYm94UG9saWN5SACIAQESJQodZW5hYmxlX3dyaXRlX3NoZWxsX3N0ZGluX3Rvb2wYBiABKAhCEQoPX3NhbmRib3hfcG9saWN5IoECChpCYWNrZ3JvdW5kU2hlbGxTcGF3blJlc3VsdBI4CgdzdWNjZXNzGAEgASgLMiUuYWdlbnQudjEuQmFja2dyb3VuZFNoZWxsU3Bhd25TdWNjZXNzSAASNAoFZXJyb3IYAiABKAsyIy5hZ2VudC52MS5CYWNrZ3JvdW5kU2hlbGxTcGF3bkVycm9ySAASKwoIcmVqZWN0ZWQYAyABKAsyFy5hZ2VudC52MS5TaGVsbFJlamVjdGVkSAASPAoRcGVybWlzc2lvbl9kZW5pZWQYBCABKAsyHy5hZ2VudC52MS5TaGVsbFBlcm1pc3Npb25EZW5pZWRIAEIICgZyZXN1bHQidQobQmFja2dyb3VuZFNoZWxsU3Bhd25TdWNjZXNzEhAKCHNoZWxsX2lkGAEgASgNEg8KB2NvbW1hbmQYAiABKAkSGQoRd29ya2luZ19kaXJlY3RvcnkYAyABKAkSEAoDcGlkGAQgASgNSACIAQFCBgoEX3BpZCJWChlCYWNrZ3JvdW5kU2hlbGxTcGF3bkVycm9yEg8KB2NvbW1hbmQYASABKAkSGQoRd29ya2luZ19kaXJlY3RvcnkYAiABKAkSDQoFZXJyb3IYAyABKAkiNgoTV3JpdGVTaGVsbFN0ZGluQXJncxIQCghzaGVsbF9pZBgBIAEoDRINCgVjaGFycxgCIAEoCSKHAQoVV3JpdGVTaGVsbFN0ZGluUmVzdWx0EjMKB3N1Y2Nlc3MYASABKAsyIC5hZ2VudC52MS5Xcml0ZVNoZWxsU3RkaW5TdWNjZXNzSAASLwoFZXJyb3IYAiABKAsyHi5hZ2VudC52MS5Xcml0ZVNoZWxsU3RkaW5FcnJvckgAQggKBnJlc3VsdCJdChZXcml0ZVNoZWxsU3RkaW5TdWNjZXNzEhAKCHNoZWxsX2lkGAEgASgNEjEKKXRlcm1pbmFsX2ZpbGVfbGVuZ3RoX2JlZm9yZV9pbnB1dF93cml0dGVuGAIgASgNIiUKFFdyaXRlU2hlbGxTdGRpbkVycm9yEg0KBWVycm9yGAEgASgJIiIKCkNvb3JkaW5hdGUSCQoBeBgBIAEoBRIJCgF5GAIgASgFIlUKD0NvbXB1dGVyVXNlQXJncxIUCgx0b29sX2NhbGxfaWQYASABKAkSLAoHYWN0aW9ucxgCIAMoCzIbLmFnZW50LnYxLkNvbXB1dGVyVXNlQWN0aW9uIoEEChFDb21wdXRlclVzZUFjdGlvbhIvCgptb3VzZV9tb3ZlGAEgASgLMhkuYWdlbnQudjEuTW91c2VNb3ZlQWN0aW9uSAASJgoFY2xpY2sYAiABKAsyFS5hZ2VudC52MS5DbGlja0FjdGlvbkgAEi8KCm1vdXNlX2Rvd24YAyABKAsyGS5hZ2VudC52MS5Nb3VzZURvd25BY3Rpb25IABIrCghtb3VzZV91cBgEIAEoCzIXLmFnZW50LnYxLk1vdXNlVXBBY3Rpb25IABIkCgRkcmFnGAUgASgLMhQuYWdlbnQudjEuRHJhZ0FjdGlvbkgAEigKBnNjcm9sbBgGIAEoCzIWLmFnZW50LnYxLlNjcm9sbEFjdGlvbkgAEiQKBHR5cGUYByABKAsyFC5hZ2VudC52MS5UeXBlQWN0aW9uSAASIgoDa2V5GAggASgLMhMuYWdlbnQudjEuS2V5QWN0aW9uSAASJAoEd2FpdBgJIAEoCzIULmFnZW50LnYxLldhaXRBY3Rpb25IABIwCgpzY3JlZW5zaG90GAogASgLMhouYWdlbnQudjEuU2NyZWVuc2hvdEFjdGlvbkgAEjkKD2N1cnNvcl9wb3NpdGlvbhgLIAEoCzIeLmFnZW50LnYxLkN1cnNvclBvc2l0aW9uQWN0aW9uSABCCAoGYWN0aW9uIjsKD01vdXNlTW92ZUFjdGlvbhIoCgpjb29yZGluYXRlGAEgASgLMhQuYWdlbnQudjEuQ29vcmRpbmF0ZSKYAQoLQ2xpY2tBY3Rpb24SLQoKY29vcmRpbmF0ZRgBIAEoCzIULmFnZW50LnYxLkNvb3JkaW5hdGVIAIgBARIOCgZidXR0b24YAiABKAUSDQoFY291bnQYAyABKAUSGgoNbW9kaWZpZXJfa2V5cxgEIAEoCUgBiAEBQg0KC19jb29yZGluYXRlQhAKDl9tb2RpZmllcl9rZXlzIiEKD01vdXNlRG93bkFjdGlvbhIOCgZidXR0b24YASABKAUiHwoNTW91c2VVcEFjdGlvbhIOCgZidXR0b24YASABKAUiQAoKRHJhZ0FjdGlvbhIiCgRwYXRoGAEgAygLMhQuYWdlbnQudjEuQ29vcmRpbmF0ZRIOCgZidXR0b24YAiABKAUinQEKDFNjcm9sbEFjdGlvbhItCgpjb29yZGluYXRlGAEgASgLMhQuYWdlbnQudjEuQ29vcmRpbmF0ZUgAiAEBEhEKCWRpcmVjdGlvbhgCIAEoBRIOCgZhbW91bnQYAyABKAUSGgoNbW9kaWZpZXJfa2V5cxgEIAEoCUgBiAEBQg0KC19jb29yZGluYXRlQhAKDl9tb2RpZmllcl9rZXlzIhoKClR5cGVBY3Rpb24SDAoEdGV4dBgBIAEoCSJMCglLZXlBY3Rpb24SCwoDa2V5GAEgASgJEh0KEGhvbGRfZHVyYXRpb25fbXMYAiABKAVIAIgBAUITChFfaG9sZF9kdXJhdGlvbl9tcyIhCgpXYWl0QWN0aW9uEhMKC2R1cmF0aW9uX21zGAEgASgFIhIKEFNjcmVlbnNob3RBY3Rpb24iFgoUQ3Vyc29yUG9zaXRpb25BY3Rpb24iewoRQ29tcHV0ZXJVc2VSZXN1bHQSLwoHc3VjY2VzcxgBIAEoCzIcLmFnZW50LnYxLkNvbXB1dGVyVXNlU3VjY2Vzc0gAEisKBWVycm9yGAIgASgLMhouYWdlbnQudjEuQ29tcHV0ZXJVc2VFcnJvckgAQggKBnJlc3VsdCL7AQoSQ29tcHV0ZXJVc2VTdWNjZXNzEhQKDGFjdGlvbl9jb3VudBgBIAEoBRITCgtkdXJhdGlvbl9tcxgCIAEoBRIXCgpzY3JlZW5zaG90GAMgASgJSACIAQESEAoDbG9nGAQgASgJSAGIAQESHAoPc2NyZWVuc2hvdF9wYXRoGAUgASgJSAKIAQESMgoPY3Vyc29yX3Bvc2l0aW9uGAYgASgLMhQuYWdlbnQudjEuQ29vcmRpbmF0ZUgDiAEBQg0KC19zY3JlZW5zaG90QgYKBF9sb2dCEgoQX3NjcmVlbnNob3RfcGF0aEISChBfY3Vyc29yX3Bvc2l0aW9uIsABChBDb21wdXRlclVzZUVycm9yEg0KBWVycm9yGAEgASgJEhQKDGFjdGlvbl9jb3VudBgCIAEoBRITCgtkdXJhdGlvbl9tcxgDIAEoBRIQCgNsb2cYBCABKAlIAIgBARIXCgpzY3JlZW5zaG90GAUgASgJSAGIAQESHAoPc2NyZWVuc2hvdF9wYXRoGAYgASgJSAKIAQFCBgoEX2xvZ0INCgtfc2NyZWVuc2hvdEISChBfc2NyZWVuc2hvdF9wYXRoImsKE0NvbXB1dGVyVXNlVG9vbENhbGwSJwoEYXJncxgBIAEoCzIZLmFnZW50LnYxLkNvbXB1dGVyVXNlQXJncxIrCgZyZXN1bHQYAiABKAsyGy5hZ2VudC52MS5Db21wdXRlclVzZVJlc3VsdCJoChJDcmVhdGVQbGFuVG9vbENhbGwSJgoEYXJncxgBIAEoCzIYLmFnZW50LnYxLkNyZWF0ZVBsYW5BcmdzEioKBnJlc3VsdBgCIAEoCzIaLmFnZW50LnYxLkNyZWF0ZVBsYW5SZXN1bHQiOAoFUGhhc2USDAoEbmFtZRgBIAEoCRIhCgV0b2RvcxgCIAMoCzISLmFnZW50LnYxLlRvZG9JdGVtIpYBCg5DcmVhdGVQbGFuQXJncxIMCgRwbGFuGAEgASgJEiEKBXRvZG9zGAIgAygLMhIuYWdlbnQudjEuVG9kb0l0ZW0SEAoIb3ZlcnZpZXcYAyABKAkSDAoEbmFtZRgEIAEoCRISCgppc19wcm9qZWN0GAUgASgIEh8KBnBoYXNlcxgGIAMoCzIPLmFnZW50LnYxLlBoYXNlIooBChBDcmVhdGVQbGFuUmVzdWx0EhAKCHBsYW5fdXJpGAMgASgJEi4KB3N1Y2Nlc3MYASABKAsyGy5hZ2VudC52MS5DcmVhdGVQbGFuU3VjY2Vzc0gAEioKBWVycm9yGAIgASgLMhkuYWdlbnQudjEuQ3JlYXRlUGxhbkVycm9ySABCCAoGcmVzdWx0IhMKEUNyZWF0ZVBsYW5TdWNjZXNzIiAKD0NyZWF0ZVBsYW5FcnJvchINCgVlcnJvchgBIAEoCSJWChZDcmVhdGVQbGFuUmVxdWVzdFF1ZXJ5EiYKBGFyZ3MYASABKAsyGC5hZ2VudC52MS5DcmVhdGVQbGFuQXJncxIUCgx0b29sX2NhbGxfaWQYAiABKAkiRwoZQ3JlYXRlUGxhblJlcXVlc3RSZXNwb25zZRIqCgZyZXN1bHQYASABKAsyGi5hZ2VudC52MS5DcmVhdGVQbGFuUmVzdWx0IhYKFEN1cnNvclJ1bGVUeXBlR2xvYmFsIigKF0N1cnNvclJ1bGVUeXBlRmlsZUdsb2JzEg0KBWdsb2JzGAEgAygJIjEKGkN1cnNvclJ1bGVUeXBlQWdlbnRGZXRjaGVkEhMKC2Rlc2NyaXB0aW9uGAEgASgJIiAKHkN1cnNvclJ1bGVUeXBlTWFudWFsbHlBdHRhY2hlZCKLAgoOQ3Vyc29yUnVsZVR5cGUSMAoGZ2xvYmFsGAEgASgLMh4uYWdlbnQudjEuQ3Vyc29yUnVsZVR5cGVHbG9iYWxIABI5CgxmaWxlX2dsb2JiZWQYAiABKAsyIS5hZ2VudC52MS5DdXJzb3JSdWxlVHlwZUZpbGVHbG9ic0gAEj0KDWFnZW50X2ZldGNoZWQYAyABKAsyJC5hZ2VudC52MS5DdXJzb3JSdWxlVHlwZUFnZW50RmV0Y2hlZEgAEkUKEW1hbnVhbGx5X2F0dGFjaGVkGAQgASgLMiguYWdlbnQudjEuQ3Vyc29yUnVsZVR5cGVNYW51YWxseUF0dGFjaGVkSABCBgoEdHlwZSLIAQoKQ3Vyc29yUnVsZRIRCglmdWxsX3BhdGgYASABKAkSDwoHY29udGVudBgCIAEoCRImCgR0eXBlGAMgASgLMhguYWdlbnQudjEuQ3Vyc29yUnVsZVR5cGUSDgoGc291cmNlGAQgASgFEh4KEWdpdF9yZW1vdGVfb3JpZ2luGAUgASgJSACIAQESGAoLcGFyc2VfZXJyb3IYBiABKAlIAYgBAUIUChJfZ2l0X3JlbW90ZV9vcmlnaW5CDgoMX3BhcnNlX2Vycm9yIjAKCkRlbGV0ZUFyZ3MSDAoEcGF0aBgBIAEoCRIUCgx0b29sX2NhbGxfaWQYAiABKAki7QIKDERlbGV0ZVJlc3VsdBIqCgdzdWNjZXNzGAEgASgLMhcuYWdlbnQudjEuRGVsZXRlU3VjY2Vzc0gAEjYKDmZpbGVfbm90X2ZvdW5kGAIgASgLMhwuYWdlbnQudjEuRGVsZXRlRmlsZU5vdEZvdW5kSAASKwoIbm90X2ZpbGUYAyABKAsyFy5hZ2VudC52MS5EZWxldGVOb3RGaWxlSAASPQoRcGVybWlzc2lvbl9kZW5pZWQYBCABKAsyIC5hZ2VudC52MS5EZWxldGVQZXJtaXNzaW9uRGVuaWVkSAASLQoJZmlsZV9idXN5GAUgASgLMhguYWdlbnQudjEuRGVsZXRlRmlsZUJ1c3lIABIsCghyZWplY3RlZBgGIAEoCzIYLmFnZW50LnYxLkRlbGV0ZVJlamVjdGVkSAASJgoFZXJyb3IYByABKAsyFS5hZ2VudC52MS5EZWxldGVFcnJvckgAQggKBnJlc3VsdCJcCg1EZWxldGVTdWNjZXNzEgwKBHBhdGgYASABKAkSFAoMZGVsZXRlZF9maWxlGAIgASgJEhEKCWZpbGVfc2l6ZRgDIAEoAxIUCgxwcmV2X2NvbnRlbnQYBCABKAkiIgoSRGVsZXRlRmlsZU5vdEZvdW5kEgwKBHBhdGgYASABKAkiMgoNRGVsZXRlTm90RmlsZRIMCgRwYXRoGAEgASgJEhMKC2FjdHVhbF90eXBlGAIgASgJIlkKFkRlbGV0ZVBlcm1pc3Npb25EZW5pZWQSDAoEcGF0aBgBIAEoCRIcChRjbGllbnRfdmlzaWJsZV9lcnJvchgCIAEoCRITCgtpc19yZWFkb25seRgDIAEoCCIeCg5EZWxldGVGaWxlQnVzeRIMCgRwYXRoGAEgASgJIi4KDkRlbGV0ZVJlamVjdGVkEgwKBHBhdGgYASABKAkSDgoGcmVhc29uGAIgASgJIioKC0RlbGV0ZUVycm9yEgwKBHBhdGgYASABKAkSDQoFZXJyb3IYAiABKAkiXAoORGVsZXRlVG9vbENhbGwSIgoEYXJncxgBIAEoCzIULmFnZW50LnYxLkRlbGV0ZUFyZ3MSJgoGcmVzdWx0GAIgASgLMhYuYWdlbnQudjEuRGVsZXRlUmVzdWx0IjUKD0RpYWdub3N0aWNzQXJncxIMCgRwYXRoGAEgASgJEhQKDHRvb2xfY2FsbF9pZBgCIAEoCSKvAgoRRGlhZ25vc3RpY3NSZXN1bHQSLwoHc3VjY2VzcxgBIAEoCzIcLmFnZW50LnYxLkRpYWdub3N0aWNzU3VjY2Vzc0gAEisKBWVycm9yGAIgASgLMhouYWdlbnQudjEuRGlhZ25vc3RpY3NFcnJvckgAEjEKCHJlamVjdGVkGAMgASgLMh0uYWdlbnQudjEuRGlhZ25vc3RpY3NSZWplY3RlZEgAEjsKDmZpbGVfbm90X2ZvdW5kGAQgASgLMiEuYWdlbnQudjEuRGlhZ25vc3RpY3NGaWxlTm90Rm91bmRIABJCChFwZXJtaXNzaW9uX2RlbmllZBgFIAEoCzIlLmFnZW50LnYxLkRpYWdub3N0aWNzUGVybWlzc2lvbkRlbmllZEgAQggKBnJlc3VsdCJoChJEaWFnbm9zdGljc1N1Y2Nlc3MSDAoEcGF0aBgBIAEoCRIpCgtkaWFnbm9zdGljcxgCIAMoCzIULmFnZW50LnYxLkRpYWdub3N0aWMSGQoRdG90YWxfZGlhZ25vc3RpY3MYAyABKAUifwoKRGlhZ25vc3RpYxIQCghzZXZlcml0eRgBIAEoBRIeCgVyYW5nZRgCIAEoCzIPLmFnZW50LnYxLlJhbmdlEg8KB21lc3NhZ2UYAyABKAkSDgoGc291cmNlGAQgASgJEgwKBGNvZGUYBSABKAkSEAoIaXNfc3RhbGUYBiABKAgiLwoQRGlhZ25vc3RpY3NFcnJvchIMCgRwYXRoGAEgASgJEg0KBWVycm9yGAIgASgJIjMKE0RpYWdub3N0aWNzUmVqZWN0ZWQSDAoEcGF0aBgBIAEoCRIOCgZyZWFzb24YAiABKAkiJwoXRGlhZ25vc3RpY3NGaWxlTm90Rm91bmQSDAoEcGF0aBgBIAEoCSIrChtEaWFnbm9zdGljc1Blcm1pc3Npb25EZW5pZWQSDAoEcGF0aBgBIAEoCSJICghFZGl0QXJncxIMCgRwYXRoGAEgASgJEhsKDnN0cmVhbV9jb250ZW50GAYgASgJSACIAQFCEQoPX3N0cmVhbV9jb250ZW50ItYCCgpFZGl0UmVzdWx0EigKB3N1Y2Nlc3MYASABKAsyFS5hZ2VudC52MS5FZGl0U3VjY2Vzc0gAEjQKDmZpbGVfbm90X2ZvdW5kGAIgASgLMhouYWdlbnQudjEuRWRpdEZpbGVOb3RGb3VuZEgAEkQKFnJlYWRfcGVybWlzc2lvbl9kZW5pZWQYAyABKAsyIi5hZ2VudC52MS5FZGl0UmVhZFBlcm1pc3Npb25EZW5pZWRIABJGChd3cml0ZV9wZXJtaXNzaW9uX2RlbmllZBgEIAEoCzIjLmFnZW50LnYxLkVkaXRXcml0ZVBlcm1pc3Npb25EZW5pZWRIABIqCghyZWplY3RlZBgGIAEoCzIWLmFnZW50LnYxLkVkaXRSZWplY3RlZEgAEiQKBWVycm9yGAcgASgLMhMuYWdlbnQudjEuRWRpdEVycm9ySABCCAoGcmVzdWx0IqQCCgtFZGl0U3VjY2VzcxIMCgRwYXRoGAEgASgJEhgKC2xpbmVzX2FkZGVkGAMgASgFSACIAQESGgoNbGluZXNfcmVtb3ZlZBgEIAEoBUgBiAEBEhgKC2RpZmZfc3RyaW5nGAUgASgJSAKIAQESJQoYYmVmb3JlX2Z1bGxfZmlsZV9jb250ZW50GAYgASgJSAOIAQESHwoXYWZ0ZXJfZnVsbF9maWxlX2NvbnRlbnQYByABKAkSFAoHbWVzc2FnZRgIIAEoCUgEiAEBQg4KDF9saW5lc19hZGRlZEIQCg5fbGluZXNfcmVtb3ZlZEIOCgxfZGlmZl9zdHJpbmdCGwoZX2JlZm9yZV9mdWxsX2ZpbGVfY29udGVudEIKCghfbWVzc2FnZSIgChBFZGl0RmlsZU5vdEZvdW5kEgwKBHBhdGgYASABKAkiKAoYRWRpdFJlYWRQZXJtaXNzaW9uRGVuaWVkEgwKBHBhdGgYASABKAkiTQoZRWRpdFdyaXRlUGVybWlzc2lvbkRlbmllZBIMCgRwYXRoGAEgASgJEg0KBWVycm9yGAIgASgJEhMKC2lzX3JlYWRvbmx5GAMgASgIIiwKDEVkaXRSZWplY3RlZBIMCgRwYXRoGAEgASgJEg4KBnJlYXNvbhgCIAEoCSJiCglFZGl0RXJyb3ISDAoEcGF0aBgBIAEoCRINCgVlcnJvchgCIAEoCRIgChNtb2RlbF92aXNpYmxlX2Vycm9yGAUgASgJSACIAQFCFgoUX21vZGVsX3Zpc2libGVfZXJyb3IiVgoMRWRpdFRvb2xDYWxsEiAKBGFyZ3MYASABKAsyEi5hZ2VudC52MS5FZGl0QXJncxIkCgZyZXN1bHQYAiABKAsyFC5hZ2VudC52MS5FZGl0UmVzdWx0IjEKEUVkaXRUb29sQ2FsbERlbHRhEhwKFHN0cmVhbV9jb250ZW50X2RlbHRhGAEgASgJIjEKDEV4YUZldGNoQXJncxILCgNpZHMYASADKAkSFAoMdG9vbF9jYWxsX2lkGAIgASgJIqIBCg5FeGFGZXRjaFJlc3VsdBIsCgdzdWNjZXNzGAEgASgLMhkuYWdlbnQudjEuRXhhRmV0Y2hTdWNjZXNzSAASKAoFZXJyb3IYAiABKAsyFy5hZ2VudC52MS5FeGFGZXRjaEVycm9ySAASLgoIcmVqZWN0ZWQYAyABKAsyGi5hZ2VudC52MS5FeGFGZXRjaFJlamVjdGVkSABCCAoGcmVzdWx0Ij4KD0V4YUZldGNoU3VjY2VzcxIrCghjb250ZW50cxgBIAMoCzIZLmFnZW50LnYxLkV4YUZldGNoQ29udGVudCIeCg1FeGFGZXRjaEVycm9yEg0KBWVycm9yGAEgASgJIiIKEEV4YUZldGNoUmVqZWN0ZWQSDgoGcmVhc29uGAEgASgJIlMKD0V4YUZldGNoQ29udGVudBINCgV0aXRsZRgBIAEoCRILCgN1cmwYAiABKAkSDAoEdGV4dBgDIAEoCRIWCg5wdWJsaXNoZWRfZGF0ZRgEIAEoCSJiChBFeGFGZXRjaFRvb2xDYWxsEiQKBGFyZ3MYASABKAsyFi5hZ2VudC52MS5FeGFGZXRjaEFyZ3MSKAoGcmVzdWx0GAIgASgLMhguYWdlbnQudjEuRXhhRmV0Y2hSZXN1bHQiPAoURXhhRmV0Y2hSZXF1ZXN0UXVlcnkSJAoEYXJncxgBIAEoCzIWLmFnZW50LnYxLkV4YUZldGNoQXJncyKjAQoXRXhhRmV0Y2hSZXF1ZXN0UmVzcG9uc2USPgoIYXBwcm92ZWQYASABKAsyKi5hZ2VudC52MS5FeGFGZXRjaFJlcXVlc3RSZXNwb25zZV9BcHByb3ZlZEgAEj4KCHJlamVjdGVkGAIgASgLMiouYWdlbnQudjEuRXhhRmV0Y2hSZXF1ZXN0UmVzcG9uc2VfUmVqZWN0ZWRIAEIICgZyZXN1bHQiIgogRXhhRmV0Y2hSZXF1ZXN0UmVzcG9uc2VfQXBwcm92ZWQiMgogRXhhRmV0Y2hSZXF1ZXN0UmVzcG9uc2VfUmVqZWN0ZWQSDgoGcmVhc29uGAEgASgJIlcKDUV4YVNlYXJjaEFyZ3MSDQoFcXVlcnkYASABKAkSDAoEdHlwZRgCIAEoCRITCgtudW1fcmVzdWx0cxgDIAEoBRIUCgx0b29sX2NhbGxfaWQYBCABKAkipgEKD0V4YVNlYXJjaFJlc3VsdBItCgdzdWNjZXNzGAEgASgLMhouYWdlbnQudjEuRXhhU2VhcmNoU3VjY2Vzc0gAEikKBWVycm9yGAIgASgLMhguYWdlbnQudjEuRXhhU2VhcmNoRXJyb3JIABIvCghyZWplY3RlZBgDIAEoCzIbLmFnZW50LnYxLkV4YVNlYXJjaFJlamVjdGVkSABCCAoGcmVzdWx0IkQKEEV4YVNlYXJjaFN1Y2Nlc3MSMAoKcmVmZXJlbmNlcxgBIAMoCzIcLmFnZW50LnYxLkV4YVNlYXJjaFJlZmVyZW5jZSIfCg5FeGFTZWFyY2hFcnJvchINCgVlcnJvchgBIAEoCSIjChFFeGFTZWFyY2hSZWplY3RlZBIOCgZyZWFzb24YASABKAkiVgoSRXhhU2VhcmNoUmVmZXJlbmNlEg0KBXRpdGxlGAEgASgJEgsKA3VybBgCIAEoCRIMCgR0ZXh0GAMgASgJEhYKDnB1Ymxpc2hlZF9kYXRlGAQgASgJImUKEUV4YVNlYXJjaFRvb2xDYWxsEiUKBGFyZ3MYASABKAsyFy5hZ2VudC52MS5FeGFTZWFyY2hBcmdzEikKBnJlc3VsdBgCIAEoCzIZLmFnZW50LnYxLkV4YVNlYXJjaFJlc3VsdCI+ChVFeGFTZWFyY2hSZXF1ZXN0UXVlcnkSJQoEYXJncxgBIAEoCzIXLmFnZW50LnYxLkV4YVNlYXJjaEFyZ3MipgEKGEV4YVNlYXJjaFJlcXVlc3RSZXNwb25zZRI/CghhcHByb3ZlZBgBIAEoCzIrLmFnZW50LnYxLkV4YVNlYXJjaFJlcXVlc3RSZXNwb25zZV9BcHByb3ZlZEgAEj8KCHJlamVjdGVkGAIgASgLMisuYWdlbnQudjEuRXhhU2VhcmNoUmVxdWVzdFJlc3BvbnNlX1JlamVjdGVkSABCCAoGcmVzdWx0IiMKIUV4YVNlYXJjaFJlcXVlc3RSZXNwb25zZV9BcHByb3ZlZCIzCiFFeGFTZWFyY2hSZXF1ZXN0UmVzcG9uc2VfUmVqZWN0ZWQSDgoGcmVhc29uGAEgASgJIiMKFUV4ZWNDbGllbnRTdHJlYW1DbG9zZRIKCgJpZBgBIAEoDSJWCg9FeGVjQ2xpZW50VGhyb3cSCgoCaWQYASABKA0SDQoFZXJyb3IYAiABKAkSGAoLc3RhY2tfdHJhY2UYAyABKAlIAIgBAUIOCgxfc3RhY2tfdHJhY2UiIQoTRXhlY0NsaWVudEhlYXJ0YmVhdBIKCgJpZBgBIAEoDSK+AQoYRXhlY0NsaWVudENvbnRyb2xNZXNzYWdlEjcKDHN0cmVhbV9jbG9zZRgBIAEoCzIfLmFnZW50LnYxLkV4ZWNDbGllbnRTdHJlYW1DbG9zZUgAEioKBXRocm93GAIgASgLMhkuYWdlbnQudjEuRXhlY0NsaWVudFRocm93SAASMgoJaGVhcnRiZWF0GAMgASgLMh0uYWdlbnQudjEuRXhlY0NsaWVudEhlYXJ0YmVhdEgAQgkKB21lc3NhZ2UihAEKC1NwYW5Db250ZXh0EhAKCHRyYWNlX2lkGAEgASgJEg8KB3NwYW5faWQYAiABKAkSGAoLdHJhY2VfZmxhZ3MYAyABKA1IAIgBARIYCgt0cmFjZV9zdGF0ZRgEIAEoCUgBiAEBQg4KDF90cmFjZV9mbGFnc0IOCgxfdHJhY2Vfc3RhdGUiCwoJQWJvcnRBcmdzIg0KC0Fib3J0UmVzdWx0IoUIChFFeGVjU2VydmVyTWVzc2FnZRIKCgJpZBgBIAEoDRIPCgdleGVjX2lkGA8gASgJEjAKDHNwYW5fY29udGV4dBgTIAEoCzIVLmFnZW50LnYxLlNwYW5Db250ZXh0SAGIAQESKQoKc2hlbGxfYXJncxgCIAEoCzITLmFnZW50LnYxLlNoZWxsQXJnc0gAEikKCndyaXRlX2FyZ3MYAyABKAsyEy5hZ2VudC52MS5Xcml0ZUFyZ3NIABIrCgtkZWxldGVfYXJncxgEIAEoCzIULmFnZW50LnYxLkRlbGV0ZUFyZ3NIABInCglncmVwX2FyZ3MYBSABKAsyEi5hZ2VudC52MS5HcmVwQXJnc0gAEicKCXJlYWRfYXJncxgHIAEoCzISLmFnZW50LnYxLlJlYWRBcmdzSAASIwoHbHNfYXJncxgIIAEoCzIQLmFnZW50LnYxLkxzQXJnc0gAEjUKEGRpYWdub3N0aWNzX2FyZ3MYCSABKAsyGS5hZ2VudC52MS5EaWFnbm9zdGljc0FyZ3NIABI8ChRyZXF1ZXN0X2NvbnRleHRfYXJncxgKIAEoCzIcLmFnZW50LnYxLlJlcXVlc3RDb250ZXh0QXJnc0gAEiUKCG1jcF9hcmdzGAsgASgLMhEuYWdlbnQudjEuTWNwQXJnc0gAEjAKEXNoZWxsX3N0cmVhbV9hcmdzGA4gASgLMhMuYWdlbnQudjEuU2hlbGxBcmdzSAASSQobYmFja2dyb3VuZF9zaGVsbF9zcGF3bl9hcmdzGBAgASgLMiIuYWdlbnQudjEuQmFja2dyb3VuZFNoZWxsU3Bhd25BcmdzSAASSgocbGlzdF9tY3BfcmVzb3VyY2VzX2V4ZWNfYXJncxgRIAEoCzIiLmFnZW50LnYxLkxpc3RNY3BSZXNvdXJjZXNFeGVjQXJnc0gAEkgKG3JlYWRfbWNwX3Jlc291cmNlX2V4ZWNfYXJncxgSIAEoCzIhLmFnZW50LnYxLlJlYWRNY3BSZXNvdXJjZUV4ZWNBcmdzSAASKQoKZmV0Y2hfYXJncxgUIAEoCzITLmFnZW50LnYxLkZldGNoQXJnc0gAEjgKEnJlY29yZF9zY3JlZW5fYXJncxgVIAEoCzIaLmFnZW50LnYxLlJlY29yZFNjcmVlbkFyZ3NIABI2ChFjb21wdXRlcl91c2VfYXJncxgWIAEoCzIZLmFnZW50LnYxLkNvbXB1dGVyVXNlQXJnc0gAEj8KFndyaXRlX3NoZWxsX3N0ZGluX2FyZ3MYFyABKAsyHS5hZ2VudC52MS5Xcml0ZVNoZWxsU3RkaW5BcmdzSABCCQoHbWVzc2FnZUIPCg1fc3Bhbl9jb250ZXh0Iv8HChFFeGVjQ2xpZW50TWVzc2FnZRIKCgJpZBgBIAEoDRIPCgdleGVjX2lkGA8gASgJEi0KDHNoZWxsX3Jlc3VsdBgCIAEoCzIVLmFnZW50LnYxLlNoZWxsUmVzdWx0SAASLQoMd3JpdGVfcmVzdWx0GAMgASgLMhUuYWdlbnQudjEuV3JpdGVSZXN1bHRIABIvCg1kZWxldGVfcmVzdWx0GAQgASgLMhYuYWdlbnQudjEuRGVsZXRlUmVzdWx0SAASKwoLZ3JlcF9yZXN1bHQYBSABKAsyFC5hZ2VudC52MS5HcmVwUmVzdWx0SAASKwoLcmVhZF9yZXN1bHQYByABKAsyFC5hZ2VudC52MS5SZWFkUmVzdWx0SAASJwoJbHNfcmVzdWx0GAggASgLMhIuYWdlbnQudjEuTHNSZXN1bHRIABI5ChJkaWFnbm9zdGljc19yZXN1bHQYCSABKAsyGy5hZ2VudC52MS5EaWFnbm9zdGljc1Jlc3VsdEgAEkAKFnJlcXVlc3RfY29udGV4dF9yZXN1bHQYCiABKAsyHi5hZ2VudC52MS5SZXF1ZXN0Q29udGV4dFJlc3VsdEgAEikKCm1jcF9yZXN1bHQYCyABKAsyEy5hZ2VudC52MS5NY3BSZXN1bHRIABItCgxzaGVsbF9zdHJlYW0YDiABKAsyFS5hZ2VudC52MS5TaGVsbFN0cmVhbUgAEk0KHWJhY2tncm91bmRfc2hlbGxfc3Bhd25fcmVzdWx0GBAgASgLMiQuYWdlbnQudjEuQmFja2dyb3VuZFNoZWxsU3Bhd25SZXN1bHRIABJOCh5saXN0X21jcF9yZXNvdXJjZXNfZXhlY19yZXN1bHQYESABKAsyJC5hZ2VudC52MS5MaXN0TWNwUmVzb3VyY2VzRXhlY1Jlc3VsdEgAEkwKHXJlYWRfbWNwX3Jlc291cmNlX2V4ZWNfcmVzdWx0GBIgASgLMiMuYWdlbnQudjEuUmVhZE1jcFJlc291cmNlRXhlY1Jlc3VsdEgAEi0KDGZldGNoX3Jlc3VsdBgUIAEoCzIVLmFnZW50LnYxLkZldGNoUmVzdWx0SAASPAoUcmVjb3JkX3NjcmVlbl9yZXN1bHQYFSABKAsyHC5hZ2VudC52MS5SZWNvcmRTY3JlZW5SZXN1bHRIABI6ChNjb21wdXRlcl91c2VfcmVzdWx0GBYgASgLMhsuYWdlbnQudjEuQ29tcHV0ZXJVc2VSZXN1bHRIABJDChh3cml0ZV9zaGVsbF9zdGRpbl9yZXN1bHQYFyABKAsyHy5hZ2VudC52MS5Xcml0ZVNoZWxsU3RkaW5SZXN1bHRIAEIJCgdtZXNzYWdlIi4KCUZldGNoQXJncxILCgN1cmwYASABKAkSFAoMdG9vbF9jYWxsX2lkGAIgASgJImkKC0ZldGNoUmVzdWx0EikKB3N1Y2Nlc3MYASABKAsyFi5hZ2VudC52MS5GZXRjaFN1Y2Nlc3NIABIlCgVlcnJvchgCIAEoCzIULmFnZW50LnYxLkZldGNoRXJyb3JIAEIICgZyZXN1bHQiVwoMRmV0Y2hTdWNjZXNzEgsKA3VybBgBIAEoCRIPCgdjb250ZW50GAIgASgJEhMKC3N0YXR1c19jb2RlGAMgASgFEhQKDGNvbnRlbnRfdHlwZRgEIAEoCSIoCgpGZXRjaEVycm9yEgsKA3VybBgBIAEoCRINCgVlcnJvchgCIAEoCSJtChFHZW5lcmF0ZUltYWdlQXJncxITCgtkZXNjcmlwdGlvbhgBIAEoCRIWCglmaWxlX3BhdGgYAiABKAlIAIgBARIdChVyZWZlcmVuY2VfaW1hZ2VfcGF0aHMYBSADKAlCDAoKX2ZpbGVfcGF0aCKBAQoTR2VuZXJhdGVJbWFnZVJlc3VsdBIxCgdzdWNjZXNzGAEgASgLMh4uYWdlbnQudjEuR2VuZXJhdGVJbWFnZVN1Y2Nlc3NIABItCgVlcnJvchgCIAEoCzIcLmFnZW50LnYxLkdlbmVyYXRlSW1hZ2VFcnJvckgAQggKBnJlc3VsdCI9ChRHZW5lcmF0ZUltYWdlU3VjY2VzcxIRCglmaWxlX3BhdGgYASABKAkSEgoKaW1hZ2VfZGF0YRgCIAEoCSIjChJHZW5lcmF0ZUltYWdlRXJyb3ISDQoFZXJyb3IYASABKAkicQoVR2VuZXJhdGVJbWFnZVRvb2xDYWxsEikKBGFyZ3MYASABKAsyGy5hZ2VudC52MS5HZW5lcmF0ZUltYWdlQXJncxItCgZyZXN1bHQYAiABKAsyHS5hZ2VudC52MS5HZW5lcmF0ZUltYWdlUmVzdWx0IsYECghHcmVwQXJncxIPCgdwYXR0ZXJuGAEgASgJEhEKBHBhdGgYAiABKAlIAIgBARIRCgRnbG9iGAMgASgJSAGIAQESGAoLb3V0cHV0X21vZGUYBCABKAlIAogBARIbCg5jb250ZXh0X2JlZm9yZRgFIAEoBUgDiAEBEhoKDWNvbnRleHRfYWZ0ZXIYBiABKAVIBIgBARIUCgdjb250ZXh0GAcgASgFSAWIAQESHQoQY2FzZV9pbnNlbnNpdGl2ZRgIIAEoCEgGiAEBEhEKBHR5cGUYCSABKAlIB4gBARIXCgpoZWFkX2xpbWl0GAogASgFSAiIAQESFgoJbXVsdGlsaW5lGAsgASgISAmIAQESEQoEc29ydBgMIAEoCUgKiAEBEhsKDnNvcnRfYXNjZW5kaW5nGA0gASgISAuIAQESFAoMdG9vbF9jYWxsX2lkGA4gASgJEjQKDnNhbmRib3hfcG9saWN5GA8gASgLMhcuYWdlbnQudjEuU2FuZGJveFBvbGljeUgMiAEBQgcKBV9wYXRoQgcKBV9nbG9iQg4KDF9vdXRwdXRfbW9kZUIRCg9fY29udGV4dF9iZWZvcmVCEAoOX2NvbnRleHRfYWZ0ZXJCCgoIX2NvbnRleHRCEwoRX2Nhc2VfaW5zZW5zaXRpdmVCBwoFX3R5cGVCDQoLX2hlYWRfbGltaXRCDAoKX211bHRpbGluZUIHCgVfc29ydEIRCg9fc29ydF9hc2NlbmRpbmdCEQoPX3NhbmRib3hfcG9saWN5ImYKCkdyZXBSZXN1bHQSKAoHc3VjY2VzcxgBIAEoCzIVLmFnZW50LnYxLkdyZXBTdWNjZXNzSAASJAoFZXJyb3IYAiABKAsyEy5hZ2VudC52MS5HcmVwRXJyb3JIAEIICgZyZXN1bHQiGgoJR3JlcEVycm9yEg0KBWVycm9yGAEgASgJIrQCCgtHcmVwU3VjY2VzcxIPCgdwYXR0ZXJuGAEgASgJEgwKBHBhdGgYAiABKAkSEwoLb3V0cHV0X21vZGUYAyABKAkSRgoRd29ya3NwYWNlX3Jlc3VsdHMYBCADKAsyKy5hZ2VudC52MS5HcmVwU3VjY2Vzcy5Xb3Jrc3BhY2VSZXN1bHRzRW50cnkSPAoUYWN0aXZlX2VkaXRvcl9yZXN1bHQYBSABKAsyGS5hZ2VudC52MS5HcmVwVW5pb25SZXN1bHRIAIgBARpSChVXb3Jrc3BhY2VSZXN1bHRzRW50cnkSCwoDa2V5GAEgASgJEigKBXZhbHVlGAIgASgLMhkuYWdlbnQudjEuR3JlcFVuaW9uUmVzdWx0OgI4AUIXChVfYWN0aXZlX2VkaXRvcl9yZXN1bHQiowEKD0dyZXBVbmlvblJlc3VsdBIqCgVjb3VudBgBIAEoCzIZLmFnZW50LnYxLkdyZXBDb3VudFJlc3VsdEgAEioKBWZpbGVzGAIgASgLMhkuYWdlbnQudjEuR3JlcEZpbGVzUmVzdWx0SAASLgoHY29udGVudBgDIAEoCzIbLmFnZW50LnYxLkdyZXBDb250ZW50UmVzdWx0SABCCAoGcmVzdWx0IpsBCg9HcmVwQ291bnRSZXN1bHQSJwoGY291bnRzGAEgAygLMhcuYWdlbnQudjEuR3JlcEZpbGVDb3VudBITCgt0b3RhbF9maWxlcxgCIAEoBRIVCg10b3RhbF9tYXRjaGVzGAMgASgFEhgKEGNsaWVudF90cnVuY2F0ZWQYBCABKAgSGQoRcmlwZ3JlcF90cnVuY2F0ZWQYBSABKAgiLAoNR3JlcEZpbGVDb3VudBIMCgRmaWxlGAEgASgJEg0KBWNvdW50GAIgASgFImoKD0dyZXBGaWxlc1Jlc3VsdBINCgVmaWxlcxgBIAMoCRITCgt0b3RhbF9maWxlcxgCIAEoBRIYChBjbGllbnRfdHJ1bmNhdGVkGAMgASgIEhkKEXJpcGdyZXBfdHJ1bmNhdGVkGAQgASgIIqQBChFHcmVwQ29udGVudFJlc3VsdBIoCgdtYXRjaGVzGAEgAygLMhcuYWdlbnQudjEuR3JlcEZpbGVNYXRjaBITCgt0b3RhbF9saW5lcxgCIAEoBRIbChN0b3RhbF9tYXRjaGVkX2xpbmVzGAMgASgFEhgKEGNsaWVudF90cnVuY2F0ZWQYBCABKAgSGQoRcmlwZ3JlcF90cnVuY2F0ZWQYBSABKAgiSgoNR3JlcEZpbGVNYXRjaBIMCgRmaWxlGAEgASgJEisKB21hdGNoZXMYAiADKAsyGi5hZ2VudC52MS5HcmVwQ29udGVudE1hdGNoImwKEEdyZXBDb250ZW50TWF0Y2gSEwoLbGluZV9udW1iZXIYASABKAUSDwoHY29udGVudBgCIAEoCRIZChFjb250ZW50X3RydW5jYXRlZBgDIAEoCBIXCg9pc19jb250ZXh0X2xpbmUYBCABKAgiHQoKR3JlcFN0cmVhbRIPCgdwYXR0ZXJuGAEgASgJIlYKDEdyZXBUb29sQ2FsbBIgCgRhcmdzGAEgASgLMhIuYWdlbnQudjEuR3JlcEFyZ3MSJAoGcmVzdWx0GAIgASgLMhQuYWdlbnQudjEuR3JlcFJlc3VsdCIeCgtHZXRCbG9iQXJncxIPCgdibG9iX2lkGAEgASgMIjUKDUdldEJsb2JSZXN1bHQSFgoJYmxvYl9kYXRhGAEgASgMSACIAQFCDAoKX2Jsb2JfZGF0YSIxCgtTZXRCbG9iQXJncxIPCgdibG9iX2lkGAEgASgMEhEKCWJsb2JfZGF0YRgCIAEoDCI+Cg1TZXRCbG9iUmVzdWx0EiMKBWVycm9yGAEgASgLMg8uYWdlbnQudjEuRXJyb3JIAIgBAUIICgZfZXJyb3IiywEKD0t2U2VydmVyTWVzc2FnZRIKCgJpZBgBIAEoDRIwCgxzcGFuX2NvbnRleHQYBCABKAsyFS5hZ2VudC52MS5TcGFuQ29udGV4dEgBiAEBEi4KDWdldF9ibG9iX2FyZ3MYAiABKAsyFS5hZ2VudC52MS5HZXRCbG9iQXJnc0gAEi4KDXNldF9ibG9iX2FyZ3MYAyABKAsyFS5hZ2VudC52MS5TZXRCbG9iQXJnc0gAQgkKB21lc3NhZ2VCDwoNX3NwYW5fY29udGV4dCKQAQoPS3ZDbGllbnRNZXNzYWdlEgoKAmlkGAEgASgNEjIKD2dldF9ibG9iX3Jlc3VsdBgCIAEoCzIXLmFnZW50LnYxLkdldEJsb2JSZXN1bHRIABIyCg9zZXRfYmxvYl9yZXN1bHQYAyABKAsyFy5hZ2VudC52MS5TZXRCbG9iUmVzdWx0SABCCQoHbWVzc2FnZSKtAQoGTHNBcmdzEgwKBHBhdGgYASABKAkSDgoGaWdub3JlGAIgAygJEhQKDHRvb2xfY2FsbF9pZBgDIAEoCRI0Cg5zYW5kYm94X3BvbGljeRgEIAEoCzIXLmFnZW50LnYxLlNhbmRib3hQb2xpY3lIAIgBARIXCgp0aW1lb3V0X21zGAUgASgNSAGIAQFCEQoPX3NhbmRib3hfcG9saWN5Qg0KC190aW1lb3V0X21zIrIBCghMc1Jlc3VsdBImCgdzdWNjZXNzGAEgASgLMhMuYWdlbnQudjEuTHNTdWNjZXNzSAASIgoFZXJyb3IYAiABKAsyES5hZ2VudC52MS5Mc0Vycm9ySAASKAoIcmVqZWN0ZWQYAyABKAsyFC5hZ2VudC52MS5Mc1JlamVjdGVkSAASJgoHdGltZW91dBgEIAEoCzITLmFnZW50LnYxLkxzVGltZW91dEgAQggKBnJlc3VsdCJHCglMc1N1Y2Nlc3MSOgoTZGlyZWN0b3J5X3RyZWVfcm9vdBgBIAEoCzIdLmFnZW50LnYxLkxzRGlyZWN0b3J5VHJlZU5vZGUi9gIKE0xzRGlyZWN0b3J5VHJlZU5vZGUSEAoIYWJzX3BhdGgYASABKAkSNAoNY2hpbGRyZW5fZGlycxgCIAMoCzIdLmFnZW50LnYxLkxzRGlyZWN0b3J5VHJlZU5vZGUSOgoOY2hpbGRyZW5fZmlsZXMYAyADKAsyIi5hZ2VudC52MS5Mc0RpcmVjdG9yeVRyZWVOb2RlX0ZpbGUSHwoXY2hpbGRyZW5fd2VyZV9wcm9jZXNzZWQYBCABKAgSZAodZnVsbF9zdWJ0cmVlX2V4dGVuc2lvbl9jb3VudHMYBSADKAsyPS5hZ2VudC52MS5Mc0RpcmVjdG9yeVRyZWVOb2RlLkZ1bGxTdWJ0cmVlRXh0ZW5zaW9uQ291bnRzRW50cnkSEQoJbnVtX2ZpbGVzGAYgASgFGkEKH0Z1bGxTdWJ0cmVlRXh0ZW5zaW9uQ291bnRzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgFOgI4ASJ6ChhMc0RpcmVjdG9yeVRyZWVOb2RlX0ZpbGUSDAoEbmFtZRgBIAEoCRI6ChF0ZXJtaW5hbF9tZXRhZGF0YRgCIAEoCzIaLmFnZW50LnYxLlRlcm1pbmFsTWV0YWRhdGFIAIgBAUIUChJfdGVybWluYWxfbWV0YWRhdGEiJgoHTHNFcnJvchIMCgRwYXRoGAEgASgJEg0KBWVycm9yGAIgASgJIioKCkxzUmVqZWN0ZWQSDAoEcGF0aBgBIAEoCRIOCgZyZWFzb24YAiABKAkiRwoJTHNUaW1lb3V0EjoKE2RpcmVjdG9yeV90cmVlX3Jvb3QYASABKAsyHS5hZ2VudC52MS5Mc0RpcmVjdG9yeVRyZWVOb2RlIvEBChBUZXJtaW5hbE1ldGFkYXRhEhAKA2N3ZBgBIAEoCUgAiAEBEjkKDWxhc3RfY29tbWFuZHMYAiADKAsyIi5hZ2VudC52MS5UZXJtaW5hbE1ldGFkYXRhX0NvbW1hbmQSHQoQbGFzdF9tb2RpZmllZF9tcxgDIAEoA0gBiAEBEkAKD2N1cnJlbnRfY29tbWFuZBgEIAEoCzIiLmFnZW50LnYxLlRlcm1pbmFsTWV0YWRhdGFfQ29tbWFuZEgCiAEBQgYKBF9jd2RCEwoRX2xhc3RfbW9kaWZpZWRfbXNCEgoQX2N1cnJlbnRfY29tbWFuZCKnAQoYVGVybWluYWxNZXRhZGF0YV9Db21tYW5kEg8KB2NvbW1hbmQYASABKAkSFgoJZXhpdF9jb2RlGAIgASgFSACIAQESGQoMdGltZXN0YW1wX21zGAMgASgDSAGIAQESGAoLZHVyYXRpb25fbXMYBCABKANIAogBAUIMCgpfZXhpdF9jb2RlQg8KDV90aW1lc3RhbXBfbXNCDgoMX2R1cmF0aW9uX21zIlAKCkxzVG9vbENhbGwSHgoEYXJncxgBIAEoCzIQLmFnZW50LnYxLkxzQXJncxIiCgZyZXN1bHQYAiABKAsyEi5hZ2VudC52MS5Mc1Jlc3VsdCK1AQoHTWNwQXJncxIMCgRuYW1lGAEgASgJEikKBGFyZ3MYAiADKAsyGy5hZ2VudC52MS5NY3BBcmdzLkFyZ3NFbnRyeRIUCgx0b29sX2NhbGxfaWQYAyABKAkSGwoTcHJvdmlkZXJfaWRlbnRpZmllchgEIAEoCRIRCgl0b29sX25hbWUYBSABKAkaKwoJQXJnc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoDDoCOAEi/wEKCU1jcFJlc3VsdBInCgdzdWNjZXNzGAEgASgLMhQuYWdlbnQudjEuTWNwU3VjY2Vzc0gAEiMKBWVycm9yGAIgASgLMhIuYWdlbnQudjEuTWNwRXJyb3JIABIpCghyZWplY3RlZBgDIAEoCzIVLmFnZW50LnYxLk1jcFJlamVjdGVkSAASOgoRcGVybWlzc2lvbl9kZW5pZWQYBCABKAsyHS5hZ2VudC52MS5NY3BQZXJtaXNzaW9uRGVuaWVkSAASMwoOdG9vbF9ub3RfZm91bmQYBSABKAsyGS5hZ2VudC52MS5NY3BUb29sTm90Rm91bmRIAEIICgZyZXN1bHQiOAoPTWNwVG9vbE5vdEZvdW5kEgwKBG5hbWUYASABKAkSFwoPYXZhaWxhYmxlX3Rvb2xzGAIgAygJImoKDk1jcFRleHRDb250ZW50EgwKBHRleHQYASABKAkSNgoPb3V0cHV0X2xvY2F0aW9uGAIgASgLMhguYWdlbnQudjEuT3V0cHV0TG9jYXRpb25IAIgBAUISChBfb3V0cHV0X2xvY2F0aW9uIjIKD01jcEltYWdlQ29udGVudBIMCgRkYXRhGAEgASgMEhEKCW1pbWVfdHlwZRgCIAEoCSJ7ChhNY3BUb29sUmVzdWx0Q29udGVudEl0ZW0SKAoEdGV4dBgBIAEoCzIYLmFnZW50LnYxLk1jcFRleHRDb250ZW50SAASKgoFaW1hZ2UYAiABKAsyGS5hZ2VudC52MS5NY3BJbWFnZUNvbnRlbnRIAEIJCgdjb250ZW50IlMKCk1jcFN1Y2Nlc3MSMwoHY29udGVudBgBIAMoCzIiLmFnZW50LnYxLk1jcFRvb2xSZXN1bHRDb250ZW50SXRlbRIQCghpc19lcnJvchgCIAEoCCIZCghNY3BFcnJvchINCgVlcnJvchgBIAEoCSIyCgtNY3BSZWplY3RlZBIOCgZyZWFzb24YASABKAkSEwoLaXNfcmVhZG9ubHkYAiABKAgiOQoTTWNwUGVybWlzc2lvbkRlbmllZBINCgVlcnJvchgBIAEoCRITCgtpc19yZWFkb25seRgCIAEoCCI6ChhMaXN0TWNwUmVzb3VyY2VzRXhlY0FyZ3MSEwoGc2VydmVyGAEgASgJSACIAQFCCQoHX3NlcnZlciLGAQoaTGlzdE1jcFJlc291cmNlc0V4ZWNSZXN1bHQSNAoHc3VjY2VzcxgBIAEoCzIhLmFnZW50LnYxLkxpc3RNY3BSZXNvdXJjZXNTdWNjZXNzSAASMAoFZXJyb3IYAiABKAsyHy5hZ2VudC52MS5MaXN0TWNwUmVzb3VyY2VzRXJyb3JIABI2CghyZWplY3RlZBgDIAEoCzIiLmFnZW50LnYxLkxpc3RNY3BSZXNvdXJjZXNSZWplY3RlZEgAQggKBnJlc3VsdCK9AgomTGlzdE1jcFJlc291cmNlc0V4ZWNSZXN1bHRfTWNwUmVzb3VyY2USCwoDdXJpGAEgASgJEhEKBG5hbWUYAiABKAlIAIgBARIYCgtkZXNjcmlwdGlvbhgDIAEoCUgBiAEBEhYKCW1pbWVfdHlwZRgEIAEoCUgCiAEBEg4KBnNlcnZlchgFIAEoCRJWCgthbm5vdGF0aW9ucxgGIAMoCzJBLmFnZW50LnYxLkxpc3RNY3BSZXNvdXJjZXNFeGVjUmVzdWx0X01jcFJlc291cmNlLkFubm90YXRpb25zRW50cnkaMgoQQW5ub3RhdGlvbnNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBQgcKBV9uYW1lQg4KDF9kZXNjcmlwdGlvbkIMCgpfbWltZV90eXBlIl4KF0xpc3RNY3BSZXNvdXJjZXNTdWNjZXNzEkMKCXJlc291cmNlcxgBIAMoCzIwLmFnZW50LnYxLkxpc3RNY3BSZXNvdXJjZXNFeGVjUmVzdWx0X01jcFJlc291cmNlIiYKFUxpc3RNY3BSZXNvdXJjZXNFcnJvchINCgVlcnJvchgBIAEoCSIqChhMaXN0TWNwUmVzb3VyY2VzUmVqZWN0ZWQSDgoGcmVhc29uGAEgASgJImQKF1JlYWRNY3BSZXNvdXJjZUV4ZWNBcmdzEg4KBnNlcnZlchgBIAEoCRILCgN1cmkYAiABKAkSGgoNZG93bmxvYWRfcGF0aBgDIAEoCUgAiAEBQhAKDl9kb3dubG9hZF9wYXRoIvoBChlSZWFkTWNwUmVzb3VyY2VFeGVjUmVzdWx0EjMKB3N1Y2Nlc3MYASABKAsyIC5hZ2VudC52MS5SZWFkTWNwUmVzb3VyY2VTdWNjZXNzSAASLwoFZXJyb3IYAiABKAsyHi5hZ2VudC52MS5SZWFkTWNwUmVzb3VyY2VFcnJvckgAEjUKCHJlamVjdGVkGAMgASgLMiEuYWdlbnQudjEuUmVhZE1jcFJlc291cmNlUmVqZWN0ZWRIABI2Cglub3RfZm91bmQYBCABKAsyIS5hZ2VudC52MS5SZWFkTWNwUmVzb3VyY2VOb3RGb3VuZEgAQggKBnJlc3VsdCLmAgoWUmVhZE1jcFJlc291cmNlU3VjY2VzcxILCgN1cmkYASABKAkSEQoEbmFtZRgCIAEoCUgBiAEBEhgKC2Rlc2NyaXB0aW9uGAMgASgJSAKIAQESFgoJbWltZV90eXBlGAQgASgJSAOIAQESRgoLYW5ub3RhdGlvbnMYByADKAsyMS5hZ2VudC52MS5SZWFkTWNwUmVzb3VyY2VTdWNjZXNzLkFubm90YXRpb25zRW50cnkSGgoNZG93bmxvYWRfcGF0aBgIIAEoCUgEiAEBEg4KBHRleHQYBSABKAlIABIOCgRibG9iGAYgASgMSAAaMgoQQW5ub3RhdGlvbnNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBQgkKB2NvbnRlbnRCBwoFX25hbWVCDgoMX2Rlc2NyaXB0aW9uQgwKCl9taW1lX3R5cGVCEAoOX2Rvd25sb2FkX3BhdGgiMgoUUmVhZE1jcFJlc291cmNlRXJyb3ISCwoDdXJpGAEgASgJEg0KBWVycm9yGAIgASgJIjYKF1JlYWRNY3BSZXNvdXJjZVJlamVjdGVkEgsKA3VyaRgBIAEoCRIOCgZyZWFzb24YAiABKAkiJgoXUmVhZE1jcFJlc291cmNlTm90Rm91bmQSCwoDdXJpGAEgASgJInwKEU1jcFRvb2xEZWZpbml0aW9uEgwKBG5hbWUYASABKAkSGwoTcHJvdmlkZXJfaWRlbnRpZmllchgEIAEoCRIRCgl0b29sX25hbWUYBSABKAkSEwoLZGVzY3JpcHRpb24YAiABKAkSFAoMaW5wdXRfc2NoZW1hGAMgASgMIjoKCE1jcFRvb2xzEi4KCW1jcF90b29scxgBIAMoCzIbLmFnZW50LnYxLk1jcFRvb2xEZWZpbml0aW9uIjwKD01jcEluc3RydWN0aW9ucxITCgtzZXJ2ZXJfbmFtZRgBIAEoCRIUCgxpbnN0cnVjdGlvbnMYAiABKAki1wEKDU1jcERlc2NyaXB0b3ISEwoLc2VydmVyX25hbWUYASABKAkSGQoRc2VydmVyX2lkZW50aWZpZXIYAiABKAkSGAoLZm9sZGVyX3BhdGgYAyABKAlIAIgBARIkChdzZXJ2ZXJfdXNlX2luc3RydWN0aW9ucxgEIAEoCUgBiAEBEioKBXRvb2xzGAUgAygLMhsuYWdlbnQudjEuTWNwVG9vbERlc2NyaXB0b3JCDgoMX2ZvbGRlcl9wYXRoQhoKGF9zZXJ2ZXJfdXNlX2luc3RydWN0aW9ucyJYChFNY3BUb29sRGVzY3JpcHRvchIRCgl0b29sX25hbWUYASABKAkSHAoPZGVmaW5pdGlvbl9wYXRoGAIgASgJSACIAQFCEgoQX2RlZmluaXRpb25fcGF0aCJ4ChRNY3BGaWxlU3lzdGVtT3B0aW9ucxIPCgdlbmFibGVkGAEgASgIEh0KFXdvcmtzcGFjZV9wcm9qZWN0X2RpchgCIAEoCRIwCg9tY3BfZGVzY3JpcHRvcnMYAyADKAsyFy5hZ2VudC52MS5NY3BEZXNjcmlwdG9yIi4KCFJlYWRBcmdzEgwKBHBhdGgYASABKAkSFAoMdG9vbF9jYWxsX2lkGAIgASgJIrgCCgpSZWFkUmVzdWx0EigKB3N1Y2Nlc3MYASABKAsyFS5hZ2VudC52MS5SZWFkU3VjY2Vzc0gAEiQKBWVycm9yGAIgASgLMhMuYWdlbnQudjEuUmVhZEVycm9ySAASKgoIcmVqZWN0ZWQYAyABKAsyFi5hZ2VudC52MS5SZWFkUmVqZWN0ZWRIABI0Cg5maWxlX25vdF9mb3VuZBgEIAEoCzIaLmFnZW50LnYxLlJlYWRGaWxlTm90Rm91bmRIABI7ChFwZXJtaXNzaW9uX2RlbmllZBgFIAEoCzIeLmFnZW50LnYxLlJlYWRQZXJtaXNzaW9uRGVuaWVkSAASMQoMaW52YWxpZF9maWxlGAYgASgLMhkuYWdlbnQudjEuUmVhZEludmFsaWRGaWxlSABCCAoGcmVzdWx0IrMBCgtSZWFkU3VjY2VzcxIMCgRwYXRoGAEgASgJEhMKC3RvdGFsX2xpbmVzGAMgASgFEhEKCWZpbGVfc2l6ZRgEIAEoAxIRCgl0cnVuY2F0ZWQYBiABKAgSGwoOb3V0cHV0X2Jsb2JfaWQYByABKAxIAYgBARIRCgdjb250ZW50GAIgASgJSAASDgoEZGF0YRgFIAEoDEgAQggKBm91dHB1dEIRCg9fb3V0cHV0X2Jsb2JfaWQiKAoJUmVhZEVycm9yEgwKBHBhdGgYASABKAkSDQoFZXJyb3IYAiABKAkiLAoMUmVhZFJlamVjdGVkEgwKBHBhdGgYASABKAkSDgoGcmVhc29uGAIgASgJIiAKEFJlYWRGaWxlTm90Rm91bmQSDAoEcGF0aBgBIAEoCSIkChRSZWFkUGVybWlzc2lvbkRlbmllZBIMCgRwYXRoGAEgASgJIi8KD1JlYWRJbnZhbGlkRmlsZRIMCgRwYXRoGAEgASgJEg4KBnJlYXNvbhgCIAEoCSJeCgxSZWFkVG9vbENhbGwSJAoEYXJncxgBIAEoCzIWLmFnZW50LnYxLlJlYWRUb29sQXJncxIoCgZyZXN1bHQYAiABKAsyGC5hZ2VudC52MS5SZWFkVG9vbFJlc3VsdCJaCgxSZWFkVG9vbEFyZ3MSDAoEcGF0aBgBIAEoCRITCgZvZmZzZXQYAiABKAVIAIgBARISCgVsaW1pdBgDIAEoBUgBiAEBQgkKB19vZmZzZXRCCAoGX2xpbWl0InIKDlJlYWRUb29sUmVzdWx0EiwKB3N1Y2Nlc3MYASABKAsyGS5hZ2VudC52MS5SZWFkVG9vbFN1Y2Nlc3NIABIoCgVlcnJvchgCIAEoCzIXLmFnZW50LnYxLlJlYWRUb29sRXJyb3JIAEIICgZyZXN1bHQiMQoJUmVhZFJhbmdlEhIKCnN0YXJ0X2xpbmUYASABKA0SEAoIZW5kX2xpbmUYAiABKA0ijgIKD1JlYWRUb29sU3VjY2VzcxIQCghpc19lbXB0eRgCIAEoCBIWCg5leGNlZWRlZF9saW1pdBgDIAEoCBITCgt0b3RhbF9saW5lcxgEIAEoDRIRCglmaWxlX3NpemUYBSABKA0SDAoEcGF0aBgHIAEoCRIsCgpyZWFkX3JhbmdlGAggASgLMhMuYWdlbnQudjEuUmVhZFJhbmdlSAGIAQESEQoHY29udGVudBgBIAEoCUgAEg4KBGRhdGEYBiABKAxIABIWCgxkYXRhX2Jsb2JfaWQYCSABKAxIABIZCg9jb250ZW50X2Jsb2JfaWQYCiABKAxIAEIICgZvdXRwdXRCDQoLX3JlYWRfcmFuZ2UiJgoNUmVhZFRvb2xFcnJvchIVCg1lcnJvcl9tZXNzYWdlGAEgASgJImoKEFJlY29yZFNjcmVlbkFyZ3MSDAoEbW9kZRgBIAEoBRIUCgx0b29sX2NhbGxfaWQYAiABKAkSHQoQc2F2ZV9hc19maWxlbmFtZRgDIAEoCUgAiAEBQhMKEV9zYXZlX2FzX2ZpbGVuYW1lIokCChJSZWNvcmRTY3JlZW5SZXN1bHQSOwoNc3RhcnRfc3VjY2VzcxgBIAEoCzIiLmFnZW50LnYxLlJlY29yZFNjcmVlblN0YXJ0U3VjY2Vzc0gAEjkKDHNhdmVfc3VjY2VzcxgCIAEoCzIhLmFnZW50LnYxLlJlY29yZFNjcmVlblNhdmVTdWNjZXNzSAASPwoPZGlzY2FyZF9zdWNjZXNzGAMgASgLMiQuYWdlbnQudjEuUmVjb3JkU2NyZWVuRGlzY2FyZFN1Y2Nlc3NIABIwCgdmYWlsdXJlGAQgASgLMh0uYWdlbnQudjEuUmVjb3JkU2NyZWVuRmFpbHVyZUgAQggKBnJlc3VsdCJnChhSZWNvcmRTY3JlZW5TdGFydFN1Y2Nlc3MSJQodd2FzX3ByaW9yX3JlY29yZGluZ19jYW5jZWxsZWQYASABKAgSJAocd2FzX3NhdmVfYXNfZmlsZW5hbWVfaWdub3JlZBgCIAEoCCKgAQoXUmVjb3JkU2NyZWVuU2F2ZVN1Y2Nlc3MSDAoEcGF0aBgBIAEoCRIdChVyZWNvcmRpbmdfZHVyYXRpb25fbXMYAiABKAMSMAojcmVxdWVzdGVkX2ZpbGVfcGF0aF9yZWplY3RlZF9yZWFzb24YAyABKAVIAIgBAUImCiRfcmVxdWVzdGVkX2ZpbGVfcGF0aF9yZWplY3RlZF9yZWFzb24iHAoaUmVjb3JkU2NyZWVuRGlzY2FyZFN1Y2Nlc3MiJAoTUmVjb3JkU2NyZWVuRmFpbHVyZRINCgVlcnJvchgBIAEoCSI2ChNDdXJzb3JQYWNrYWdlUHJvbXB0EgwKBG5hbWUYASABKAkSEQoJZmlsZV9wYXRoGAIgASgJIuIBCg1DdXJzb3JQYWNrYWdlEgwKBG5hbWUYASABKAkSEwoLZGVzY3JpcHRpb24YAiABKAkSEwoLZm9sZGVyX3BhdGgYAyABKAkSDwoHZW5hYmxlZBgEIAEoCBIYCgtwYXJzZV9lcnJvchgFIAEoCUgAiAEBEi4KB3Byb21wdHMYBiADKAsyHS5hZ2VudC52MS5DdXJzb3JQYWNrYWdlUHJvbXB0EhgKEHJlYWRtZV9maWxlX3BhdGgYByABKAkSFAoMcGFja2FnZV90eXBlGAggASgFQg4KDF9wYXJzZV9lcnJvciKrAgoWUmVwb3NpdG9yeUluZGV4aW5nSW5mbxIfChdyZWxhdGl2ZV93b3Jrc3BhY2VfcGF0aBgBIAEoCRITCgtyZW1vdGVfdXJscxgCIAMoCRIUCgxyZW1vdGVfbmFtZXMYAyADKAkSEQoJcmVwb19uYW1lGAQgASgJEhIKCnJlcG9fb3duZXIYBSABKAkSEgoKaXNfdHJhY2tlZBgGIAEoCBIQCghpc19sb2NhbBgHIAEoCBImChlvcnRob2dvbmFsX3RyYW5zZm9ybV9zZWVkGAggASgBSACIAQESFQoNd29ya3NwYWNlX3VyaRgJIAEoCRIbChNwYXRoX2VuY3J5cHRpb25fa2V5GAogASgJQhwKGl9vcnRob2dvbmFsX3RyYW5zZm9ybV9zZWVkInQKElJlcXVlc3RDb250ZXh0QXJncxIdChBub3Rlc19zZXNzaW9uX2lkGAIgASgJSACIAQESGQoMd29ya3NwYWNlX2lkGAMgASgJSAGIAQFCEwoRX25vdGVzX3Nlc3Npb25faWRCDwoNX3dvcmtzcGFjZV9pZCK6AQoUUmVxdWVzdENvbnRleHRSZXN1bHQSMgoHc3VjY2VzcxgBIAEoCzIfLmFnZW50LnYxLlJlcXVlc3RDb250ZXh0U3VjY2Vzc0gAEi4KBWVycm9yGAIgASgLMh0uYWdlbnQudjEuUmVxdWVzdENvbnRleHRFcnJvckgAEjQKCHJlamVjdGVkGAMgASgLMiAuYWdlbnQudjEuUmVxdWVzdENvbnRleHRSZWplY3RlZEgAQggKBnJlc3VsdCJKChVSZXF1ZXN0Q29udGV4dFN1Y2Nlc3MSMQoPcmVxdWVzdF9jb250ZXh0GAEgASgLMhguYWdlbnQudjEuUmVxdWVzdENvbnRleHQiJAoTUmVxdWVzdENvbnRleHRFcnJvchINCgVlcnJvchgBIAEoCSIoChZSZXF1ZXN0Q29udGV4dFJlamVjdGVkEg4KBnJlYXNvbhgBIAEoCSLCAQoKSW1hZ2VQcm90bxIMCgRkYXRhGAEgASgMEgwKBHV1aWQYAiABKAkSDAoEcGF0aBgDIAEoCRIxCglkaW1lbnNpb24YBCABKAsyHi5hZ2VudC52MS5JbWFnZVByb3RvX0RpbWVuc2lvbhImChl0YXNrX3NwZWNpZmljX2Rlc2NyaXB0aW9uGAYgASgJSACIAQESEQoJbWltZV90eXBlGAcgASgJQhwKGl90YXNrX3NwZWNpZmljX2Rlc2NyaXB0aW9uIjUKFEltYWdlUHJvdG9fRGltZW5zaW9uEg0KBXdpZHRoGAEgASgFEg4KBmhlaWdodBgCIAEoBSJoCgtHaXRSZXBvSW5mbxIMCgRwYXRoGAEgASgJEg4KBnN0YXR1cxgCIAEoCRITCgticmFuY2hfbmFtZRgDIAEoCRIXCgpyZW1vdGVfdXJsGAQgASgJSACIAQFCDQoLX3JlbW90ZV91cmwimwIKEVJlcXVlc3RDb250ZXh0RW52EhIKCm9zX3ZlcnNpb24YASABKAkSFwoPd29ya3NwYWNlX3BhdGhzGAIgAygJEg0KBXNoZWxsGAMgASgJEhcKD3NhbmRib3hfZW5hYmxlZBgFIAEoCBIYChB0ZXJtaW5hbHNfZm9sZGVyGAcgASgJEiEKGWFnZW50X3NoYXJlZF9ub3Rlc19mb2xkZXIYCCABKAkSJwofYWdlbnRfY29udmVyc2F0aW9uX25vdGVzX2ZvbGRlchgJIAEoCRIRCgl0aW1lX3pvbmUYCiABKAkSFgoOcHJvamVjdF9mb2xkZXIYCyABKAkSIAoYYWdlbnRfdHJhbnNjcmlwdHNfZm9sZGVyGAwgASgJIjwKD0RlYnVnTW9kZUNvbmZpZxIQCghsb2dfcGF0aBgBIAEoCRIXCg9zZXJ2ZXJfZW5kcG9pbnQYAiABKAkitAEKD1NraWxsRGVzY3JpcHRvchIMCgRuYW1lGAEgASgJEhMKC2Rlc2NyaXB0aW9uGAIgASgJEhMKC2ZvbGRlcl9wYXRoGAMgASgJEg8KB2VuYWJsZWQYBCABKAgSGAoLcGFyc2VfZXJyb3IYBSABKAlIAIgBARIYChByZWFkbWVfZmlsZV9wYXRoGAYgASgJEhQKDHBhY2thZ2VfdHlwZRgHIAEoBUIOCgxfcGFyc2VfZXJyb3IiRAoMU2tpbGxPcHRpb25zEjQKEXNraWxsX2Rlc2NyaXB0b3JzGAEgAygLMhkuYWdlbnQudjEuU2tpbGxEZXNjcmlwdG9yIvYICg5SZXF1ZXN0Q29udGV4dBIjCgVydWxlcxgCIAMoCzIULmFnZW50LnYxLkN1cnNvclJ1bGUSKAoDZW52GAQgASgLMhsuYWdlbnQudjEuUmVxdWVzdENvbnRleHRFbnYSOQoPcmVwb3NpdG9yeV9pbmZvGAYgAygLMiAuYWdlbnQudjEuUmVwb3NpdG9yeUluZGV4aW5nSW5mbxIqCgV0b29scxgHIAMoCzIbLmFnZW50LnYxLk1jcFRvb2xEZWZpbml0aW9uEicKGmNvbnZlcnNhdGlvbl9ub3Rlc19saXN0aW5nGAggASgJSACIAQESIQoUc2hhcmVkX25vdGVzX2xpc3RpbmcYCSABKAlIAYgBARIoCglnaXRfcmVwb3MYCyADKAsyFS5hZ2VudC52MS5HaXRSZXBvSW5mbxI2Cg9wcm9qZWN0X2xheW91dHMYDSADKAsyHS5hZ2VudC52MS5Mc0RpcmVjdG9yeVRyZWVOb2RlEjMKEG1jcF9pbnN0cnVjdGlvbnMYDiADKAsyGS5hZ2VudC52MS5NY3BJbnN0cnVjdGlvbnMSOQoRZGVidWdfbW9kZV9jb25maWcYDyABKAsyGS5hZ2VudC52MS5EZWJ1Z01vZGVDb25maWdIAogBARIXCgpjbG91ZF9ydWxlGBAgASgJSAOIAQESHwoSd2ViX3NlYXJjaF9lbmFibGVkGBEgASgISASIAQESMgoNc2tpbGxfb3B0aW9ucxgSIAEoCzIWLmFnZW50LnYxLlNraWxsT3B0aW9uc0gFiAEBEi4KIXJlcG9zaXRvcnlfaW5mb19zaG91bGRfcXVlcnlfcHJvZBgTIAEoCEgGiAEBEkEKDWZpbGVfY29udGVudHMYFCADKAsyKi5hZ2VudC52MS5SZXF1ZXN0Q29udGV4dC5GaWxlQ29udGVudHNFbnRyeRIgChN1c2VyX2ludGVudF9zdW1tYXJ5GBUgASgJSAeIAQESMgoQY3VzdG9tX3N1YmFnZW50cxgWIAMoCzIYLmFnZW50LnYxLkN1c3RvbVN1YmFnZW50EkQKF21jcF9maWxlX3N5c3RlbV9vcHRpb25zGBcgASgLMh4uYWdlbnQudjEuTWNwRmlsZVN5c3RlbU9wdGlvbnNICIgBARozChFGaWxlQ29udGVudHNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBQh0KG19jb252ZXJzYXRpb25fbm90ZXNfbGlzdGluZ0IXChVfc2hhcmVkX25vdGVzX2xpc3RpbmdCFAoSX2RlYnVnX21vZGVfY29uZmlnQg0KC19jbG91ZF9ydWxlQhUKE193ZWJfc2VhcmNoX2VuYWJsZWRCEAoOX3NraWxsX29wdGlvbnNCJAoiX3JlcG9zaXRvcnlfaW5mb19zaG91bGRfcXVlcnlfcHJvZEIWChRfdXNlcl9pbnRlbnRfc3VtbWFyeUIaChhfbWNwX2ZpbGVfc3lzdGVtX29wdGlvbnMisgIKDVNhbmRib3hQb2xpY3kSDAoEdHlwZRgBIAEoBRIbCg5uZXR3b3JrX2FjY2VzcxgCIAEoCEgAiAEBEiIKGmFkZGl0aW9uYWxfcmVhZHdyaXRlX3BhdGhzGAMgAygJEiEKGWFkZGl0aW9uYWxfcmVhZG9ubHlfcGF0aHMYBCADKAkSHQoQZGVidWdfb3V0cHV0X2RpchgFIAEoCUgBiAEBEh0KEGJsb2NrX2dpdF93cml0ZXMYBiABKAhIAogBARIeChFkaXNhYmxlX3RtcF93cml0ZRgHIAEoCEgDiAEBQhEKD19uZXR3b3JrX2FjY2Vzc0ITChFfZGVidWdfb3V0cHV0X2RpckITChFfYmxvY2tfZ2l0X3dyaXRlc0IUChJfZGlzYWJsZV90bXBfd3JpdGUi7wEKDVNlbGVjdGVkSW1hZ2USDAoEdXVpZBgCIAEoCRIMCgRwYXRoGAMgASgJEjQKCWRpbWVuc2lvbhgEIAEoCzIhLmFnZW50LnYxLlNlbGVjdGVkSW1hZ2VfRGltZW5zaW9uEhEKCW1pbWVfdHlwZRgHIAEoCRIRCgdibG9iX2lkGAEgASgMSAASDgoEZGF0YRgIIAEoDEgAEkMKEWJsb2JfaWRfd2l0aF9kYXRhGAkgASgLMiYuYWdlbnQudjEuU2VsZWN0ZWRJbWFnZV9CbG9iSWRXaXRoRGF0YUgAQhEKD2RhdGFfb3JfYmxvYl9pZCI9ChxTZWxlY3RlZEltYWdlX0Jsb2JJZFdpdGhEYXRhEg8KB2Jsb2JfaWQYASABKAwSDAoEZGF0YRgCIAEoDCI4ChdTZWxlY3RlZEltYWdlX0RpbWVuc2lvbhINCgV3aWR0aBgBIAEoBRIOCgZoZWlnaHQYAiABKAUiSQoRRXh0cmFDb250ZXh0RW50cnkSDgoEZGF0YRgBIAEoCUgAEhEKB2Jsb2JfaWQYAiABKAxIAEIRCg9kYXRhX29yX2Jsb2JfaWQiWwoMU2VsZWN0ZWRGaWxlEg8KB2NvbnRlbnQYASABKAkSDAoEcGF0aBgCIAEoCRIaCg1yZWxhdGl2ZV9wYXRoGAMgASgJSACIAQFCEAoOX3JlbGF0aXZlX3BhdGgihAEKFVNlbGVjdGVkQ29kZVNlbGVjdGlvbhIPCgdjb250ZW50GAEgASgJEgwKBHBhdGgYAiABKAkSGgoNcmVsYXRpdmVfcGF0aBgDIAEoCUgAiAEBEh4KBXJhbmdlGAQgASgLMg8uYWdlbnQudjEuUmFuZ2VCEAoOX3JlbGF0aXZlX3BhdGgiXQoQU2VsZWN0ZWRUZXJtaW5hbBIPCgdjb250ZW50GAEgASgJEhIKBXRpdGxlGAIgASgJSACIAQESEQoEcGF0aBgDIAEoCUgBiAEBQggKBl90aXRsZUIHCgVfcGF0aCKGAQoZU2VsZWN0ZWRUZXJtaW5hbFNlbGVjdGlvbhIPCgdjb250ZW50GAEgASgJEhIKBXRpdGxlGAIgASgJSACIAQESEQoEcGF0aBgDIAEoCUgBiAEBEh4KBXJhbmdlGAQgASgLMg8uYWdlbnQudjEuUmFuZ2VCCAoGX3RpdGxlQgcKBV9wYXRoIoMBCg5TZWxlY3RlZEZvbGRlchIMCgRwYXRoGAEgASgJEhoKDXJlbGF0aXZlX3BhdGgYAiABKAlIAIgBARI1Cg5kaXJlY3RvcnlfdHJlZRgDIAEoCzIdLmFnZW50LnYxLkxzRGlyZWN0b3J5VHJlZU5vZGVCEAoOX3JlbGF0aXZlX3BhdGginwEKFFNlbGVjdGVkRXh0ZXJuYWxMaW5rEgsKA3VybBgBIAEoCRIMCgR1dWlkGAIgASgJEhgKC3BkZl9jb250ZW50GAMgASgJSACIAQESEwoGaXNfcGRmGAQgASgISAGIAQESFQoIZmlsZW5hbWUYBSABKAlIAogBAUIOCgxfcGRmX2NvbnRlbnRCCQoHX2lzX3BkZkILCglfZmlsZW5hbWUiOAoSU2VsZWN0ZWRDdXJzb3JSdWxlEiIKBHJ1bGUYASABKAsyFC5hZ2VudC52MS5DdXJzb3JSdWxlIiIKD1NlbGVjdGVkR2l0RGlmZhIPCgdjb250ZW50GAEgASgJIjIKH1NlbGVjdGVkR2l0RGlmZkZyb21CcmFuY2hUb01haW4SDwoHY29udGVudBgBIAEoCSJpChFTZWxlY3RlZEdpdENvbW1pdBILCgNzaGEYASABKAkSDwoHbWVzc2FnZRgCIAEoCRIYCgtkZXNjcmlwdGlvbhgDIAEoCUgAiAEBEgwKBGRpZmYYBCABKAlCDgoMX2Rlc2NyaXB0aW9uIt0BChNTZWxlY3RlZFB1bGxSZXF1ZXN0Eg4KBm51bWJlchgBIAEoBRILCgN1cmwYAiABKAkSEgoFdGl0bGUYAyABKAlIAIgBARITCgtmb2xkZXJfcGF0aBgEIAEoCRIZCgxzdW1tYXJ5X2pzb24YBSABKAlIAYgBARIYCgtkZXNjcmlwdGlvbhgGIAEoCUgCiAEBEhQKB2Jsb2JfaWQYByABKAxIA4gBAUIICgZfdGl0bGVCDwoNX3N1bW1hcnlfanNvbkIOCgxfZGVzY3JpcHRpb25CCgoIX2Jsb2JfaWQiswEKGlNlbGVjdGVkR2l0UFJEaWZmU2VsZWN0aW9uEg4KBnByX3VybBgBIAEoCRIRCglmaWxlX3BhdGgYAiABKAkSEgoKc3RhcnRfbGluZRgDIAEoBRIQCghlbmRfbGluZRgEIAEoBRIZCgxkaWZmX2NvbnRlbnQYBSABKAlIAIgBARIUCgdibG9iX2lkGAYgASgMSAGIAQFCDwoNX2RpZmZfY29udGVudEIKCghfYmxvYl9pZCI2ChVTZWxlY3RlZEN1cnNvckNvbW1hbmQSDAoEbmFtZRgBIAEoCRIPCgdjb250ZW50GAIgASgJIjUKFVNlbGVjdGVkRG9jdW1lbnRhdGlvbhIOCgZkb2NfaWQYASABKAkSDAoEbmFtZRgCIAEoCSIyChBTZWxlY3RlZFBhc3RDaGF0EhAKCGFnZW50X2lkGAEgASgJEgwKBG5hbWUYAiABKAkiqwEKCUNhbGxGcmFtZRIaCg1mdW5jdGlvbl9uYW1lGAEgASgJSACIAQESEAoDdXJsGAIgASgJSAGIAQESGAoLbGluZV9udW1iZXIYAyABKAVIAogBARIaCg1jb2x1bW5fbnVtYmVyGAQgASgFSAOIAQFCEAoOX2Z1bmN0aW9uX25hbWVCBgoEX3VybEIOCgxfbGluZV9udW1iZXJCEAoOX2NvbHVtbl9udW1iZXIiaAoKU3RhY2tUcmFjZRIoCgtjYWxsX2ZyYW1lcxgBIAMoCzITLmFnZW50LnYxLkNhbGxGcmFtZRIcCg9yYXdfc3RhY2tfdHJhY2UYAiABKAlIAIgBAUISChBfcmF3X3N0YWNrX3RyYWNlIuQBChJTZWxlY3RlZENvbnNvbGVMb2cSDwoHbWVzc2FnZRgBIAEoCRIRCgl0aW1lc3RhbXAYAiABKAESDQoFbGV2ZWwYAyABKAkSEwoLY2xpZW50X25hbWUYBCABKAkSEgoKc2Vzc2lvbl9pZBgFIAEoCRIuCgtzdGFja190cmFjZRgGIAEoCzIULmFnZW50LnYxLlN0YWNrVHJhY2VIAIgBARIdChBvYmplY3RfZGF0YV9qc29uGAcgASgJSAGIAQFCDgoMX3N0YWNrX3RyYWNlQhMKEV9vYmplY3RfZGF0YV9qc29uIroBChFTZWxlY3RlZFVJRWxlbWVudBIPCgdlbGVtZW50GAEgASgJEg0KBXhwYXRoGAIgASgJEhQKDHRleHRfY29udGVudBgDIAEoCRINCgVleHRyYRgEIAEoCRIWCgljb21wb25lbnQYBSABKAlIAIgBARIhChRjb21wb25lbnRfcHJvcHNfanNvbhgGIAEoCUgBiAEBQgwKCl9jb21wb25lbnRCFwoVX2NvbXBvbmVudF9wcm9wc19qc29uIiAKEFNlbGVjdGVkU3ViYWdlbnQSDAoEbmFtZRgBIAEoCSKCCgoPU2VsZWN0ZWRDb250ZXh0EjAKD3NlbGVjdGVkX2ltYWdlcxgBIAMoCzIXLmFnZW50LnYxLlNlbGVjdGVkSW1hZ2USPAoSaW52b2NhdGlvbl9jb250ZXh0GAIgASgLMhsuYWdlbnQudjEuSW52b2NhdGlvbkNvbnRleHRIAIgBARIVCg1leHRyYV9jb250ZXh0GAMgAygJEjoKFWV4dHJhX2NvbnRleHRfZW50cmllcxgQIAMoCzIbLmFnZW50LnYxLkV4dHJhQ29udGV4dEVudHJ5EiUKBWZpbGVzGAQgAygLMhYuYWdlbnQudjEuU2VsZWN0ZWRGaWxlEjgKD2NvZGVfc2VsZWN0aW9ucxgFIAMoCzIfLmFnZW50LnYxLlNlbGVjdGVkQ29kZVNlbGVjdGlvbhItCgl0ZXJtaW5hbHMYBiADKAsyGi5hZ2VudC52MS5TZWxlY3RlZFRlcm1pbmFsEkAKE3Rlcm1pbmFsX3NlbGVjdGlvbnMYByADKAsyIy5hZ2VudC52MS5TZWxlY3RlZFRlcm1pbmFsU2VsZWN0aW9uEikKB2ZvbGRlcnMYCCADKAsyGC5hZ2VudC52MS5TZWxlY3RlZEZvbGRlchI2Cg5leHRlcm5hbF9saW5rcxgJIAMoCzIeLmFnZW50LnYxLlNlbGVjdGVkRXh0ZXJuYWxMaW5rEjIKDGN1cnNvcl9ydWxlcxgKIAMoCzIcLmFnZW50LnYxLlNlbGVjdGVkQ3Vyc29yUnVsZRIwCghnaXRfZGlmZhgSIAEoCzIZLmFnZW50LnYxLlNlbGVjdGVkR2l0RGlmZkgBiAEBElQKHGdpdF9kaWZmX2Zyb21fYnJhbmNoX3RvX21haW4YCyABKAsyKS5hZ2VudC52MS5TZWxlY3RlZEdpdERpZmZGcm9tQnJhbmNoVG9NYWluSAKIAQESOAoPY3Vyc29yX2NvbW1hbmRzGAwgAygLMh8uYWdlbnQudjEuU2VsZWN0ZWRDdXJzb3JDb21tYW5kEjcKDmRvY3VtZW50YXRpb25zGA0gAygLMh8uYWdlbnQudjEuU2VsZWN0ZWREb2N1bWVudGF0aW9uEjAKC3VpX2VsZW1lbnRzGA4gAygLMhsuYWdlbnQudjEuU2VsZWN0ZWRVSUVsZW1lbnQSMgoMY29uc29sZV9sb2dzGA8gAygLMhwuYWdlbnQudjEuU2VsZWN0ZWRDb25zb2xlTG9nEjAKC2dpdF9jb21taXRzGBEgAygLMhsuYWdlbnQudjEuU2VsZWN0ZWRHaXRDb21taXQSLgoKcGFzdF9jaGF0cxgTIAMoCzIaLmFnZW50LnYxLlNlbGVjdGVkUGFzdENoYXQSRAoWZ2l0X3ByX2RpZmZfc2VsZWN0aW9ucxgUIAMoCzIkLmFnZW50LnYxLlNlbGVjdGVkR2l0UFJEaWZmU2VsZWN0aW9uEj0KFnNlbGVjdGVkX3B1bGxfcmVxdWVzdHMYFSADKAsyHS5hZ2VudC52MS5TZWxlY3RlZFB1bGxSZXF1ZXN0EjYKEnNlbGVjdGVkX3N1YmFnZW50cxgWIAMoCzIaLmFnZW50LnYxLlNlbGVjdGVkU3ViYWdlbnRCFQoTX2ludm9jYXRpb25fY29udGV4dEILCglfZ2l0X2RpZmZCHwodX2dpdF9kaWZmX2Zyb21fYnJhbmNoX3RvX21haW4i5QEKEUludm9jYXRpb25Db250ZXh0Ej8KDHNsYWNrX3RocmVhZBgBIAEoCzInLmFnZW50LnYxLkludm9jYXRpb25Db250ZXh0X1NsYWNrVGhyZWFkSAASOQoJZ2l0aHViX3ByGAIgASgLMiQuYWdlbnQudjEuSW52b2NhdGlvbkNvbnRleHRfR2l0aHViUFJIABI5CglpZGVfc3RhdGUYAyABKAsyJC5hZ2VudC52MS5JbnZvY2F0aW9uQ29udGV4dF9JZGVTdGF0ZUgAEhEKB2Jsb2JfaWQYCiABKAxIAEIGCgRkYXRhIrsBCh1JbnZvY2F0aW9uQ29udGV4dF9TbGFja1RocmVhZBIOCgZ0aHJlYWQYASABKAkSGQoMY2hhbm5lbF9uYW1lGAIgASgJSACIAQESHAoPY2hhbm5lbF9wdXJwb3NlGAMgASgJSAGIAQESGgoNY2hhbm5lbF90b3BpYxgEIAEoCUgCiAEBQg8KDV9jaGFubmVsX25hbWVCEgoQX2NoYW5uZWxfcHVycG9zZUIQCg5fY2hhbm5lbF90b3BpYyJ8ChpJbnZvY2F0aW9uQ29udGV4dF9HaXRodWJQUhINCgV0aXRsZRgBIAEoCRITCgtkZXNjcmlwdGlvbhgCIAEoCRIQCghjb21tZW50cxgDIAEoCRIYCgtjaV9mYWlsdXJlcxgEIAEoCUgAiAEBQg4KDF9jaV9mYWlsdXJlcyL+AQoaSW52b2NhdGlvbkNvbnRleHRfSWRlU3RhdGUSQAoNdmlzaWJsZV9maWxlcxgBIAMoCzIpLmFnZW50LnYxLkludm9jYXRpb25Db250ZXh0X0lkZVN0YXRlX0ZpbGUSSAoVcmVjZW50bHlfdmlld2VkX2ZpbGVzGAIgAygLMikuYWdlbnQudjEuSW52b2NhdGlvbkNvbnRleHRfSWRlU3RhdGVfRmlsZRJUChRjdXJyZW50bHlfdmlld2VkX3BycxgDIAMoCzI2LmFnZW50LnYxLkludm9jYXRpb25Db250ZXh0X0lkZVN0YXRlX1ZpZXdlZFB1bGxSZXF1ZXN0Io4CCh9JbnZvY2F0aW9uQ29udGV4dF9JZGVTdGF0ZV9GaWxlEgwKBHBhdGgYASABKAkSGgoNcmVsYXRpdmVfcGF0aBgCIAEoCUgAiAEBElYKD2N1cnNvcl9wb3NpdGlvbhgDIAEoCzI4LmFnZW50LnYxLkludm9jYXRpb25Db250ZXh0X0lkZVN0YXRlX0ZpbGVfQ3Vyc29yUG9zaXRpb25IAYgBARITCgt0b3RhbF9saW5lcxgEIAEoBRIbCg5hY3RpdmVfY29tbWFuZBgFIAEoCUgCiAEBQhAKDl9yZWxhdGl2ZV9wYXRoQhIKEF9jdXJzb3JfcG9zaXRpb25CEQoPX2FjdGl2ZV9jb21tYW5kIkwKLkludm9jYXRpb25Db250ZXh0X0lkZVN0YXRlX0ZpbGVfQ3Vyc29yUG9zaXRpb24SDAoEbGluZRgBIAEoBRIMCgR0ZXh0GAIgASgJIukBCixJbnZvY2F0aW9uQ29udGV4dF9JZGVTdGF0ZV9WaWV3ZWRQdWxsUmVxdWVzdBIOCgZudW1iZXIYASABKAUSCwoDdXJsGAIgASgJEhIKBXRpdGxlGAMgASgJSACIAQESGAoLZm9sZGVyX3BhdGgYBCABKAlIAYgBARIZCgxzdW1tYXJ5X2pzb24YBSABKAlIAogBARIYCgtkZXNjcmlwdGlvbhgGIAEoCUgDiAEBQggKBl90aXRsZUIOCgxfZm9sZGVyX3BhdGhCDwoNX3N1bW1hcnlfanNvbkIOCgxfZGVzY3JpcHRpb24iSAoWU2V0dXBWbUVudmlyb25tZW50QXJncxIXCg9pbnN0YWxsX2NvbW1hbmQYAiABKAkSFQoNc3RhcnRfY29tbWFuZBgDIAEoCSJcChhTZXR1cFZtRW52aXJvbm1lbnRSZXN1bHQSNgoHc3VjY2VzcxgBIAEoCzIjLmFnZW50LnYxLlNldHVwVm1FbnZpcm9ubWVudFN1Y2Nlc3NIAEIICgZyZXN1bHQiGwoZU2V0dXBWbUVudmlyb25tZW50U3VjY2VzcyKAAQoaU2V0dXBWbUVudmlyb25tZW50VG9vbENhbGwSLgoEYXJncxgBIAEoCzIgLmFnZW50LnYxLlNldHVwVm1FbnZpcm9ubWVudEFyZ3MSMgoGcmVzdWx0GAIgASgLMiIuYWdlbnQudjEuU2V0dXBWbUVudmlyb25tZW50UmVzdWx0IsABChlTaGVsbENvbW1hbmRQYXJzaW5nUmVzdWx0EhYKDnBhcnNpbmdfZmFpbGVkGAEgASgIElIKE2V4ZWN1dGFibGVfY29tbWFuZHMYAiADKAsyNS5hZ2VudC52MS5TaGVsbENvbW1hbmRQYXJzaW5nUmVzdWx0X0V4ZWN1dGFibGVDb21tYW5kEhUKDWhhc19yZWRpcmVjdHMYAyABKAgSIAoYaGFzX2NvbW1hbmRfc3Vic3RpdHV0aW9uGAQgASgIIk0KLlNoZWxsQ29tbWFuZFBhcnNpbmdSZXN1bHRfRXhlY3V0YWJsZUNvbW1hbmRBcmcSDAoEdHlwZRgBIAEoCRINCgV2YWx1ZRgCIAEoCSKWAQorU2hlbGxDb21tYW5kUGFyc2luZ1Jlc3VsdF9FeGVjdXRhYmxlQ29tbWFuZBIMCgRuYW1lGAEgASgJEkYKBGFyZ3MYAiADKAsyOC5hZ2VudC52MS5TaGVsbENvbW1hbmRQYXJzaW5nUmVzdWx0X0V4ZWN1dGFibGVDb21tYW5kQXJnEhEKCWZ1bGxfdGV4dBgDIAEoCSKIBAoJU2hlbGxBcmdzEg8KB2NvbW1hbmQYASABKAkSGQoRd29ya2luZ19kaXJlY3RvcnkYAiABKAkSDwoHdGltZW91dBgDIAEoBRIUCgx0b29sX2NhbGxfaWQYBCABKAkSFwoPc2ltcGxlX2NvbW1hbmRzGAUgAygJEhoKEmhhc19pbnB1dF9yZWRpcmVjdBgGIAEoCBIbChNoYXNfb3V0cHV0X3JlZGlyZWN0GAcgASgIEjsKDnBhcnNpbmdfcmVzdWx0GAggASgLMiMuYWdlbnQudjEuU2hlbGxDb21tYW5kUGFyc2luZ1Jlc3VsdBI+ChhyZXF1ZXN0ZWRfc2FuZGJveF9wb2xpY3kYCSABKAsyFy5hZ2VudC52MS5TYW5kYm94UG9saWN5SACIAQESKAobZmlsZV9vdXRwdXRfdGhyZXNob2xkX2J5dGVzGAogASgESAGIAQESFQoNaXNfYmFja2dyb3VuZBgLIAEoCBIVCg1za2lwX2FwcHJvdmFsGAwgASgIEhgKEHRpbWVvdXRfYmVoYXZpb3IYDSABKAUSGQoMaGFyZF90aW1lb3V0GA4gASgFSAKIAQFCGwoZX3JlcXVlc3RlZF9zYW5kYm94X3BvbGljeUIeChxfZmlsZV9vdXRwdXRfdGhyZXNob2xkX2J5dGVzQg8KDV9oYXJkX3RpbWVvdXQi+gMKC1NoZWxsUmVzdWx0EjQKDnNhbmRib3hfcG9saWN5GGUgASgLMhcuYWdlbnQudjEuU2FuZGJveFBvbGljeUgBiAEBEhoKDWlzX2JhY2tncm91bmQYZiABKAhIAogBARIdChB0ZXJtaW5hbHNfZm9sZGVyGGcgASgJSAOIAQESEAoDcGlkGGggASgNSASIAQESKQoHc3VjY2VzcxgBIAEoCzIWLmFnZW50LnYxLlNoZWxsU3VjY2Vzc0gAEikKB2ZhaWx1cmUYAiABKAsyFi5hZ2VudC52MS5TaGVsbEZhaWx1cmVIABIpCgd0aW1lb3V0GAMgASgLMhYuYWdlbnQudjEuU2hlbGxUaW1lb3V0SAASKwoIcmVqZWN0ZWQYBCABKAsyFy5hZ2VudC52MS5TaGVsbFJlamVjdGVkSAASMAoLc3Bhd25fZXJyb3IYBSABKAsyGS5hZ2VudC52MS5TaGVsbFNwYXduRXJyb3JIABI8ChFwZXJtaXNzaW9uX2RlbmllZBgHIAEoCzIfLmFnZW50LnYxLlNoZWxsUGVybWlzc2lvbkRlbmllZEgAQggKBnJlc3VsdEIRCg9fc2FuZGJveF9wb2xpY3lCEAoOX2lzX2JhY2tncm91bmRCEwoRX3Rlcm1pbmFsc19mb2xkZXJCBgoEX3BpZCIhChFTaGVsbFN0cmVhbVN0ZG91dBIMCgRkYXRhGAEgASgJIiEKEVNoZWxsU3RyZWFtU3RkZXJyEgwKBGRhdGEYASABKAkitQEKD1NoZWxsU3RyZWFtRXhpdBIMCgRjb2RlGAEgASgNEgsKA2N3ZBgCIAEoCRI2Cg9vdXRwdXRfbG9jYXRpb24YAyABKAsyGC5hZ2VudC52MS5PdXRwdXRMb2NhdGlvbkgAiAEBEg8KB2Fib3J0ZWQYBCABKAgSGQoMYWJvcnRfcmVhc29uGAUgASgFSAGIAQFCEgoQX291dHB1dF9sb2NhdGlvbkIPCg1fYWJvcnRfcmVhc29uIlsKEFNoZWxsU3RyZWFtU3RhcnQSNAoOc2FuZGJveF9wb2xpY3kYASABKAsyFy5hZ2VudC52MS5TYW5kYm94UG9saWN5SACIAQFCEQoPX3NhbmRib3hfcG9saWN5IpkBChdTaGVsbFN0cmVhbUJhY2tncm91bmRlZBIQCghzaGVsbF9pZBgBIAEoDRIPCgdjb21tYW5kGAIgASgJEhkKEXdvcmtpbmdfZGlyZWN0b3J5GAMgASgJEhAKA3BpZBgEIAEoDUgAiAEBEhcKCm1zX3RvX3dhaXQYBSABKAVIAYgBAUIGCgRfcGlkQg0KC19tc190b193YWl0IvICCgtTaGVsbFN0cmVhbRItCgZzdGRvdXQYASABKAsyGy5hZ2VudC52MS5TaGVsbFN0cmVhbVN0ZG91dEgAEi0KBnN0ZGVychgCIAEoCzIbLmFnZW50LnYxLlNoZWxsU3RyZWFtU3RkZXJySAASKQoEZXhpdBgDIAEoCzIZLmFnZW50LnYxLlNoZWxsU3RyZWFtRXhpdEgAEisKBXN0YXJ0GAQgASgLMhouYWdlbnQudjEuU2hlbGxTdHJlYW1TdGFydEgAEisKCHJlamVjdGVkGAUgASgLMhcuYWdlbnQudjEuU2hlbGxSZWplY3RlZEgAEjwKEXBlcm1pc3Npb25fZGVuaWVkGAYgASgLMh8uYWdlbnQudjEuU2hlbGxQZXJtaXNzaW9uRGVuaWVkSAASOQoMYmFja2dyb3VuZGVkGAcgASgLMiEuYWdlbnQudjEuU2hlbGxTdHJlYW1CYWNrZ3JvdW5kZWRIAEIHCgVldmVudCJLCg5PdXRwdXRMb2NhdGlvbhIRCglmaWxlX3BhdGgYASABKAkSEgoKc2l6ZV9ieXRlcxgCIAEoAxISCgpsaW5lX2NvdW50GAMgASgDIv8CCgxTaGVsbFN1Y2Nlc3MSDwoHY29tbWFuZBgBIAEoCRIZChF3b3JraW5nX2RpcmVjdG9yeRgCIAEoCRIRCglleGl0X2NvZGUYAyABKAUSDgoGc2lnbmFsGAQgASgJEg4KBnN0ZG91dBgFIAEoCRIOCgZzdGRlcnIYBiABKAkSFgoOZXhlY3V0aW9uX3RpbWUYByABKAUSNgoPb3V0cHV0X2xvY2F0aW9uGAggASgLMhguYWdlbnQudjEuT3V0cHV0TG9jYXRpb25IAIgBARIVCghzaGVsbF9pZBgJIAEoDUgBiAEBEh8KEmludGVybGVhdmVkX291dHB1dBgKIAEoCUgCiAEBEhAKA3BpZBgLIAEoDUgDiAEBEhcKCm1zX3RvX3dhaXQYDCABKAVIBIgBAUISChBfb3V0cHV0X2xvY2F0aW9uQgsKCV9zaGVsbF9pZEIVChNfaW50ZXJsZWF2ZWRfb3V0cHV0QgYKBF9waWRCDQoLX21zX3RvX3dhaXQi1gIKDFNoZWxsRmFpbHVyZRIPCgdjb21tYW5kGAEgASgJEhkKEXdvcmtpbmdfZGlyZWN0b3J5GAIgASgJEhEKCWV4aXRfY29kZRgDIAEoBRIOCgZzaWduYWwYBCABKAkSDgoGc3Rkb3V0GAUgASgJEg4KBnN0ZGVychgGIAEoCRIWCg5leGVjdXRpb25fdGltZRgHIAEoBRI2Cg9vdXRwdXRfbG9jYXRpb24YCCABKAsyGC5hZ2VudC52MS5PdXRwdXRMb2NhdGlvbkgAiAEBEh8KEmludGVybGVhdmVkX291dHB1dBgJIAEoCUgBiAEBEhkKDGFib3J0X3JlYXNvbhgKIAEoBUgCiAEBEg8KB2Fib3J0ZWQYCyABKAhCEgoQX291dHB1dF9sb2NhdGlvbkIVChNfaW50ZXJsZWF2ZWRfb3V0cHV0Qg8KDV9hYm9ydF9yZWFzb24iTgoMU2hlbGxUaW1lb3V0Eg8KB2NvbW1hbmQYASABKAkSGQoRd29ya2luZ19kaXJlY3RvcnkYAiABKAkSEgoKdGltZW91dF9tcxgDIAEoBSJgCg1TaGVsbFJlamVjdGVkEg8KB2NvbW1hbmQYASABKAkSGQoRd29ya2luZ19kaXJlY3RvcnkYAiABKAkSDgoGcmVhc29uGAMgASgJEhMKC2lzX3JlYWRvbmx5GAQgASgIImcKFVNoZWxsUGVybWlzc2lvbkRlbmllZBIPCgdjb21tYW5kGAEgASgJEhkKEXdvcmtpbmdfZGlyZWN0b3J5GAIgASgJEg0KBWVycm9yGAMgASgJEhMKC2lzX3JlYWRvbmx5GAQgASgIIkwKD1NoZWxsU3Bhd25FcnJvchIPCgdjb21tYW5kGAEgASgJEhkKEXdvcmtpbmdfZGlyZWN0b3J5GAIgASgJEg0KBWVycm9yGAMgASgJIkAKElNoZWxsUGFydGlhbFJlc3VsdBIUCgxzdGRvdXRfZGVsdGEYASABKAkSFAoMc3RkZXJyX2RlbHRhGAIgASgJIlkKDVNoZWxsVG9vbENhbGwSIQoEYXJncxgBIAEoCzITLmFnZW50LnYxLlNoZWxsQXJncxIlCgZyZXN1bHQYAiABKAsyFS5hZ2VudC52MS5TaGVsbFJlc3VsdCIrChhTaGVsbFRvb2xDYWxsU3Rkb3V0RGVsdGESDwoHY29udGVudBgBIAEoCSIrChhTaGVsbFRvb2xDYWxsU3RkZXJyRGVsdGESDwoHY29udGVudBgBIAEoCSKJAQoSU2hlbGxUb29sQ2FsbERlbHRhEjQKBnN0ZG91dBgBIAEoCzIiLmFnZW50LnYxLlNoZWxsVG9vbENhbGxTdGRvdXREZWx0YUgAEjQKBnN0ZGVychgCIAEoCzIiLmFnZW50LnYxLlNoZWxsVG9vbENhbGxTdGRlcnJEZWx0YUgAQgcKBWRlbHRhIu0BCgxTdWJhZ2VudFR5cGUSOAoLdW5zcGVjaWZpZWQYASABKAsyIS5hZ2VudC52MS5TdWJhZ2VudFR5cGVVbnNwZWNpZmllZEgAEjkKDGNvbXB1dGVyX3VzZRgCIAEoCzIhLmFnZW50LnYxLlN1YmFnZW50VHlwZUNvbXB1dGVyVXNlSAASLgoGY3VzdG9tGAMgASgLMhwuYWdlbnQudjEuU3ViYWdlbnRUeXBlQ3VzdG9tSAASMAoHZXhwbG9yZRgEIAEoCzIdLmFnZW50LnYxLlN1YmFnZW50VHlwZUV4cGxvcmVIAEIGCgR0eXBlIhkKF1N1YmFnZW50VHlwZVVuc3BlY2lmaWVkIhkKF1N1YmFnZW50VHlwZUNvbXB1dGVyVXNlIhUKE1N1YmFnZW50VHlwZUV4cGxvcmUiIgoSU3ViYWdlbnRUeXBlQ3VzdG9tEgwKBG5hbWUYASABKAkijQEKDkN1c3RvbVN1YmFnZW50EhEKCWZ1bGxfcGF0aBgBIAEoCRIMCgRuYW1lGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJEg0KBXRvb2xzGAQgAygJEg0KBW1vZGVsGAUgASgJEg4KBnByb21wdBgGIAEoCRIXCg9wZXJtaXNzaW9uX21vZGUYByABKAUiaAoOU3dpdGNoTW9kZUFyZ3MSFgoOdGFyZ2V0X21vZGVfaWQYASABKAkSGAoLZXhwbGFuYXRpb24YAiABKAlIAIgBARIUCgx0b29sX2NhbGxfaWQYAyABKAlCDgoMX2V4cGxhbmF0aW9uIqoBChBTd2l0Y2hNb2RlUmVzdWx0Ei4KB3N1Y2Nlc3MYASABKAsyGy5hZ2VudC52MS5Td2l0Y2hNb2RlU3VjY2Vzc0gAEioKBWVycm9yGAIgASgLMhkuYWdlbnQudjEuU3dpdGNoTW9kZUVycm9ySAASMAoIcmVqZWN0ZWQYAyABKAsyHC5hZ2VudC52MS5Td2l0Y2hNb2RlUmVqZWN0ZWRIAEIICgZyZXN1bHQiPQoRU3dpdGNoTW9kZVN1Y2Nlc3MSFAoMZnJvbV9tb2RlX2lkGAEgASgJEhIKCnRvX21vZGVfaWQYAiABKAkiIAoPU3dpdGNoTW9kZUVycm9yEg0KBWVycm9yGAEgASgJIiQKElN3aXRjaE1vZGVSZWplY3RlZBIOCgZyZWFzb24YASABKAkiaAoSU3dpdGNoTW9kZVRvb2xDYWxsEiYKBGFyZ3MYASABKAsyGC5hZ2VudC52MS5Td2l0Y2hNb2RlQXJncxIqCgZyZXN1bHQYAiABKAsyGi5hZ2VudC52MS5Td2l0Y2hNb2RlUmVzdWx0IkAKFlN3aXRjaE1vZGVSZXF1ZXN0UXVlcnkSJgoEYXJncxgBIAEoCzIYLmFnZW50LnYxLlN3aXRjaE1vZGVBcmdzIqkBChlTd2l0Y2hNb2RlUmVxdWVzdFJlc3BvbnNlEkAKCGFwcHJvdmVkGAEgASgLMiwuYWdlbnQudjEuU3dpdGNoTW9kZVJlcXVlc3RSZXNwb25zZV9BcHByb3ZlZEgAEkAKCHJlamVjdGVkGAIgASgLMiwuYWdlbnQudjEuU3dpdGNoTW9kZVJlcXVlc3RSZXNwb25zZV9SZWplY3RlZEgAQggKBnJlc3VsdCIkCiJTd2l0Y2hNb2RlUmVxdWVzdFJlc3BvbnNlX0FwcHJvdmVkIjQKIlN3aXRjaE1vZGVSZXF1ZXN0UmVzcG9uc2VfUmVqZWN0ZWQSDgoGcmVhc29uGAEgASgJInUKCFRvZG9JdGVtEgoKAmlkGAEgASgJEg8KB2NvbnRlbnQYAiABKAkSDgoGc3RhdHVzGAMgASgFEhIKCmNyZWF0ZWRfYXQYBCABKAMSEgoKdXBkYXRlZF9hdBgFIAEoAxIUCgxkZXBlbmRlbmNpZXMYBiADKAkiawoTVXBkYXRlVG9kb3NUb29sQ2FsbBInCgRhcmdzGAEgASgLMhkuYWdlbnQudjEuVXBkYXRlVG9kb3NBcmdzEisKBnJlc3VsdBgCIAEoCzIbLmFnZW50LnYxLlVwZGF0ZVRvZG9zUmVzdWx0IkMKD1VwZGF0ZVRvZG9zQXJncxIhCgV0b2RvcxgBIAMoCzISLmFnZW50LnYxLlRvZG9JdGVtEg0KBW1lcmdlGAIgASgIInsKEVVwZGF0ZVRvZG9zUmVzdWx0Ei8KB3N1Y2Nlc3MYASABKAsyHC5hZ2VudC52MS5VcGRhdGVUb2Rvc1N1Y2Nlc3NIABIrCgVlcnJvchgCIAEoCzIaLmFnZW50LnYxLlVwZGF0ZVRvZG9zRXJyb3JIAEIICgZyZXN1bHQiXwoSVXBkYXRlVG9kb3NTdWNjZXNzEiEKBXRvZG9zGAEgAygLMhIuYWdlbnQudjEuVG9kb0l0ZW0SEwoLdG90YWxfY291bnQYAiABKAUSEQoJd2FzX21lcmdlGAMgASgIIiEKEFVwZGF0ZVRvZG9zRXJyb3ISDQoFZXJyb3IYASABKAkiZQoRUmVhZFRvZG9zVG9vbENhbGwSJQoEYXJncxgBIAEoCzIXLmFnZW50LnYxLlJlYWRUb2Rvc0FyZ3MSKQoGcmVzdWx0GAIgASgLMhkuYWdlbnQudjEuUmVhZFRvZG9zUmVzdWx0IjkKDVJlYWRUb2Rvc0FyZ3MSFQoNc3RhdHVzX2ZpbHRlchgBIAMoBRIRCglpZF9maWx0ZXIYAiADKAkidQoPUmVhZFRvZG9zUmVzdWx0Ei0KB3N1Y2Nlc3MYASABKAsyGi5hZ2VudC52MS5SZWFkVG9kb3NTdWNjZXNzSAASKQoFZXJyb3IYAiABKAsyGC5hZ2VudC52MS5SZWFkVG9kb3NFcnJvckgAQggKBnJlc3VsdCJKChBSZWFkVG9kb3NTdWNjZXNzEiEKBXRvZG9zGAEgAygLMhIuYWdlbnQudjEuVG9kb0l0ZW0SEwoLdG90YWxfY291bnQYAiABKAUiHwoOUmVhZFRvZG9zRXJyb3ISDQoFZXJyb3IYASABKAkiSwoFUmFuZ2USIQoFc3RhcnQYASABKAsyEi5hZ2VudC52MS5Qb3NpdGlvbhIfCgNlbmQYAiABKAsyEi5hZ2VudC52MS5Qb3NpdGlvbiIoCghQb3NpdGlvbhIMCgRsaW5lGAEgASgNEg4KBmNvbHVtbhgCIAEoDSIYCgVFcnJvchIPCgdtZXNzYWdlGAEgASgJIjoKDVdlYlNlYXJjaEFyZ3MSEwoLc2VhcmNoX3Rlcm0YASABKAkSFAoMdG9vbF9jYWxsX2lkGAIgASgJIqYBCg9XZWJTZWFyY2hSZXN1bHQSLQoHc3VjY2VzcxgBIAEoCzIaLmFnZW50LnYxLldlYlNlYXJjaFN1Y2Nlc3NIABIpCgVlcnJvchgCIAEoCzIYLmFnZW50LnYxLldlYlNlYXJjaEVycm9ySAASLwoIcmVqZWN0ZWQYAyABKAsyGy5hZ2VudC52MS5XZWJTZWFyY2hSZWplY3RlZEgAQggKBnJlc3VsdCJEChBXZWJTZWFyY2hTdWNjZXNzEjAKCnJlZmVyZW5jZXMYASADKAsyHC5hZ2VudC52MS5XZWJTZWFyY2hSZWZlcmVuY2UiHwoOV2ViU2VhcmNoRXJyb3ISDQoFZXJyb3IYASABKAkiIwoRV2ViU2VhcmNoUmVqZWN0ZWQSDgoGcmVhc29uGAEgASgJIj8KEldlYlNlYXJjaFJlZmVyZW5jZRINCgV0aXRsZRgBIAEoCRILCgN1cmwYAiABKAkSDQoFY2h1bmsYAyABKAkiZQoRV2ViU2VhcmNoVG9vbENhbGwSJQoEYXJncxgBIAEoCzIXLmFnZW50LnYxLldlYlNlYXJjaEFyZ3MSKQoGcmVzdWx0GAIgASgLMhkuYWdlbnQudjEuV2ViU2VhcmNoUmVzdWx0Ij4KFVdlYlNlYXJjaFJlcXVlc3RRdWVyeRIlCgRhcmdzGAEgASgLMhcuYWdlbnQudjEuV2ViU2VhcmNoQXJncyKmAQoYV2ViU2VhcmNoUmVxdWVzdFJlc3BvbnNlEj8KCGFwcHJvdmVkGAEgASgLMisuYWdlbnQudjEuV2ViU2VhcmNoUmVxdWVzdFJlc3BvbnNlX0FwcHJvdmVkSAASPwoIcmVqZWN0ZWQYAiABKAsyKy5hZ2VudC52MS5XZWJTZWFyY2hSZXF1ZXN0UmVzcG9uc2VfUmVqZWN0ZWRIAEIICgZyZXN1bHQiIwohV2ViU2VhcmNoUmVxdWVzdFJlc3BvbnNlX0FwcHJvdmVkIjMKIVdlYlNlYXJjaFJlcXVlc3RSZXNwb25zZV9SZWplY3RlZBIOCgZyZWFzb24YASABKAkifwoJV3JpdGVBcmdzEgwKBHBhdGgYASABKAkSEQoJZmlsZV90ZXh0GAIgASgJEhQKDHRvb2xfY2FsbF9pZBgDIAEoCRInCh9yZXR1cm5fZmlsZV9jb250ZW50X2FmdGVyX3dyaXRlGAQgASgIEhIKCmZpbGVfYnl0ZXMYBSABKAwigAIKC1dyaXRlUmVzdWx0EikKB3N1Y2Nlc3MYASABKAsyFi5hZ2VudC52MS5Xcml0ZVN1Y2Nlc3NIABI8ChFwZXJtaXNzaW9uX2RlbmllZBgDIAEoCzIfLmFnZW50LnYxLldyaXRlUGVybWlzc2lvbkRlbmllZEgAEioKCG5vX3NwYWNlGAQgASgLMhYuYWdlbnQudjEuV3JpdGVOb1NwYWNlSAASJQoFZXJyb3IYBSABKAsyFC5hZ2VudC52MS5Xcml0ZUVycm9ySAASKwoIcmVqZWN0ZWQYBiABKAsyFy5hZ2VudC52MS5Xcml0ZVJlamVjdGVkSABCCAoGcmVzdWx0IooBCgxXcml0ZVN1Y2Nlc3MSDAoEcGF0aBgBIAEoCRIVCg1saW5lc19jcmVhdGVkGAIgASgFEhEKCWZpbGVfc2l6ZRgDIAEoBRIlChhmaWxlX2NvbnRlbnRfYWZ0ZXJfd3JpdGUYBCABKAlIAIgBAUIbChlfZmlsZV9jb250ZW50X2FmdGVyX3dyaXRlIm8KFVdyaXRlUGVybWlzc2lvbkRlbmllZBIMCgRwYXRoGAEgASgJEhEKCWRpcmVjdG9yeRgCIAEoCRIRCglvcGVyYXRpb24YAyABKAkSDQoFZXJyb3IYBCABKAkSEwoLaXNfcmVhZG9ubHkYBSABKAgiHAoMV3JpdGVOb1NwYWNlEgwKBHBhdGgYASABKAkiKQoKV3JpdGVFcnJvchIMCgRwYXRoGAEgASgJEg0KBWVycm9yGAIgASgJIi0KDVdyaXRlUmVqZWN0ZWQSDAoEcGF0aBgBIAEoCRIOCgZyZWFzb24YAiABKAkigwEKF0Jvb3RzdHJhcFN0YXRzaWdSZXF1ZXN0Eh4KEWlnbm9yZV9kZXZfc3RhdHVzGAEgASgISACIAQESHQoQb3BlcmF0aW5nX3N5c3RlbRgCIAEoBUgBiAEBQhQKEl9pZ25vcmVfZGV2X3N0YXR1c0ITChFfb3BlcmF0aW5nX3N5c3RlbSIOCgxQaW5nUmVzcG9uc2UitwEKC0V4ZWNSZXF1ZXN0Eg8KB2NvbW1hbmQYASABKAkSEAoDY3dkGAIgASgJSACIAQESDAoEYXJncxgDIAMoCRI7CgtlbnZpcm9ubWVudBgEIAMoCzImLmFnZW50LnYxLkV4ZWNSZXF1ZXN0LkVudmlyb25tZW50RW50cnkaMgoQRW52aXJvbm1lbnRFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBQgYKBF9jd2QioAEKDEV4ZWNSZXNwb25zZRItCgxzdGRvdXRfZXZlbnQYASABKAsyFS5hZ2VudC52MS5TdGRvdXRFdmVudEgAEi0KDHN0ZGVycl9ldmVudBgCIAEoCzIVLmFnZW50LnYxLlN0ZGVyckV2ZW50SAASKQoKZXhpdF9ldmVudBgDIAEoCzITLmFnZW50LnYxLkV4aXRFdmVudEgAQgcKBWV2ZW50IhsKC1N0ZG91dEV2ZW50EgwKBGRhdGEYASABKAkiGwoLU3RkZXJyRXZlbnQSDAoEZGF0YRgBIAEoCSIeCglFeGl0RXZlbnQSEQoJZXhpdF9jb2RlGAEgASgFIiMKE1JlYWRUZXh0RmlsZVJlcXVlc3QSDAoEcGF0aBgBIAEoCSInChRSZWFkVGV4dEZpbGVSZXNwb25zZRIPCgdjb250ZW50GAEgASgJIjUKFFdyaXRlVGV4dEZpbGVSZXF1ZXN0EgwKBHBhdGgYASABKAkSDwoHY29udGVudBgCIAEoCSIXChVXcml0ZVRleHRGaWxlUmVzcG9uc2UiJQoVUmVhZEJpbmFyeUZpbGVSZXF1ZXN0EgwKBHBhdGgYASABKAkiKQoWUmVhZEJpbmFyeUZpbGVSZXNwb25zZRIPCgdjb250ZW50GAEgASgMIjcKFldyaXRlQmluYXJ5RmlsZVJlcXVlc3QSDAoEcGF0aBgBIAEoCRIPCgdjb250ZW50GAIgASgMIhkKF1dyaXRlQmluYXJ5RmlsZVJlc3BvbnNlIkUKHkdldFdvcmtzcGFjZUNoYW5nZXNIYXNoUmVxdWVzdBIRCglyb290X3BhdGgYASABKAkSEAoIYmFzZV9yZWYYAiABKAkiLwofR2V0V29ya3NwYWNlQ2hhbmdlc0hhc2hSZXNwb25zZRIMCgRoYXNoGAEgASgJIlAKH1JlZnJlc2hHaXRodWJBY2Nlc3NUb2tlblJlcXVlc3QSGwoTZ2l0aHViX2FjY2Vzc190b2tlbhgBIAEoCRIQCghob3N0bmFtZRgCIAEoCSIiCiBSZWZyZXNoR2l0aHViQWNjZXNzVG9rZW5SZXNwb25zZSJXCh1XYXJtUmVtb3RlQWNjZXNzU2VydmVyUmVxdWVzdBIOCgZjb21taXQYASABKAkSDAoEcG9ydBgCIAEoBRIYChBjb25uZWN0aW9uX3Rva2VuGAMgASgJIiAKHldhcm1SZW1vdGVBY2Nlc3NTZXJ2ZXJSZXNwb25zZSIWChRMaXN0QXJ0aWZhY3RzUmVxdWVzdCKKAgoWQXJ0aWZhY3RVcGxvYWRNZXRhZGF0YRIVCg1hYnNvbHV0ZV9wYXRoGAEgASgJEhIKCnNpemVfYnl0ZXMYAiABKAQSGgoSdXBkYXRlZF9hdF91bml4X21zGAMgASgDEg4KBnN0YXR1cxgEIAEoBRIWCg5ieXRlc191cGxvYWRlZBgFIAEoBBISCgpsYXN0X2Vycm9yGAYgASgJEhcKD3VwbG9hZF9hdHRlbXB0cxgHIAEoDRIfChdsYXN0X3N0YXJ0ZWRfYXRfdW5peF9tcxgIIAEoAxIgChhsYXN0X2ZpbmlzaGVkX2F0X3VuaXhfbXMYCSABKAMSEQoJdXBsb2FkX2lkGAogASgJIkwKFUxpc3RBcnRpZmFjdHNSZXNwb25zZRIzCglhcnRpZmFjdHMYASADKAsyIC5hZ2VudC52MS5BcnRpZmFjdFVwbG9hZE1ldGFkYXRhIk4KFlVwbG9hZEFydGlmYWN0c1JlcXVlc3QSNAoHdXBsb2FkcxgBIAMoCzIjLmFnZW50LnYxLkFydGlmYWN0VXBsb2FkSW5zdHJ1Y3Rpb24i1wIKGUFydGlmYWN0VXBsb2FkSW5zdHJ1Y3Rpb24SFQoNYWJzb2x1dGVfcGF0aBgBIAEoCRISCgp1cGxvYWRfdXJsGAIgASgJEg4KBm1ldGhvZBgDIAEoCRJBCgdoZWFkZXJzGAQgAygLMjAuYWdlbnQudjEuQXJ0aWZhY3RVcGxvYWRJbnN0cnVjdGlvbi5IZWFkZXJzRW50cnkSGQoMY29udGVudF90eXBlGAUgASgJSACIAQESHQoQc2xhY2tfdXBsb2FkX3VybBgGIAEoCUgBiAEBEhoKDXNsYWNrX2ZpbGVfaWQYByABKAlIAogBARouCgxIZWFkZXJzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4AUIPCg1fY29udGVudF90eXBlQhMKEV9zbGFja191cGxvYWRfdXJsQhAKDl9zbGFja19maWxlX2lkIoQBChxBcnRpZmFjdFVwbG9hZERpc3BhdGNoUmVzdWx0EhUKDWFic29sdXRlX3BhdGgYASABKAkSDgoGc3RhdHVzGAIgASgFEg8KB21lc3NhZ2UYAyABKAkSGgoNc2xhY2tfZmlsZV9pZBgEIAEoCUgAiAEBQhAKDl9zbGFja19maWxlX2lkIlIKF1VwbG9hZEFydGlmYWN0c1Jlc3BvbnNlEjcKB3Jlc3VsdHMYASADKAsyJi5hZ2VudC52MS5BcnRpZmFjdFVwbG9hZERpc3BhdGNoUmVzdWx0IhwKGkdldE1jcFJlZnJlc2hUb2tlbnNSZXF1ZXN0IqUBChtHZXRNY3BSZWZyZXNoVG9rZW5zUmVzcG9uc2USUAoOcmVmcmVzaF90b2tlbnMYASADKAsyOC5hZ2VudC52MS5HZXRNY3BSZWZyZXNoVG9rZW5zUmVzcG9uc2UuUmVmcmVzaFRva2Vuc0VudHJ5GjQKElJlZnJlc2hUb2tlbnNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIqMBCiFVcGRhdGVFbnZpcm9ubWVudFZhcmlhYmxlc1JlcXVlc3QSQQoDZW52GAEgAygLMjQuYWdlbnQudjEuVXBkYXRlRW52aXJvbm1lbnRWYXJpYWJsZXNSZXF1ZXN0LkVudkVudHJ5Eg8KB3JlcGxhY2UYAiABKAgaKgoIRW52RW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASJGCiJVcGRhdGVFbnZpcm9ubWVudFZhcmlhYmxlc1Jlc3BvbnNlEg8KB2FwcGxpZWQYASABKA0SDwoHcmVtb3ZlZBgCIAEoDSKDAQoSTWNwT0F1dGhTdG9yZWREYXRhEhUKDXJlZnJlc2hfdG9rZW4YASABKAkSEQoJY2xpZW50X2lkGAIgASgJEhoKDWNsaWVudF9zZWNyZXQYAyABKAlIAIgBARIVCg1yZWRpcmVjdF91cmlzGAQgAygJQhAKDl9jbGllbnRfc2VjcmV0Ik4KBUZyYW1lEgoKAmlkGAEgASgJEg4KBm1ldGhvZBgCIAEoCRIMCgRkYXRhGAMgASgMEgwKBGtpbmQYBCABKAUSDQoFZXJyb3IYBSABKAkiBwoFRW1wdHkiIwoNQmlkaVJlcXVlc3RJZBISCgpyZXF1ZXN0X2lkGAEgASgJKogBCh1BcHBsaWVkQWdlbnRDaGFuZ2VfQ2hhbmdlVHlwZRIbChdDSEFOR0VfVFlQRV9VTlNQRUNJRklFRBAAEhcKE0NIQU5HRV9UWVBFX0NSRUFURUQQARIYChRDSEFOR0VfVFlQRV9NT0RJRklFRBACEhcKE0NIQU5HRV9UWVBFX0RFTEVURUQQAyqkAQoLTW91c2VCdXR0b24SHAoYTU9VU0VfQlVUVE9OX1VOU1BFQ0lGSUVEEAASFQoRTU9VU0VfQlVUVE9OX0xFRlQQARIWChJNT1VTRV9CVVRUT05fUklHSFQQAhIXChNNT1VTRV9CVVRUT05fTUlERExFEAMSFQoRTU9VU0VfQlVUVE9OX0JBQ0sQBBIYChRNT1VTRV9CVVRUT05fRk9SV0FSRBAFKp4BCg9TY3JvbGxEaXJlY3Rpb24SIAocU0NST0xMX0RJUkVDVElPTl9VTlNQRUNJRklFRBAAEhcKE1NDUk9MTF9ESVJFQ1RJT05fVVAQARIZChVTQ1JPTExfRElSRUNUSU9OX0RPV04QAhIZChVTQ1JPTExfRElSRUNUSU9OX0xFRlQQAxIaChZTQ1JPTExfRElSRUNUSU9OX1JJR0hUEAQqcAoQQ3Vyc29yUnVsZVNvdXJjZRIiCh5DVVJTT1JfUlVMRV9TT1VSQ0VfVU5TUEVDSUZJRUQQABIbChdDVVJTT1JfUlVMRV9TT1VSQ0VfVEVBTRABEhsKF0NVUlNPUl9SVUxFX1NPVVJDRV9VU0VSEAIqvAEKEkRpYWdub3N0aWNTZXZlcml0eRIjCh9ESUFHTk9TVElDX1NFVkVSSVRZX1VOU1BFQ0lGSUVEEAASHQoZRElBR05PU1RJQ19TRVZFUklUWV9FUlJPUhABEh8KG0RJQUdOT1NUSUNfU0VWRVJJVFlfV0FSTklORxACEiMKH0RJQUdOT1NUSUNfU0VWRVJJVFlfSU5GT1JNQVRJT04QAxIcChhESUFHTk9TVElDX1NFVkVSSVRZX0hJTlQQBCqcAQoNUmVjb3JkaW5nTW9kZRIeChpSRUNPUkRJTkdfTU9ERV9VTlNQRUNJRklFRBAAEiIKHlJFQ09SRElOR19NT0RFX1NUQVJUX1JFQ09SRElORxABEiEKHVJFQ09SRElOR19NT0RFX1NBVkVfUkVDT1JESU5HEAISJAogUkVDT1JESU5HX01PREVfRElTQ0FSRF9SRUNPUkRJTkcQAyqTAQofUmVxdWVzdGVkRmlsZVBhdGhSZWplY3RlZFJlYXNvbhIzCi9SRVFVRVNURURfRklMRV9QQVRIX1JFSkVDVEVEX1JFQVNPTl9VTlNQRUNJRklFRBAAEjsKN1JFUVVFU1RFRF9GSUxFX1BBVEhfUkVKRUNURURfUkVBU09OX1NMQVNIRVNfTk9UX0FMTE9XRUQQASqtAQoLUGFja2FnZVR5cGUSHAoYUEFDS0FHRV9UWVBFX1VOU1BFQ0lGSUVEEAASHwobUEFDS0FHRV9UWVBFX0NVUlNPUl9QUk9KRUNUEAESIAocUEFDS0FHRV9UWVBFX0NVUlNPUl9QRVJTT05BTBACEh0KGVBBQ0tBR0VfVFlQRV9DTEFVREVfU0tJTEwQAxIeChpQQUNLQUdFX1RZUEVfQ0xBVURFX1BMVUdJThAEKn0KElNhbmRib3hQb2xpY3lfVHlwZRIUChBUWVBFX1VOU1BFQ0lGSUVEEAASFgoSVFlQRV9JTlNFQ1VSRV9OT05FEAESHAoYVFlQRV9XT1JLU1BBQ0VfUkVBRFdSSVRFEAISGwoXVFlQRV9XT1JLU1BBQ0VfUkVBRE9OTFkQAypxCg9UaW1lb3V0QmVoYXZpb3ISIAocVElNRU9VVF9CRUhBVklPUl9VTlNQRUNJRklFRBAAEhsKF1RJTUVPVVRfQkVIQVZJT1JfQ0FOQ0VMEAESHwobVElNRU9VVF9CRUhBVklPUl9CQUNLR1JPVU5EEAIqeQoQU2hlbGxBYm9ydFJlYXNvbhIiCh5TSEVMTF9BQk9SVF9SRUFTT05fVU5TUEVDSUZJRUQQABIhCh1TSEVMTF9BQk9SVF9SRUFTT05fVVNFUl9BQk9SVBABEh4KGlNIRUxMX0FCT1JUX1JFQVNPTl9USU1FT1VUEAIqqgEKHEN1c3RvbVN1YmFnZW50UGVybWlzc2lvbk1vZGUSLworQ1VTVE9NX1NVQkFHRU5UX1BFUk1JU1NJT05fTU9ERV9VTlNQRUNJRklFRBAAEisKJ0NVU1RPTV9TVUJBR0VOVF9QRVJNSVNTSU9OX01PREVfREVGQVVMVBABEiwKKENVU1RPTV9TVUJBR0VOVF9QRVJNSVNTSU9OX01PREVfUkVBRE9OTFkQAiqVAQoKVG9kb1N0YXR1cxIbChdUT0RPX1NUQVRVU19VTlNQRUNJRklFRBAAEhcKE1RPRE9fU1RBVFVTX1BFTkRJTkcQARIbChdUT0RPX1NUQVRVU19JTl9QUk9HUkVTUxACEhkKFVRPRE9fU1RBVFVTX0NPTVBMRVRFRBADEhkKFVRPRE9fU1RBVFVTX0NBTkNFTExFRBAEKmYKCENsaWVudE9TEhkKFUNMSUVOVF9PU19VTlNQRUNJRklFRBAAEhUKEUNMSUVOVF9PU19XSU5ET1dTEAESEwoPQ0xJRU5UX09TX01BQ09TEAISEwoPQ0xJRU5UX09TX0xJTlVYEAMq7AEKHEFydGlmYWN0VXBsb2FkRGlzcGF0Y2hTdGF0dXMSLworQVJUSUZBQ1RfVVBMT0FEX0RJU1BBVENIX1NUQVRVU19VTlNQRUNJRklFRBAAEiwKKEFSVElGQUNUX1VQTE9BRF9ESVNQQVRDSF9TVEFUVVNfQUNDRVBURUQQARIsCihBUlRJRkFDVF9VUExPQURfRElTUEFUQ0hfU1RBVFVTX1JFSkVDVEVEEAISPwo7QVJUSUZBQ1RfVVBMT0FEX0RJU1BBVENIX1NUQVRVU19TS0lQUEVEX0FMUkVBRFlfSU5fUFJPR1JFU1MQAypXCgpGcmFtZV9LaW5kEhQKEEtJTkRfVU5TUEVDSUZJRUQQABIQCgxLSU5EX1JFUVVFU1QQARIRCg1LSU5EX1JFU1BPTlNFEAISDgoKS0lORF9FUlJPUhADKrACChdCdWdib3REZWVwbGlua0V2ZW50S2luZBIqCiZCVUdCT1RfREVFUExJTktfRVZFTlRfS0lORF9VTlNQRUNJRklFRBAAEiYKIkJVR0JPVF9ERUVQTElOS19FVkVOVF9LSU5EX0NMSUNLRUQQARIzCi9CVUdCT1RfREVFUExJTktfRVZFTlRfS0lORF9IQU5ETEVEX0RJQUxPR19TSE9XThACEjMKL0JVR0JPVF9ERUVQTElOS19FVkVOVF9LSU5EX0hBTkRMRURfQ0hBVF9DUkVBVEVEEAMSJAogQlVHQk9UX0RFRVBMSU5LX0VWRU5UX0tJTkRfRVJST1IQBBIxCi1CVUdCT1RfREVFUExJTktfRVZFTlRfS0lORF9IQU5ETEVEX0ZJWF9JTl9XRUIQBTKHBAoMQWdlbnRTZXJ2aWNlEkEKA1J1bhIcLmFnZW50LnYxLkFnZW50Q2xpZW50TWVzc2FnZRocLmFnZW50LnYxLkFnZW50U2VydmVyTWVzc2FnZRI/CgZSdW5TU0USFy5hZ2VudC52MS5CaWRpUmVxdWVzdElkGhwuYWdlbnQudjEuQWdlbnRTZXJ2ZXJNZXNzYWdlEkQKCU5hbWVBZ2VudBIaLmFnZW50LnYxLk5hbWVBZ2VudFJlcXVlc3QaGy5hZ2VudC52MS5OYW1lQWdlbnRSZXNwb25zZRJWCg9HZXRVc2FibGVNb2RlbHMSIC5hZ2VudC52MS5HZXRVc2FibGVNb2RlbHNSZXF1ZXN0GiEuYWdlbnQudjEuR2V0VXNhYmxlTW9kZWxzUmVzcG9uc2USaAoVR2V0RGVmYXVsdE1vZGVsRm9yQ2xpEiYuYWdlbnQudjEuR2V0RGVmYXVsdE1vZGVsRm9yQ2xpUmVxdWVzdBonLmFnZW50LnYxLkdldERlZmF1bHRNb2RlbEZvckNsaVJlc3BvbnNlEmsKFkdldEFsbG93ZWRNb2RlbEludGVudHMSJy5hZ2VudC52MS5HZXRBbGxvd2VkTW9kZWxJbnRlbnRzUmVxdWVzdBooLmFnZW50LnYxLkdldEFsbG93ZWRNb2RlbEludGVudHNSZXNwb25zZTK1CAoOQ29udHJvbFNlcnZpY2USTQoMUmVhZFRleHRGaWxlEh0uYWdlbnQudjEuUmVhZFRleHRGaWxlUmVxdWVzdBoeLmFnZW50LnYxLlJlYWRUZXh0RmlsZVJlc3BvbnNlElAKDVdyaXRlVGV4dEZpbGUSHi5hZ2VudC52MS5Xcml0ZVRleHRGaWxlUmVxdWVzdBofLmFnZW50LnYxLldyaXRlVGV4dEZpbGVSZXNwb25zZRJTCg5SZWFkQmluYXJ5RmlsZRIfLmFnZW50LnYxLlJlYWRCaW5hcnlGaWxlUmVxdWVzdBogLmFnZW50LnYxLlJlYWRCaW5hcnlGaWxlUmVzcG9uc2USVgoPV3JpdGVCaW5hcnlGaWxlEiAuYWdlbnQudjEuV3JpdGVCaW5hcnlGaWxlUmVxdWVzdBohLmFnZW50LnYxLldyaXRlQmluYXJ5RmlsZVJlc3BvbnNlEm4KF0dldFdvcmtzcGFjZUNoYW5nZXNIYXNoEiguYWdlbnQudjEuR2V0V29ya3NwYWNlQ2hhbmdlc0hhc2hSZXF1ZXN0GikuYWdlbnQudjEuR2V0V29ya3NwYWNlQ2hhbmdlc0hhc2hSZXNwb25zZRJxChhSZWZyZXNoR2l0aHViQWNjZXNzVG9rZW4SKS5hZ2VudC52MS5SZWZyZXNoR2l0aHViQWNjZXNzVG9rZW5SZXF1ZXN0GiouYWdlbnQudjEuUmVmcmVzaEdpdGh1YkFjY2Vzc1Rva2VuUmVzcG9uc2USawoWV2FybVJlbW90ZUFjY2Vzc1NlcnZlchInLmFnZW50LnYxLldhcm1SZW1vdGVBY2Nlc3NTZXJ2ZXJSZXF1ZXN0GiguYWdlbnQudjEuV2FybVJlbW90ZUFjY2Vzc1NlcnZlclJlc3BvbnNlElAKDUxpc3RBcnRpZmFjdHMSHi5hZ2VudC52MS5MaXN0QXJ0aWZhY3RzUmVxdWVzdBofLmFnZW50LnYxLkxpc3RBcnRpZmFjdHNSZXNwb25zZRJWCg9VcGxvYWRBcnRpZmFjdHMSIC5hZ2VudC52MS5VcGxvYWRBcnRpZmFjdHNSZXF1ZXN0GiEuYWdlbnQudjEuVXBsb2FkQXJ0aWZhY3RzUmVzcG9uc2USYgoTR2V0TWNwUmVmcmVzaFRva2VucxIkLmFnZW50LnYxLkdldE1jcFJlZnJlc2hUb2tlbnNSZXF1ZXN0GiUuYWdlbnQudjEuR2V0TWNwUmVmcmVzaFRva2Vuc1Jlc3BvbnNlEncKGlVwZGF0ZUVudmlyb25tZW50VmFyaWFibGVzEisuYWdlbnQudjEuVXBkYXRlRW52aXJvbm1lbnRWYXJpYWJsZXNSZXF1ZXN0GiwuYWdlbnQudjEuVXBkYXRlRW52aXJvbm1lbnRWYXJpYWJsZXNSZXNwb25zZTINCgtFeGVjU2VydmljZTJRCiJQcml2YXRlV29ya2VyQnJpZGdlRXh0ZXJuYWxTZXJ2aWNlEisKB0Nvbm5lY3QSDy5hZ2VudC52MS5GcmFtZRoPLmFnZW50LnYxLkZyYW1lMngKEExpZmVjeWNsZVNlcnZpY2USMQoNUmVzZXRJbnN0YW5jZRIPLmFnZW50LnYxLkVtcHR5Gg8uYWdlbnQudjEuRW1wdHkSMQoNUmVuZXdJbnN0YW5jZRIPLmFnZW50LnYxLkVtcHR5Gg8uYWdlbnQudjEuRW1wdHliBnByb3RvMw", - ); +export const file_agent: GenFile = /*@__PURE__*/ + fileDesc("CgthZ2VudC5wcm90bxIIYWdlbnQudjEicgoOR2xvYlRvb2xSZXN1bHQSLAoHc3VjY2VzcxgBIAEoCzIZLmFnZW50LnYxLkdsb2JUb29sU3VjY2Vzc0gAEigKBWVycm9yGAIgASgLMhcuYWdlbnQudjEuR2xvYlRvb2xFcnJvckgAQggKBnJlc3VsdCIeCg1HbG9iVG9vbEVycm9yEg0KBWVycm9yGAEgASgJIokBCg9HbG9iVG9vbFN1Y2Nlc3MSDwoHcGF0dGVybhgBIAEoCRIMCgRwYXRoGAIgASgJEg0KBWZpbGVzGAMgAygJEhMKC3RvdGFsX2ZpbGVzGAQgASgFEhgKEGNsaWVudF90cnVuY2F0ZWQYBSABKAgSGQoRcmlwZ3JlcF90cnVuY2F0ZWQYBiABKAgiRgoMR2xvYlRvb2xDYWxsEgwKBGFyZ3MYASABKAwSKAoGcmVzdWx0GAIgASgLMhguYWdlbnQudjEuR2xvYlRvb2xSZXN1bHQibQoRUmVhZExpbnRzVG9vbENhbGwSKQoEYXJncxgBIAEoCzIbLmFnZW50LnYxLlJlYWRMaW50c1Rvb2xBcmdzEi0KBnJlc3VsdBgCIAEoCzIdLmFnZW50LnYxLlJlYWRMaW50c1Rvb2xSZXN1bHQiIgoRUmVhZExpbnRzVG9vbEFyZ3MSDQoFcGF0aHMYASADKAkigQEKE1JlYWRMaW50c1Rvb2xSZXN1bHQSMQoHc3VjY2VzcxgBIAEoCzIeLmFnZW50LnYxLlJlYWRMaW50c1Rvb2xTdWNjZXNzSAASLQoFZXJyb3IYAiABKAsyHC5hZ2VudC52MS5SZWFkTGludHNUb29sRXJyb3JIAEIICgZyZXN1bHQiewoUUmVhZExpbnRzVG9vbFN1Y2Nlc3MSMwoQZmlsZV9kaWFnbm9zdGljcxgBIAMoCzIZLmFnZW50LnYxLkZpbGVEaWFnbm9zdGljcxITCgt0b3RhbF9maWxlcxgCIAEoBRIZChF0b3RhbF9kaWFnbm9zdGljcxgDIAEoBSJpCg9GaWxlRGlhZ25vc3RpY3MSDAoEcGF0aBgBIAEoCRItCgtkaWFnbm9zdGljcxgCIAMoCzIYLmFnZW50LnYxLkRpYWdub3N0aWNJdGVtEhkKEWRpYWdub3N0aWNzX2NvdW50GAMgASgFIqsBCg5EaWFnbm9zdGljSXRlbRIuCghzZXZlcml0eRgBIAEoDjIcLmFnZW50LnYxLkRpYWdub3N0aWNTZXZlcml0eRIoCgVyYW5nZRgCIAEoCzIZLmFnZW50LnYxLkRpYWdub3N0aWNSYW5nZRIPCgdtZXNzYWdlGAMgASgJEg4KBnNvdXJjZRgEIAEoCRIMCgRjb2RlGAUgASgJEhAKCGlzX3N0YWxlGAYgASgIIlUKD0RpYWdub3N0aWNSYW5nZRIhCgVzdGFydBgBIAEoCzISLmFnZW50LnYxLlBvc2l0aW9uEh8KA2VuZBgCIAEoCzISLmFnZW50LnYxLlBvc2l0aW9uIisKElJlYWRMaW50c1Rvb2xFcnJvchIVCg1lcnJvcl9tZXNzYWdlGAEgASgJIj0KDE1jcFRvb2xFcnJvchINCgVlcnJvchgBIAEoCRIeChZyZWFkX3Rvb2xfZGVmX3JlbWluZGVyGAIgASgJItIBCg1NY3BUb29sUmVzdWx0EicKB3N1Y2Nlc3MYASABKAsyFC5hZ2VudC52MS5NY3BTdWNjZXNzSAASJwoFZXJyb3IYAiABKAsyFi5hZ2VudC52MS5NY3BUb29sRXJyb3JIABIpCghyZWplY3RlZBgDIAEoCzIVLmFnZW50LnYxLk1jcFJlamVjdGVkSAASOgoRcGVybWlzc2lvbl9kZW5pZWQYBCABKAsyHS5hZ2VudC52MS5NY3BQZXJtaXNzaW9uRGVuaWVkSABCCAoGcmVzdWx0IoEBCgtNY3BUb29sQ2FsbBIfCgRhcmdzGAEgASgLMhEuYWdlbnQudjEuTWNwQXJncxInCgZyZXN1bHQYAiABKAsyFy5hZ2VudC52MS5NY3BUb29sUmVzdWx0EhgKC2Rlc2NyaXB0aW9uGAMgASgJSACIAQFCDgoMX2Rlc2NyaXB0aW9uIm0KEVNlbVNlYXJjaFRvb2xDYWxsEikKBGFyZ3MYASABKAsyGy5hZ2VudC52MS5TZW1TZWFyY2hUb29sQXJncxItCgZyZXN1bHQYAiABKAsyHS5hZ2VudC52MS5TZW1TZWFyY2hUb29sUmVzdWx0IlMKEVNlbVNlYXJjaFRvb2xBcmdzEg0KBXF1ZXJ5GAEgASgJEhoKEnRhcmdldF9kaXJlY3RvcmllcxgCIAMoCRITCgtleHBsYW5hdGlvbhgDIAEoCSKBAQoTU2VtU2VhcmNoVG9vbFJlc3VsdBIxCgdzdWNjZXNzGAEgASgLMh4uYWdlbnQudjEuU2VtU2VhcmNoVG9vbFN1Y2Nlc3NIABItCgVlcnJvchgCIAEoCzIcLmFnZW50LnYxLlNlbVNlYXJjaFRvb2xFcnJvckgAQggKBnJlc3VsdCI9ChRTZW1TZWFyY2hUb29sU3VjY2VzcxIPCgdyZXN1bHRzGAEgASgJEhQKDGNvZGVfcmVzdWx0cxgCIAMoDCIrChJTZW1TZWFyY2hUb29sRXJyb3ISFQoNZXJyb3JfbWVzc2FnZRgBIAEoCSKCAQoYTGlzdE1jcFJlc291cmNlc1Rvb2xDYWxsEjAKBGFyZ3MYASABKAsyIi5hZ2VudC52MS5MaXN0TWNwUmVzb3VyY2VzRXhlY0FyZ3MSNAoGcmVzdWx0GAIgASgLMiQuYWdlbnQudjEuTGlzdE1jcFJlc291cmNlc0V4ZWNSZXN1bHQifwoXUmVhZE1jcFJlc291cmNlVG9vbENhbGwSLwoEYXJncxgBIAEoCzIhLmFnZW50LnYxLlJlYWRNY3BSZXNvdXJjZUV4ZWNBcmdzEjMKBnJlc3VsdBgCIAEoCzIjLmFnZW50LnYxLlJlYWRNY3BSZXNvdXJjZUV4ZWNSZXN1bHQiWQoNRmV0Y2hUb29sQ2FsbBIhCgRhcmdzGAEgASgLMhMuYWdlbnQudjEuRmV0Y2hBcmdzEiUKBnJlc3VsdBgCIAEoCzIVLmFnZW50LnYxLkZldGNoUmVzdWx0Im4KFFJlY29yZFNjcmVlblRvb2xDYWxsEigKBGFyZ3MYASABKAsyGi5hZ2VudC52MS5SZWNvcmRTY3JlZW5BcmdzEiwKBnJlc3VsdBgCIAEoCzIcLmFnZW50LnYxLlJlY29yZFNjcmVlblJlc3VsdCJ3ChdXcml0ZVNoZWxsU3RkaW5Ub29sQ2FsbBIrCgRhcmdzGAEgASgLMh0uYWdlbnQudjEuV3JpdGVTaGVsbFN0ZGluQXJncxIvCgZyZXN1bHQYAiABKAsyHy5hZ2VudC52MS5Xcml0ZVNoZWxsU3RkaW5SZXN1bHQisQEKC1JlZmxlY3RBcmdzEiIKGnVuZXhwZWN0ZWRfYWN0aW9uX291dGNvbWVzGAEgASgJEh0KFXJlbGV2YW50X2luc3RydWN0aW9ucxgCIAEoCRIZChFzY2VuYXJpb19hbmFseXNpcxgDIAEoCRIaChJjcml0aWNhbF9zeW50aGVzaXMYBCABKAkSEgoKbmV4dF9zdGVwcxgFIAEoCRIUCgx0b29sX2NhbGxfaWQYBiABKAkibwoNUmVmbGVjdFJlc3VsdBIrCgdzdWNjZXNzGAEgASgLMhguYWdlbnQudjEuUmVmbGVjdFN1Y2Nlc3NIABInCgVlcnJvchgCIAEoCzIWLmFnZW50LnYxLlJlZmxlY3RFcnJvckgAQggKBnJlc3VsdCIQCg5SZWZsZWN0U3VjY2VzcyIdCgxSZWZsZWN0RXJyb3ISDQoFZXJyb3IYASABKAkiXwoPUmVmbGVjdFRvb2xDYWxsEiMKBGFyZ3MYASABKAsyFS5hZ2VudC52MS5SZWZsZWN0QXJncxInCgZyZXN1bHQYAiABKAsyFy5hZ2VudC52MS5SZWZsZWN0UmVzdWx0IlkKF1N0YXJ0R3JpbmRFeGVjdXRpb25BcmdzEhgKC2V4cGxhbmF0aW9uGAEgASgJSACIAQESFAoMdG9vbF9jYWxsX2lkGAIgASgJQg4KDF9leHBsYW5hdGlvbiKTAQoZU3RhcnRHcmluZEV4ZWN1dGlvblJlc3VsdBI3CgdzdWNjZXNzGAEgASgLMiQuYWdlbnQudjEuU3RhcnRHcmluZEV4ZWN1dGlvblN1Y2Nlc3NIABIzCgVlcnJvchgCIAEoCzIiLmFnZW50LnYxLlN0YXJ0R3JpbmRFeGVjdXRpb25FcnJvckgAQggKBnJlc3VsdCIcChpTdGFydEdyaW5kRXhlY3V0aW9uU3VjY2VzcyIpChhTdGFydEdyaW5kRXhlY3V0aW9uRXJyb3ISDQoFZXJyb3IYASABKAkigwEKG1N0YXJ0R3JpbmRFeGVjdXRpb25Ub29sQ2FsbBIvCgRhcmdzGAEgASgLMiEuYWdlbnQudjEuU3RhcnRHcmluZEV4ZWN1dGlvbkFyZ3MSMwoGcmVzdWx0GAIgASgLMiMuYWdlbnQudjEuU3RhcnRHcmluZEV4ZWN1dGlvblJlc3VsdCJYChZTdGFydEdyaW5kUGxhbm5pbmdBcmdzEhgKC2V4cGxhbmF0aW9uGAEgASgJSACIAQESFAoMdG9vbF9jYWxsX2lkGAIgASgJQg4KDF9leHBsYW5hdGlvbiKQAQoYU3RhcnRHcmluZFBsYW5uaW5nUmVzdWx0EjYKB3N1Y2Nlc3MYASABKAsyIy5hZ2VudC52MS5TdGFydEdyaW5kUGxhbm5pbmdTdWNjZXNzSAASMgoFZXJyb3IYAiABKAsyIS5hZ2VudC52MS5TdGFydEdyaW5kUGxhbm5pbmdFcnJvckgAQggKBnJlc3VsdCIbChlTdGFydEdyaW5kUGxhbm5pbmdTdWNjZXNzIigKF1N0YXJ0R3JpbmRQbGFubmluZ0Vycm9yEg0KBWVycm9yGAEgASgJIoABChpTdGFydEdyaW5kUGxhbm5pbmdUb29sQ2FsbBIuCgRhcmdzGAEgASgLMiAuYWdlbnQudjEuU3RhcnRHcmluZFBsYW5uaW5nQXJncxIyCgZyZXN1bHQYAiABKAsyIi5hZ2VudC52MS5TdGFydEdyaW5kUGxhbm5pbmdSZXN1bHQinAEKCFRhc2tBcmdzEhMKC2Rlc2NyaXB0aW9uGAEgASgJEg4KBnByb21wdBgCIAEoCRItCg1zdWJhZ2VudF90eXBlGAMgASgLMhYuYWdlbnQudjEuU3ViYWdlbnRUeXBlEhIKBW1vZGVsGAQgASgJSACIAQESEwoGcmVzdW1lGAUgASgJSAGIAQFCCAoGX21vZGVsQgkKB19yZXN1bWUiqgEKC1Rhc2tTdWNjZXNzEjYKEmNvbnZlcnNhdGlvbl9zdGVwcxgBIAMoCzIaLmFnZW50LnYxLkNvbnZlcnNhdGlvblN0ZXASFQoIYWdlbnRfaWQYAiABKAlIAIgBARIVCg1pc19iYWNrZ3JvdW5kGAMgASgIEhgKC2R1cmF0aW9uX21zGAQgASgESAGIAQFCCwoJX2FnZW50X2lkQg4KDF9kdXJhdGlvbl9tcyIaCglUYXNrRXJyb3ISDQoFZXJyb3IYASABKAkiZgoKVGFza1Jlc3VsdBIoCgdzdWNjZXNzGAEgASgLMhUuYWdlbnQudjEuVGFza1N1Y2Nlc3NIABIkCgVlcnJvchgCIAEoCzITLmFnZW50LnYxLlRhc2tFcnJvckgAQggKBnJlc3VsdCJWCgxUYXNrVG9vbENhbGwSIAoEYXJncxgBIAEoCzISLmFnZW50LnYxLlRhc2tBcmdzEiQKBnJlc3VsdBgCIAEoCzIULmFnZW50LnYxLlRhc2tSZXN1bHQiTAoRVGFza1Rvb2xDYWxsRGVsdGESNwoSaW50ZXJhY3Rpb25fdXBkYXRlGAEgASgLMhsuYWdlbnQudjEuSW50ZXJhY3Rpb25VcGRhdGUihhQKCFRvb2xDYWxsEjIKD3NoZWxsX3Rvb2xfY2FsbBgBIAEoCzIXLmFnZW50LnYxLlNoZWxsVG9vbENhbGxIABI0ChBkZWxldGVfdG9vbF9jYWxsGAMgASgLMhguYWdlbnQudjEuRGVsZXRlVG9vbENhbGxIABIwCg5nbG9iX3Rvb2xfY2FsbBgEIAEoCzIWLmFnZW50LnYxLkdsb2JUb29sQ2FsbEgAEjAKDmdyZXBfdG9vbF9jYWxsGAUgASgLMhYuYWdlbnQudjEuR3JlcFRvb2xDYWxsSAASMAoOcmVhZF90b29sX2NhbGwYCCABKAsyFi5hZ2VudC52MS5SZWFkVG9vbENhbGxIABI/ChZ1cGRhdGVfdG9kb3NfdG9vbF9jYWxsGAkgASgLMh0uYWdlbnQudjEuVXBkYXRlVG9kb3NUb29sQ2FsbEgAEjsKFHJlYWRfdG9kb3NfdG9vbF9jYWxsGAogASgLMhsuYWdlbnQudjEuUmVhZFRvZG9zVG9vbENhbGxIABIwCg5lZGl0X3Rvb2xfY2FsbBgMIAEoCzIWLmFnZW50LnYxLkVkaXRUb29sQ2FsbEgAEiwKDGxzX3Rvb2xfY2FsbBgNIAEoCzIULmFnZW50LnYxLkxzVG9vbENhbGxIABI7ChRyZWFkX2xpbnRzX3Rvb2xfY2FsbBgOIAEoCzIbLmFnZW50LnYxLlJlYWRMaW50c1Rvb2xDYWxsSAASLgoNbWNwX3Rvb2xfY2FsbBgPIAEoCzIVLmFnZW50LnYxLk1jcFRvb2xDYWxsSAASOwoUc2VtX3NlYXJjaF90b29sX2NhbGwYECABKAsyGy5hZ2VudC52MS5TZW1TZWFyY2hUb29sQ2FsbEgAEj0KFWNyZWF0ZV9wbGFuX3Rvb2xfY2FsbBgRIAEoCzIcLmFnZW50LnYxLkNyZWF0ZVBsYW5Ub29sQ2FsbEgAEjsKFHdlYl9zZWFyY2hfdG9vbF9jYWxsGBIgASgLMhsuYWdlbnQudjEuV2ViU2VhcmNoVG9vbENhbGxIABIwCg50YXNrX3Rvb2xfY2FsbBgTIAEoCzIWLmFnZW50LnYxLlRhc2tUb29sQ2FsbEgAEkoKHGxpc3RfbWNwX3Jlc291cmNlc190b29sX2NhbGwYFCABKAsyIi5hZ2VudC52MS5MaXN0TWNwUmVzb3VyY2VzVG9vbENhbGxIABJIChtyZWFkX21jcF9yZXNvdXJjZV90b29sX2NhbGwYFSABKAsyIS5hZ2VudC52MS5SZWFkTWNwUmVzb3VyY2VUb29sQ2FsbEgAEkYKGmFwcGx5X2FnZW50X2RpZmZfdG9vbF9jYWxsGBYgASgLMiAuYWdlbnQudjEuQXBwbHlBZ2VudERpZmZUb29sQ2FsbEgAEj8KFmFza19xdWVzdGlvbl90b29sX2NhbGwYFyABKAsyHS5hZ2VudC52MS5Bc2tRdWVzdGlvblRvb2xDYWxsSAASMgoPZmV0Y2hfdG9vbF9jYWxsGBggASgLMhcuYWdlbnQudjEuRmV0Y2hUb29sQ2FsbEgAEj0KFXN3aXRjaF9tb2RlX3Rvb2xfY2FsbBgZIAEoCzIcLmFnZW50LnYxLlN3aXRjaE1vZGVUb29sQ2FsbEgAEjsKFGV4YV9zZWFyY2hfdG9vbF9jYWxsGBogASgLMhsuYWdlbnQudjEuRXhhU2VhcmNoVG9vbENhbGxIABI5ChNleGFfZmV0Y2hfdG9vbF9jYWxsGBsgASgLMhouYWdlbnQudjEuRXhhRmV0Y2hUb29sQ2FsbEgAEkMKGGdlbmVyYXRlX2ltYWdlX3Rvb2xfY2FsbBgcIAEoCzIfLmFnZW50LnYxLkdlbmVyYXRlSW1hZ2VUb29sQ2FsbEgAEkEKF3JlY29yZF9zY3JlZW5fdG9vbF9jYWxsGB0gASgLMh4uYWdlbnQudjEuUmVjb3JkU2NyZWVuVG9vbENhbGxIABI/ChZjb21wdXRlcl91c2VfdG9vbF9jYWxsGB4gASgLMh0uYWdlbnQudjEuQ29tcHV0ZXJVc2VUb29sQ2FsbEgAEkgKG3dyaXRlX3NoZWxsX3N0ZGluX3Rvb2xfY2FsbBgfIAEoCzIhLmFnZW50LnYxLldyaXRlU2hlbGxTdGRpblRvb2xDYWxsSAASNgoRcmVmbGVjdF90b29sX2NhbGwYICABKAsyGS5hZ2VudC52MS5SZWZsZWN0VG9vbENhbGxIABJOCh5zZXR1cF92bV9lbnZpcm9ubWVudF90b29sX2NhbGwYISABKAsyJC5hZ2VudC52MS5TZXR1cFZtRW52aXJvbm1lbnRUb29sQ2FsbEgAEjoKE3RydW5jYXRlZF90b29sX2NhbGwYIiABKAsyGy5hZ2VudC52MS5UcnVuY2F0ZWRUb29sQ2FsbEgAElAKH3N0YXJ0X2dyaW5kX2V4ZWN1dGlvbl90b29sX2NhbGwYIyABKAsyJS5hZ2VudC52MS5TdGFydEdyaW5kRXhlY3V0aW9uVG9vbENhbGxIABJOCh5zdGFydF9ncmluZF9wbGFubmluZ190b29sX2NhbGwYJCABKAsyJC5hZ2VudC52MS5TdGFydEdyaW5kUGxhbm5pbmdUb29sQ2FsbEgAEjUKEXBpX3JlYWRfdG9vbF9jYWxsGD0gASgLMhguYWdlbnQudjEuUGlSZWFkVG9vbENhbGxIABI1ChFwaV9iYXNoX3Rvb2xfY2FsbBg+IAEoCzIYLmFnZW50LnYxLlBpQmFzaFRvb2xDYWxsSAASNQoRcGlfZWRpdF90b29sX2NhbGwYPyABKAsyGC5hZ2VudC52MS5QaUVkaXRUb29sQ2FsbEgAEjcKEnBpX3dyaXRlX3Rvb2xfY2FsbBhAIAEoCzIZLmFnZW50LnYxLlBpV3JpdGVUb29sQ2FsbEgAEjUKEXBpX2dyZXBfdG9vbF9jYWxsGEEgASgLMhguYWdlbnQudjEuUGlHcmVwVG9vbENhbGxIABI1ChFwaV9maW5kX3Rvb2xfY2FsbBhCIAEoCzIYLmFnZW50LnYxLlBpRmluZFRvb2xDYWxsSAASMQoPcGlfbHNfdG9vbF9jYWxsGEMgASgLMhYuYWdlbnQudjEuUGlMc1Rvb2xDYWxsSAASPQoVY29ubmVjdF9zY21fdG9vbF9jYWxsGEQgASgLMhwuYWdlbnQudjEuQ29ubmVjdFNjbVRvb2xDYWxsSAASTwoec2VhcmNoX2NvbnZlcnNhdGlvbnNfdG9vbF9jYWxsGEUgASgLMiUuYWdlbnQudjEuU2VhcmNoQ29udmVyc2F0aW9uc1Rvb2xDYWxsSAASGQoMdG9vbF9jYWxsX2lkGDkgASgJSAGIAQFCBgoEdG9vbEIPCg1fdG9vbF9jYWxsX2lkIhcKFVRydW5jYXRlZFRvb2xDYWxsQXJncyIaChhUcnVuY2F0ZWRUb29sQ2FsbFN1Y2Nlc3MiJwoWVHJ1bmNhdGVkVG9vbENhbGxFcnJvchINCgVlcnJvchgBIAEoCSKNAQoXVHJ1bmNhdGVkVG9vbENhbGxSZXN1bHQSNQoHc3VjY2VzcxgBIAEoCzIiLmFnZW50LnYxLlRydW5jYXRlZFRvb2xDYWxsU3VjY2Vzc0gAEjEKBWVycm9yGAIgASgLMiAuYWdlbnQudjEuVHJ1bmNhdGVkVG9vbENhbGxFcnJvckgAQggKBnJlc3VsdCKUAQoRVHJ1bmNhdGVkVG9vbENhbGwSHQoVb3JpZ2luYWxfc3RlcF9ibG9iX2lkGAEgASgMEi0KBGFyZ3MYAiABKAsyHy5hZ2VudC52MS5UcnVuY2F0ZWRUb29sQ2FsbEFyZ3MSMQoGcmVzdWx0GAMgASgLMiEuYWdlbnQudjEuVHJ1bmNhdGVkVG9vbENhbGxSZXN1bHQi0QEKDVRvb2xDYWxsRGVsdGESPQoVc2hlbGxfdG9vbF9jYWxsX2RlbHRhGAEgASgLMhwuYWdlbnQudjEuU2hlbGxUb29sQ2FsbERlbHRhSAASOwoUdGFza190b29sX2NhbGxfZGVsdGEYAiABKAsyGy5hZ2VudC52MS5UYXNrVG9vbENhbGxEZWx0YUgAEjsKFGVkaXRfdG9vbF9jYWxsX2RlbHRhGAMgASgLMhsuYWdlbnQudjEuRWRpdFRvb2xDYWxsRGVsdGFIAEIHCgVkZWx0YSK2AQoQQ29udmVyc2F0aW9uU3RlcBI3ChFhc3Npc3RhbnRfbWVzc2FnZRgBIAEoCzIaLmFnZW50LnYxLkFzc2lzdGFudE1lc3NhZ2VIABInCgl0b29sX2NhbGwYAiABKAsyEi5hZ2VudC52MS5Ub29sQ2FsbEgAEjUKEHRoaW5raW5nX21lc3NhZ2UYAyABKAsyGS5hZ2VudC52MS5UaGlua2luZ01lc3NhZ2VIAEIJCgdtZXNzYWdlIoEEChJDb252ZXJzYXRpb25BY3Rpb24SOgoTdXNlcl9tZXNzYWdlX2FjdGlvbhgBIAEoCzIbLmFnZW50LnYxLlVzZXJNZXNzYWdlQWN0aW9uSAASLwoNcmVzdW1lX2FjdGlvbhgCIAEoCzIWLmFnZW50LnYxLlJlc3VtZUFjdGlvbkgAEi8KDWNhbmNlbF9hY3Rpb24YAyABKAsyFi5hZ2VudC52MS5DYW5jZWxBY3Rpb25IABI1ChBzdW1tYXJpemVfYWN0aW9uGAQgASgLMhkuYWdlbnQudjEuU3VtbWFyaXplQWN0aW9uSAASPAoUc2hlbGxfY29tbWFuZF9hY3Rpb24YBSABKAsyHC5hZ2VudC52MS5TaGVsbENvbW1hbmRBY3Rpb25IABI2ChFzdGFydF9wbGFuX2FjdGlvbhgGIAEoCzIZLmFnZW50LnYxLlN0YXJ0UGxhbkFjdGlvbkgAEjoKE2V4ZWN1dGVfcGxhbl9hY3Rpb24YByABKAsyGy5hZ2VudC52MS5FeGVjdXRlUGxhbkFjdGlvbkgAEloKJGFzeW5jX2Fza19xdWVzdGlvbl9jb21wbGV0aW9uX2FjdGlvbhgIIAEoCzIqLmFnZW50LnYxLkFzeW5jQXNrUXVlc3Rpb25Db21wbGV0aW9uQWN0aW9uSABCCAoGYWN0aW9uIr8BChFVc2VyTWVzc2FnZUFjdGlvbhIrCgx1c2VyX21lc3NhZ2UYASABKAsyFS5hZ2VudC52MS5Vc2VyTWVzc2FnZRIxCg9yZXF1ZXN0X2NvbnRleHQYAiABKAsyGC5hZ2VudC52MS5SZXF1ZXN0Q29udGV4dBIpChxzZW5kX3RvX2ludGVyYWN0aW9uX2xpc3RlbmVyGAMgASgISACIAQFCHwodX3NlbmRfdG9faW50ZXJhY3Rpb25fbGlzdGVuZXIiDgoMQ2FuY2VsQWN0aW9uIkEKDFJlc3VtZUFjdGlvbhIxCg9yZXF1ZXN0X2NvbnRleHQYAiABKAsyGC5hZ2VudC52MS5SZXF1ZXN0Q29udGV4dCKgAQogQXN5bmNBc2tRdWVzdGlvbkNvbXBsZXRpb25BY3Rpb24SHQoVb3JpZ2luYWxfdG9vbF9jYWxsX2lkGAEgASgJEjAKDW9yaWdpbmFsX2FyZ3MYAiABKAsyGS5hZ2VudC52MS5Bc2tRdWVzdGlvbkFyZ3MSKwoGcmVzdWx0GAMgASgLMhsuYWdlbnQudjEuQXNrUXVlc3Rpb25SZXN1bHQiEQoPU3VtbWFyaXplQWN0aW9uIlQKElNoZWxsQ29tbWFuZEFjdGlvbhItCg1zaGVsbF9jb21tYW5kGAEgASgLMhYuYWdlbnQudjEuU2hlbGxDb21tYW5kEg8KB2V4ZWNfaWQYAiABKAkiggEKD1N0YXJ0UGxhbkFjdGlvbhIrCgx1c2VyX21lc3NhZ2UYASABKAsyFS5hZ2VudC52MS5Vc2VyTWVzc2FnZRIxCg9yZXF1ZXN0X2NvbnRleHQYAiABKAsyGC5hZ2VudC52MS5SZXF1ZXN0Q29udGV4dBIPCgdpc19zcGVjGAMgASgIIuIBChFFeGVjdXRlUGxhbkFjdGlvbhIxCg9yZXF1ZXN0X2NvbnRleHQYASABKAsyGC5hZ2VudC52MS5SZXF1ZXN0Q29udGV4dBItCgRwbGFuGAIgASgLMhouYWdlbnQudjEuQ29udmVyc2F0aW9uUGxhbkgAiAEBEhoKDXBsYW5fZmlsZV91cmkYAyABKAlIAYgBARIeChFwbGFuX2ZpbGVfY29udGVudBgEIAEoCUgCiAEBQgcKBV9wbGFuQhAKDl9wbGFuX2ZpbGVfdXJpQhQKEl9wbGFuX2ZpbGVfY29udGVudCLoAgoLVXNlck1lc3NhZ2USDAoEdGV4dBgBIAEoCRISCgptZXNzYWdlX2lkGAIgASgJEjgKEHNlbGVjdGVkX2NvbnRleHQYAyABKAsyGS5hZ2VudC52MS5TZWxlY3RlZENvbnRleHRIAIgBARIMCgRtb2RlGAQgASgFEh0KEGlzX3NpbXVsYXRlZF9tc2cYBSABKAhIAYgBARIfChJiZXN0X29mX25fZ3JvdXBfaWQYBiABKAlIAogBARIoCht0cnlfdXNlX2Jlc3Rfb2Zfbl9wcm9tb3Rpb24YByABKAhIA4gBARIWCglyaWNoX3RleHQYCCABKAlIBIgBAUITChFfc2VsZWN0ZWRfY29udGV4dEITChFfaXNfc2ltdWxhdGVkX21zZ0IVChNfYmVzdF9vZl9uX2dyb3VwX2lkQh4KHF90cnlfdXNlX2Jlc3Rfb2Zfbl9wcm9tb3Rpb25CDAoKX3JpY2hfdGV4dCIgChBBc3Npc3RhbnRNZXNzYWdlEgwKBHRleHQYASABKAkiNAoPVGhpbmtpbmdNZXNzYWdlEgwKBHRleHQYASABKAkSEwoLZHVyYXRpb25fbXMYAiABKA0iHwoMU2hlbGxDb21tYW5kEg8KB2NvbW1hbmQYASABKAkiQAoLU2hlbGxPdXRwdXQSDgoGc3Rkb3V0GAEgASgJEg4KBnN0ZGVychgCIAEoCRIRCglleGl0X2NvZGUYAyABKAUiogEKEENvbnZlcnNhdGlvblR1cm4SQgoXYWdlbnRfY29udmVyc2F0aW9uX3R1cm4YASABKAsyHy5hZ2VudC52MS5BZ2VudENvbnZlcnNhdGlvblR1cm5IABJCChdzaGVsbF9jb252ZXJzYXRpb25fdHVybhgCIAEoCzIfLmFnZW50LnYxLlNoZWxsQ29udmVyc2F0aW9uVHVybkgAQgYKBHR1cm4iIAoQQ29udmVyc2F0aW9uUGxhbhIMCgRwbGFuGAEgASgJIr0BChlDb252ZXJzYXRpb25UdXJuU3RydWN0dXJlEksKF2FnZW50X2NvbnZlcnNhdGlvbl90dXJuGAEgASgLMiguYWdlbnQudjEuQWdlbnRDb252ZXJzYXRpb25UdXJuU3RydWN0dXJlSAASSwoXc2hlbGxfY29udmVyc2F0aW9uX3R1cm4YAiABKAsyKC5hZ2VudC52MS5TaGVsbENvbnZlcnNhdGlvblR1cm5TdHJ1Y3R1cmVIAEIGCgR0dXJuIpcBChVBZ2VudENvbnZlcnNhdGlvblR1cm4SKwoMdXNlcl9tZXNzYWdlGAEgASgLMhUuYWdlbnQudjEuVXNlck1lc3NhZ2USKQoFc3RlcHMYAiADKAsyGi5hZ2VudC52MS5Db252ZXJzYXRpb25TdGVwEhcKCnJlcXVlc3RfaWQYAyABKAlIAIgBAUINCgtfcmVxdWVzdF9pZCJtCh5BZ2VudENvbnZlcnNhdGlvblR1cm5TdHJ1Y3R1cmUSFAoMdXNlcl9tZXNzYWdlGAEgASgMEg0KBXN0ZXBzGAIgAygMEhcKCnJlcXVlc3RfaWQYAyABKAlIAIgBAUINCgtfcmVxdWVzdF9pZCJzChVTaGVsbENvbnZlcnNhdGlvblR1cm4SLQoNc2hlbGxfY29tbWFuZBgBIAEoCzIWLmFnZW50LnYxLlNoZWxsQ29tbWFuZBIrCgxzaGVsbF9vdXRwdXQYAiABKAsyFS5hZ2VudC52MS5TaGVsbE91dHB1dCJNCh5TaGVsbENvbnZlcnNhdGlvblR1cm5TdHJ1Y3R1cmUSFQoNc2hlbGxfY29tbWFuZBgBIAEoDBIUCgxzaGVsbF9vdXRwdXQYAiABKAwiJgoTQ29udmVyc2F0aW9uU3VtbWFyeRIPCgdzdW1tYXJ5GAEgASgJIngKGkNvbnZlcnNhdGlvblN1bW1hcnlBcmNoaXZlEhsKE3N1bW1hcml6ZWRfbWVzc2FnZXMYASADKAwSDwoHc3VtbWFyeRgCIAEoCRITCgt3aW5kb3dfdGFpbBgDIAEoDRIXCg9zdW1tYXJ5X21lc3NhZ2UYBCABKAwiQwoYQ29udmVyc2F0aW9uVG9rZW5EZXRhaWxzEhMKC3VzZWRfdG9rZW5zGAEgASgNEhIKCm1heF90b2tlbnMYAiABKA0iXwoJRmlsZVN0YXRlEhQKB2NvbnRlbnQYASABKAlIAIgBARIcCg9pbml0aWFsX2NvbnRlbnQYAiABKAlIAYgBAUIKCghfY29udGVudEISChBfaW5pdGlhbF9jb250ZW50ImgKEkZpbGVTdGF0ZVN0cnVjdHVyZRIUCgdjb250ZW50GAEgASgMSACIAQESHAoPaW5pdGlhbF9jb250ZW50GAIgASgMSAGIAQFCCgoIX2NvbnRlbnRCEgoQX2luaXRpYWxfY29udGVudCI3CgpTdGVwVGltaW5nEhMKC2R1cmF0aW9uX21zGAEgASgEEhQKDHRpbWVzdGFtcF9tcxgCIAEoBCL2BAoRQ29udmVyc2F0aW9uU3RhdGUSIQoZcm9vdF9wcm9tcHRfbWVzc2FnZXNfanNvbhgBIAMoCRIpCgV0dXJucxgIIAMoCzIaLmFnZW50LnYxLkNvbnZlcnNhdGlvblR1cm4SIQoFdG9kb3MYAyADKAsyEi5hZ2VudC52MS5Ub2RvSXRlbRIaChJwZW5kaW5nX3Rvb2xfY2FsbHMYBCADKAkSOQoNdG9rZW5fZGV0YWlscxgFIAEoCzIiLmFnZW50LnYxLkNvbnZlcnNhdGlvblRva2VuRGV0YWlscxIzCgdzdW1tYXJ5GAYgASgLMh0uYWdlbnQudjEuQ29udmVyc2F0aW9uU3VtbWFyeUgAiAEBEi0KBHBsYW4YByABKAsyGi5hZ2VudC52MS5Db252ZXJzYXRpb25QbGFuSAGIAQESQgoPc3VtbWFyeV9hcmNoaXZlGAkgASgLMiQuYWdlbnQudjEuQ29udmVyc2F0aW9uU3VtbWFyeUFyY2hpdmVIAogBARJACgtmaWxlX3N0YXRlcxgKIAMoCzIrLmFnZW50LnYxLkNvbnZlcnNhdGlvblN0YXRlLkZpbGVTdGF0ZXNFbnRyeRI+ChBzdW1tYXJ5X2FyY2hpdmVzGAsgAygLMiQuYWdlbnQudjEuQ29udmVyc2F0aW9uU3VtbWFyeUFyY2hpdmUaRgoPRmlsZVN0YXRlc0VudHJ5EgsKA2tleRgBIAEoCRIiCgV2YWx1ZRgCIAEoCzITLmFnZW50LnYxLkZpbGVTdGF0ZToCOAFCCgoIX3N1bW1hcnlCBwoFX3BsYW5CEgoQX3N1bW1hcnlfYXJjaGl2ZSLHAQoWU3ViYWdlbnRQZXJzaXN0ZWRTdGF0ZRJAChJjb252ZXJzYXRpb25fc3RhdGUYASABKAsyJC5hZ2VudC52MS5Db252ZXJzYXRpb25TdGF0ZVN0cnVjdHVyZRIcChRjcmVhdGVkX3RpbWVzdGFtcF9tcxgCIAEoBBIeChZsYXN0X3VzZWRfdGltZXN0YW1wX21zGAMgASgEEi0KDXN1YmFnZW50X3R5cGUYBCABKAsyFi5hZ2VudC52MS5TdWJhZ2VudFR5cGUitwcKGkNvbnZlcnNhdGlvblN0YXRlU3RydWN0dXJlEhEKCXR1cm5zX29sZBgCIAMoDBIhChlyb290X3Byb21wdF9tZXNzYWdlc19qc29uGAEgAygMEg0KBXR1cm5zGAggAygMEg0KBXRvZG9zGAMgAygMEhoKEnBlbmRpbmdfdG9vbF9jYWxscxgEIAMoCRI5Cg10b2tlbl9kZXRhaWxzGAUgASgLMiIuYWdlbnQudjEuQ29udmVyc2F0aW9uVG9rZW5EZXRhaWxzEhQKB3N1bW1hcnkYBiABKAxIAIgBARIRCgRwbGFuGAcgASgMSAGIAQESHwoXcHJldmlvdXNfd29ya3NwYWNlX3VyaXMYCSADKAkSEQoEbW9kZRgKIAEoBUgCiAEBEhwKD3N1bW1hcnlfYXJjaGl2ZRgLIAEoDEgDiAEBEkkKC2ZpbGVfc3RhdGVzGAwgAygLMjQuYWdlbnQudjEuQ29udmVyc2F0aW9uU3RhdGVTdHJ1Y3R1cmUuRmlsZVN0YXRlc0VudHJ5Ek4KDmZpbGVfc3RhdGVzX3YyGA8gAygLMjYuYWdlbnQudjEuQ29udmVyc2F0aW9uU3RhdGVTdHJ1Y3R1cmUuRmlsZVN0YXRlc1YyRW50cnkSGAoQc3VtbWFyeV9hcmNoaXZlcxgNIAMoDBIqCgx0dXJuX3RpbWluZ3MYDiADKAsyFC5hZ2VudC52MS5TdGVwVGltaW5nElEKD3N1YmFnZW50X3N0YXRlcxgQIAMoCzI4LmFnZW50LnYxLkNvbnZlcnNhdGlvblN0YXRlU3RydWN0dXJlLlN1YmFnZW50U3RhdGVzRW50cnkSGgoSc2VsZl9zdW1tYXJ5X2NvdW50GBEgASgNEhIKCnJlYWRfcGF0aHMYEiADKAkaMQoPRmlsZVN0YXRlc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoDDoCOAEaUQoRRmlsZVN0YXRlc1YyRW50cnkSCwoDa2V5GAEgASgJEisKBXZhbHVlGAIgASgLMhwuYWdlbnQudjEuRmlsZVN0YXRlU3RydWN0dXJlOgI4ARpXChNTdWJhZ2VudFN0YXRlc0VudHJ5EgsKA2tleRgBIAEoCRIvCgV2YWx1ZRgCIAEoCzIgLmFnZW50LnYxLlN1YmFnZW50UGVyc2lzdGVkU3RhdGU6AjgBQgoKCF9zdW1tYXJ5QgcKBV9wbGFuQgcKBV9tb2RlQhIKEF9zdW1tYXJ5X2FyY2hpdmUiEQoPVGhpbmtpbmdEZXRhaWxzIkgKEUFwaUtleUNyZWRlbnRpYWxzEg8KB2FwaV9rZXkYASABKAkSFQoIYmFzZV91cmwYAiABKAlIAIgBAUILCglfYmFzZV91cmwiSQoQQXp1cmVDcmVkZW50aWFscxIPCgdhcGlfa2V5GAEgASgJEhAKCGJhc2VfdXJsGAIgASgJEhIKCmRlcGxveW1lbnQYAyABKAkiegoSQmVkcm9ja0NyZWRlbnRpYWxzEhIKCmFjY2Vzc19rZXkYASABKAkSEgoKc2VjcmV0X2tleRgCIAEoCRIOCgZyZWdpb24YAyABKAkSGgoNc2Vzc2lvbl90b2tlbhgEIAEoCUgAiAEBQhAKDl9zZXNzaW9uX3Rva2VuIrEDCgxNb2RlbERldGFpbHMSEAoIbW9kZWxfaWQYASABKAkSGAoQZGlzcGxheV9tb2RlbF9pZBgDIAEoCRIUCgxkaXNwbGF5X25hbWUYBCABKAkSGgoSZGlzcGxheV9uYW1lX3Nob3J0GAUgASgJEg8KB2FsaWFzZXMYBiADKAkSOAoQdGhpbmtpbmdfZGV0YWlscxgCIAEoCzIZLmFnZW50LnYxLlRoaW5raW5nRGV0YWlsc0gBiAEBEhUKCG1heF9tb2RlGAcgASgISAKIAQESOgoTYXBpX2tleV9jcmVkZW50aWFscxgIIAEoCzIbLmFnZW50LnYxLkFwaUtleUNyZWRlbnRpYWxzSAASNwoRYXp1cmVfY3JlZGVudGlhbHMYCSABKAsyGi5hZ2VudC52MS5BenVyZUNyZWRlbnRpYWxzSAASOwoTYmVkcm9ja19jcmVkZW50aWFscxgKIAEoCzIcLmFnZW50LnYxLkJlZHJvY2tDcmVkZW50aWFsc0gAQg0KC2NyZWRlbnRpYWxzQhMKEV90aGlua2luZ19kZXRhaWxzQgsKCV9tYXhfbW9kZSK3AgoOUmVxdWVzdGVkTW9kZWwSEAoIbW9kZWxfaWQYASABKAkSEAoIbWF4X21vZGUYAiABKAgSQAoKcGFyYW1ldGVycxgDIAMoCzIsLmFnZW50LnYxLlJlcXVlc3RlZE1vZGVsX01vZGVsUGFyYW1ldGVyYnl0ZXMSOgoTYXBpX2tleV9jcmVkZW50aWFscxgEIAEoCzIbLmFnZW50LnYxLkFwaUtleUNyZWRlbnRpYWxzSAASNwoRYXp1cmVfY3JlZGVudGlhbHMYBSABKAsyGi5hZ2VudC52MS5BenVyZUNyZWRlbnRpYWxzSAASOwoTYmVkcm9ja19jcmVkZW50aWFscxgGIAEoCzIcLmFnZW50LnYxLkJlZHJvY2tDcmVkZW50aWFsc0gAQg0KC2NyZWRlbnRpYWxzIj8KIlJlcXVlc3RlZE1vZGVsX01vZGVsUGFyYW1ldGVyYnl0ZXMSCgoCaWQYASABKAkSDQoFdmFsdWUYAiABKAkiuQQKD0FnZW50UnVuUmVxdWVzdBJAChJjb252ZXJzYXRpb25fc3RhdGUYASABKAsyJC5hZ2VudC52MS5Db252ZXJzYXRpb25TdGF0ZVN0cnVjdHVyZRIsCgZhY3Rpb24YAiABKAsyHC5hZ2VudC52MS5Db252ZXJzYXRpb25BY3Rpb24SLQoNbW9kZWxfZGV0YWlscxgDIAEoCzIWLmFnZW50LnYxLk1vZGVsRGV0YWlscxI2Cg9yZXF1ZXN0ZWRfbW9kZWwYCSABKAsyGC5hZ2VudC52MS5SZXF1ZXN0ZWRNb2RlbEgAiAEBEiUKCW1jcF90b29scxgEIAEoCzISLmFnZW50LnYxLk1jcFRvb2xzEhwKD2NvbnZlcnNhdGlvbl9pZBgFIAEoCUgBiAEBEkQKF21jcF9maWxlX3N5c3RlbV9vcHRpb25zGAYgASgLMh4uYWdlbnQudjEuTWNwRmlsZVN5c3RlbU9wdGlvbnNIAogBARIyCg1za2lsbF9vcHRpb25zGAcgASgLMhYuYWdlbnQudjEuU2tpbGxPcHRpb25zSAOIAQESIQoUY3VzdG9tX3N5c3RlbV9wcm9tcHQYCCABKAlIBIgBAUISChBfcmVxdWVzdGVkX21vZGVsQhIKEF9jb252ZXJzYXRpb25faWRCGgoYX21jcF9maWxlX3N5c3RlbV9vcHRpb25zQhAKDl9za2lsbF9vcHRpb25zQhcKFV9jdXN0b21fc3lzdGVtX3Byb21wdCIfCg9UZXh0RGVsdGFVcGRhdGUSDAoEdGV4dBgBIAEoCSJmChVUb29sQ2FsbFN0YXJ0ZWRVcGRhdGUSDwoHY2FsbF9pZBgBIAEoCRIlCgl0b29sX2NhbGwYAiABKAsyEi5hZ2VudC52MS5Ub29sQ2FsbBIVCg1tb2RlbF9jYWxsX2lkGAMgASgJImgKF1Rvb2xDYWxsQ29tcGxldGVkVXBkYXRlEg8KB2NhbGxfaWQYASABKAkSJQoJdG9vbF9jYWxsGAIgASgLMhIuYWdlbnQudjEuVG9vbENhbGwSFQoNbW9kZWxfY2FsbF9pZBgDIAEoCSJvChNUb29sQ2FsbERlbHRhVXBkYXRlEg8KB2NhbGxfaWQYASABKAkSMAoPdG9vbF9jYWxsX2RlbHRhGAIgASgLMhcuYWdlbnQudjEuVG9vbENhbGxEZWx0YRIVCg1tb2RlbF9jYWxsX2lkGAMgASgJIn8KFVBhcnRpYWxUb29sQ2FsbFVwZGF0ZRIPCgdjYWxsX2lkGAEgASgJEiUKCXRvb2xfY2FsbBgCIAEoCzISLmFnZW50LnYxLlRvb2xDYWxsEhcKD2FyZ3NfdGV4dF9kZWx0YRgDIAEoCRIVCg1tb2RlbF9jYWxsX2lkGAQgASgJIiMKE1RoaW5raW5nRGVsdGFVcGRhdGUSDAoEdGV4dBgBIAEoCSI3ChdUaGlua2luZ0NvbXBsZXRlZFVwZGF0ZRIcChR0aGlua2luZ19kdXJhdGlvbl9tcxgBIAEoBSIiChBUb2tlbkRlbHRhVXBkYXRlEg4KBnRva2VucxgBIAEoBSIgCg1TdW1tYXJ5VXBkYXRlEg8KB3N1bW1hcnkYASABKAkiFgoUU3VtbWFyeVN0YXJ0ZWRVcGRhdGUiEQoPSGVhcnRiZWF0VXBkYXRlIhgKFlN1bW1hcnlDb21wbGV0ZWRVcGRhdGUi1wEKFlNoZWxsT3V0cHV0RGVsdGFVcGRhdGUSLQoGc3Rkb3V0GAEgASgLMhsuYWdlbnQudjEuU2hlbGxTdHJlYW1TdGRvdXRIABItCgZzdGRlcnIYAiABKAsyGy5hZ2VudC52MS5TaGVsbFN0cmVhbVN0ZGVyckgAEikKBGV4aXQYAyABKAsyGS5hZ2VudC52MS5TaGVsbFN0cmVhbUV4aXRIABIrCgVzdGFydBgEIAEoCzIaLmFnZW50LnYxLlNoZWxsU3RyZWFtU3RhcnRIAEIHCgVldmVudCIRCg9UdXJuRW5kZWRVcGRhdGUiSAoZVXNlck1lc3NhZ2VBcHBlbmRlZFVwZGF0ZRIrCgx1c2VyX21lc3NhZ2UYASABKAsyFS5hZ2VudC52MS5Vc2VyTWVzc2FnZSIkChFTdGVwU3RhcnRlZFVwZGF0ZRIPCgdzdGVwX2lkGAEgASgEIkAKE1N0ZXBDb21wbGV0ZWRVcGRhdGUSDwoHc3RlcF9pZBgBIAEoBBIYChBzdGVwX2R1cmF0aW9uX21zGAIgASgDIu8HChFJbnRlcmFjdGlvblVwZGF0ZRIvCgp0ZXh0X2RlbHRhGAEgASgLMhkuYWdlbnQudjEuVGV4dERlbHRhVXBkYXRlSAASPAoRcGFydGlhbF90b29sX2NhbGwYByABKAsyHy5hZ2VudC52MS5QYXJ0aWFsVG9vbENhbGxVcGRhdGVIABI4Cg90b29sX2NhbGxfZGVsdGEYDyABKAsyHS5hZ2VudC52MS5Ub29sQ2FsbERlbHRhVXBkYXRlSAASPAoRdG9vbF9jYWxsX3N0YXJ0ZWQYAiABKAsyHy5hZ2VudC52MS5Ub29sQ2FsbFN0YXJ0ZWRVcGRhdGVIABJAChN0b29sX2NhbGxfY29tcGxldGVkGAMgASgLMiEuYWdlbnQudjEuVG9vbENhbGxDb21wbGV0ZWRVcGRhdGVIABI3Cg50aGlua2luZ19kZWx0YRgEIAEoCzIdLmFnZW50LnYxLlRoaW5raW5nRGVsdGFVcGRhdGVIABI/ChJ0aGlua2luZ19jb21wbGV0ZWQYBSABKAsyIS5hZ2VudC52MS5UaGlua2luZ0NvbXBsZXRlZFVwZGF0ZUgAEkQKFXVzZXJfbWVzc2FnZV9hcHBlbmRlZBgGIAEoCzIjLmFnZW50LnYxLlVzZXJNZXNzYWdlQXBwZW5kZWRVcGRhdGVIABIxCgt0b2tlbl9kZWx0YRgIIAEoCzIaLmFnZW50LnYxLlRva2VuRGVsdGFVcGRhdGVIABIqCgdzdW1tYXJ5GAkgASgLMhcuYWdlbnQudjEuU3VtbWFyeVVwZGF0ZUgAEjkKD3N1bW1hcnlfc3RhcnRlZBgKIAEoCzIeLmFnZW50LnYxLlN1bW1hcnlTdGFydGVkVXBkYXRlSAASPQoRc3VtbWFyeV9jb21wbGV0ZWQYCyABKAsyIC5hZ2VudC52MS5TdW1tYXJ5Q29tcGxldGVkVXBkYXRlSAASPgoSc2hlbGxfb3V0cHV0X2RlbHRhGAwgASgLMiAuYWdlbnQudjEuU2hlbGxPdXRwdXREZWx0YVVwZGF0ZUgAEi4KCWhlYXJ0YmVhdBgNIAEoCzIZLmFnZW50LnYxLkhlYXJ0YmVhdFVwZGF0ZUgAEi8KCnR1cm5fZW5kZWQYDiABKAsyGS5hZ2VudC52MS5UdXJuRW5kZWRVcGRhdGVIABIzCgxzdGVwX3N0YXJ0ZWQYECABKAsyGy5hZ2VudC52MS5TdGVwU3RhcnRlZFVwZGF0ZUgAEjcKDnN0ZXBfY29tcGxldGVkGBEgASgLMh0uYWdlbnQudjEuU3RlcENvbXBsZXRlZFVwZGF0ZUgAQgkKB21lc3NhZ2UimgQKEEludGVyYWN0aW9uUXVlcnkSCgoCaWQYASABKA0SQwoYd2ViX3NlYXJjaF9yZXF1ZXN0X3F1ZXJ5GAIgASgLMh8uYWdlbnQudjEuV2ViU2VhcmNoUmVxdWVzdFF1ZXJ5SAASTwoeYXNrX3F1ZXN0aW9uX2ludGVyYWN0aW9uX3F1ZXJ5GAMgASgLMiUuYWdlbnQudjEuQXNrUXVlc3Rpb25JbnRlcmFjdGlvblF1ZXJ5SAASRQoZc3dpdGNoX21vZGVfcmVxdWVzdF9xdWVyeRgEIAEoCzIgLmFnZW50LnYxLlN3aXRjaE1vZGVSZXF1ZXN0UXVlcnlIABJDChhleGFfc2VhcmNoX3JlcXVlc3RfcXVlcnkYBSABKAsyHy5hZ2VudC52MS5FeGFTZWFyY2hSZXF1ZXN0UXVlcnlIABJBChdleGFfZmV0Y2hfcmVxdWVzdF9xdWVyeRgGIAEoCzIeLmFnZW50LnYxLkV4YUZldGNoUmVxdWVzdFF1ZXJ5SAASRQoZY3JlYXRlX3BsYW5fcmVxdWVzdF9xdWVyeRgHIAEoCzIgLmFnZW50LnYxLkNyZWF0ZVBsYW5SZXF1ZXN0UXVlcnlIABJFChlzZXR1cF92bV9lbnZpcm9ubWVudF9hcmdzGAggASgLMiAuYWdlbnQudjEuU2V0dXBWbUVudmlyb25tZW50QXJnc0gAQgcKBXF1ZXJ5IsYEChNJbnRlcmFjdGlvblJlc3BvbnNlEgoKAmlkGAEgASgNEkkKG3dlYl9zZWFyY2hfcmVxdWVzdF9yZXNwb25zZRgCIAEoCzIiLmFnZW50LnYxLldlYlNlYXJjaFJlcXVlc3RSZXNwb25zZUgAElUKIWFza19xdWVzdGlvbl9pbnRlcmFjdGlvbl9yZXNwb25zZRgDIAEoCzIoLmFnZW50LnYxLkFza1F1ZXN0aW9uSW50ZXJhY3Rpb25SZXNwb25zZUgAEksKHHN3aXRjaF9tb2RlX3JlcXVlc3RfcmVzcG9uc2UYBCABKAsyIy5hZ2VudC52MS5Td2l0Y2hNb2RlUmVxdWVzdFJlc3BvbnNlSAASSQobZXhhX3NlYXJjaF9yZXF1ZXN0X3Jlc3BvbnNlGAUgASgLMiIuYWdlbnQudjEuRXhhU2VhcmNoUmVxdWVzdFJlc3BvbnNlSAASRwoaZXhhX2ZldGNoX3JlcXVlc3RfcmVzcG9uc2UYBiABKAsyIS5hZ2VudC52MS5FeGFGZXRjaFJlcXVlc3RSZXNwb25zZUgAEksKHGNyZWF0ZV9wbGFuX3JlcXVlc3RfcmVzcG9uc2UYByABKAsyIy5hZ2VudC52MS5DcmVhdGVQbGFuUmVxdWVzdFJlc3BvbnNlSAASSQobc2V0dXBfdm1fZW52aXJvbm1lbnRfcmVzdWx0GAggASgLMiIuYWdlbnQudjEuU2V0dXBWbUVudmlyb25tZW50UmVzdWx0SABCCAoGcmVzdWx0IlwKG0Fza1F1ZXN0aW9uSW50ZXJhY3Rpb25RdWVyeRInCgRhcmdzGAEgASgLMhkuYWdlbnQudjEuQXNrUXVlc3Rpb25BcmdzEhQKDHRvb2xfY2FsbF9pZBgCIAEoCSJNCh5Bc2tRdWVzdGlvbkludGVyYWN0aW9uUmVzcG9uc2USKwoGcmVzdWx0GAEgASgLMhsuYWdlbnQudjEuQXNrUXVlc3Rpb25SZXN1bHQiEQoPQ2xpZW50SGVhcnRiZWF0IsYECg5QcmV3YXJtUmVxdWVzdBItCg1tb2RlbF9kZXRhaWxzGAEgASgLMhYuYWdlbnQudjEuTW9kZWxEZXRhaWxzEjYKD3JlcXVlc3RlZF9tb2RlbBgJIAEoCzIYLmFnZW50LnYxLlJlcXVlc3RlZE1vZGVsSACIAQESHAoPY29udmVyc2F0aW9uX2lkGAIgASgJSAGIAQESQAoSY29udmVyc2F0aW9uX3N0YXRlGAMgASgLMiQuYWdlbnQudjEuQ29udmVyc2F0aW9uU3RhdGVTdHJ1Y3R1cmUSJQoJbWNwX3Rvb2xzGAQgASgLMhIuYWdlbnQudjEuTWNwVG9vbHMSRAoXbWNwX2ZpbGVfc3lzdGVtX29wdGlvbnMYBSABKAsyHi5hZ2VudC52MS5NY3BGaWxlU3lzdGVtT3B0aW9uc0gCiAEBEh8KEmJlc3Rfb2Zfbl9ncm91cF9pZBgGIAEoCUgDiAEBEigKG3RyeV91c2VfYmVzdF9vZl9uX3Byb21vdGlvbhgHIAEoCEgEiAEBEiEKFGN1c3RvbV9zeXN0ZW1fcHJvbXB0GAggASgJSAWIAQFCEgoQX3JlcXVlc3RlZF9tb2RlbEISChBfY29udmVyc2F0aW9uX2lkQhoKGF9tY3BfZmlsZV9zeXN0ZW1fb3B0aW9uc0IVChNfYmVzdF9vZl9uX2dyb3VwX2lkQh4KHF90cnlfdXNlX2Jlc3Rfb2Zfbl9wcm9tb3Rpb25CFwoVX2N1c3RvbV9zeXN0ZW1fcHJvbXB0Ih0KD0V4ZWNTZXJ2ZXJBYm9ydBIKCgJpZBgBIAEoDSJRChhFeGVjU2VydmVyQ29udHJvbE1lc3NhZ2USKgoFYWJvcnQYASABKAsyGS5hZ2VudC52MS5FeGVjU2VydmVyQWJvcnRIAEIJCgdtZXNzYWdlIvgDChJBZ2VudENsaWVudE1lc3NhZ2USMAoLcnVuX3JlcXVlc3QYASABKAsyGS5hZ2VudC52MS5BZ2VudFJ1blJlcXVlc3RIABI6ChNleGVjX2NsaWVudF9tZXNzYWdlGAIgASgLMhsuYWdlbnQudjEuRXhlY0NsaWVudE1lc3NhZ2VIABJJChtleGVjX2NsaWVudF9jb250cm9sX21lc3NhZ2UYBSABKAsyIi5hZ2VudC52MS5FeGVjQ2xpZW50Q29udHJvbE1lc3NhZ2VIABI2ChFrdl9jbGllbnRfbWVzc2FnZRgDIAEoCzIZLmFnZW50LnYxLkt2Q2xpZW50TWVzc2FnZUgAEjsKE2NvbnZlcnNhdGlvbl9hY3Rpb24YBCABKAsyHC5hZ2VudC52MS5Db252ZXJzYXRpb25BY3Rpb25IABI9ChRpbnRlcmFjdGlvbl9yZXNwb25zZRgGIAEoCzIdLmFnZW50LnYxLkludGVyYWN0aW9uUmVzcG9uc2VIABI1ChBjbGllbnRfaGVhcnRiZWF0GAcgASgLMhkuYWdlbnQudjEuQ2xpZW50SGVhcnRiZWF0SAASMwoPcHJld2FybV9yZXF1ZXN0GAggASgLMhguYWdlbnQudjEuUHJld2FybVJlcXVlc3RIAEIJCgdtZXNzYWdlIqIDChJBZ2VudFNlcnZlck1lc3NhZ2USOQoSaW50ZXJhY3Rpb25fdXBkYXRlGAEgASgLMhsuYWdlbnQudjEuSW50ZXJhY3Rpb25VcGRhdGVIABI6ChNleGVjX3NlcnZlcl9tZXNzYWdlGAIgASgLMhsuYWdlbnQudjEuRXhlY1NlcnZlck1lc3NhZ2VIABJJChtleGVjX3NlcnZlcl9jb250cm9sX21lc3NhZ2UYBSABKAsyIi5hZ2VudC52MS5FeGVjU2VydmVyQ29udHJvbE1lc3NhZ2VIABJOCh5jb252ZXJzYXRpb25fY2hlY2twb2ludF91cGRhdGUYAyABKAsyJC5hZ2VudC52MS5Db252ZXJzYXRpb25TdGF0ZVN0cnVjdHVyZUgAEjYKEWt2X3NlcnZlcl9tZXNzYWdlGAQgASgLMhkuYWdlbnQudjEuS3ZTZXJ2ZXJNZXNzYWdlSAASNwoRaW50ZXJhY3Rpb25fcXVlcnkYByABKAsyGi5hZ2VudC52MS5JbnRlcmFjdGlvblF1ZXJ5SABCCQoHbWVzc2FnZSIoChBOYW1lQWdlbnRSZXF1ZXN0EhQKDHVzZXJfbWVzc2FnZRgBIAEoCSIhChFOYW1lQWdlbnRSZXNwb25zZRIMCgRuYW1lGAEgASgJIjIKFkdldFVzYWJsZU1vZGVsc1JlcXVlc3QSGAoQY3VzdG9tX21vZGVsX2lkcxgBIAMoCSJBChdHZXRVc2FibGVNb2RlbHNSZXNwb25zZRImCgZtb2RlbHMYASADKAsyFi5hZ2VudC52MS5Nb2RlbERldGFpbHMiHgocR2V0RGVmYXVsdE1vZGVsRm9yQ2xpUmVxdWVzdCJGCh1HZXREZWZhdWx0TW9kZWxGb3JDbGlSZXNwb25zZRIlCgVtb2RlbBgBIAEoCzIWLmFnZW50LnYxLk1vZGVsRGV0YWlscyIfCh1HZXRBbGxvd2VkTW9kZWxJbnRlbnRzUmVxdWVzdCI3Ch5HZXRBbGxvd2VkTW9kZWxJbnRlbnRzUmVzcG9uc2USFQoNbW9kZWxfaW50ZW50cxgBIAMoCSKXAgoTSWRlRWRpdG9yc1N0YXRlRmlsZRIVCg1yZWxhdGl2ZV9wYXRoGAEgASgJEhUKDWFic29sdXRlX3BhdGgYAiABKAkSIQoUaXNfY3VycmVudGx5X2ZvY3VzZWQYAyABKAhIAIgBARIgChNjdXJyZW50X2xpbmVfbnVtYmVyGAQgASgFSAGIAQESHgoRY3VycmVudF9saW5lX3RleHQYBSABKAlIAogBARIXCgpsaW5lX2NvdW50GAYgASgFSAOIAQFCFwoVX2lzX2N1cnJlbnRseV9mb2N1c2VkQhYKFF9jdXJyZW50X2xpbmVfbnVtYmVyQhQKEl9jdXJyZW50X2xpbmVfdGV4dEINCgtfbGluZV9jb3VudCJTChNJZGVFZGl0b3JzU3RhdGVMaXRlEjwKFXJlY2VudGx5X3ZpZXdlZF9maWxlcxgBIAMoCzIdLmFnZW50LnYxLklkZUVkaXRvcnNTdGF0ZUZpbGUidAoWQXBwbHlBZ2VudERpZmZUb29sQ2FsbBIqCgRhcmdzGAEgASgLMhwuYWdlbnQudjEuQXBwbHlBZ2VudERpZmZBcmdzEi4KBnJlc3VsdBgCIAEoCzIeLmFnZW50LnYxLkFwcGx5QWdlbnREaWZmUmVzdWx0IiYKEkFwcGx5QWdlbnREaWZmQXJncxIQCghhZ2VudF9pZBgBIAEoCSKEAQoUQXBwbHlBZ2VudERpZmZSZXN1bHQSMgoHc3VjY2VzcxgBIAEoCzIfLmFnZW50LnYxLkFwcGx5QWdlbnREaWZmU3VjY2Vzc0gAEi4KBWVycm9yGAIgASgLMh0uYWdlbnQudjEuQXBwbHlBZ2VudERpZmZFcnJvckgAQggKBnJlc3VsdCJOChVBcHBseUFnZW50RGlmZlN1Y2Nlc3MSNQoPYXBwbGllZF9jaGFuZ2VzGAEgAygLMhwuYWdlbnQudjEuQXBwbGllZEFnZW50Q2hhbmdlIukBChJBcHBsaWVkQWdlbnRDaGFuZ2USDAoEcGF0aBgBIAEoCRITCgtjaGFuZ2VfdHlwZRgCIAEoBRIbCg5iZWZvcmVfY29udGVudBgDIAEoCUgAiAEBEhoKDWFmdGVyX2NvbnRlbnQYBCABKAlIAYgBARISCgVlcnJvchgFIAEoCUgCiAEBEh4KEW1lc3NhZ2VfZm9yX21vZGVsGAYgASgJSAOIAQFCEQoPX2JlZm9yZV9jb250ZW50QhAKDl9hZnRlcl9jb250ZW50QggKBl9lcnJvckIUChJfbWVzc2FnZV9mb3JfbW9kZWwiWwoTQXBwbHlBZ2VudERpZmZFcnJvchINCgVlcnJvchgBIAEoCRI1Cg9hcHBsaWVkX2NoYW5nZXMYAiADKAsyHC5hZ2VudC52MS5BcHBsaWVkQWdlbnRDaGFuZ2UiawoTQXNrUXVlc3Rpb25Ub29sQ2FsbBInCgRhcmdzGAEgASgLMhkuYWdlbnQudjEuQXNrUXVlc3Rpb25BcmdzEisKBnJlc3VsdBgCIAEoCzIbLmFnZW50LnYxLkFza1F1ZXN0aW9uUmVzdWx0Io8BCg9Bc2tRdWVzdGlvbkFyZ3MSDQoFdGl0bGUYASABKAkSNQoJcXVlc3Rpb25zGAIgAygLMiIuYWdlbnQudjEuQXNrUXVlc3Rpb25BcmdzX1F1ZXN0aW9uEhEKCXJ1bl9hc3luYxgFIAEoCBIjChthc3luY19vcmlnaW5hbF90b29sX2NhbGxfaWQYBiABKAkigQEKGEFza1F1ZXN0aW9uQXJnc19RdWVzdGlvbhIKCgJpZBgBIAEoCRIOCgZwcm9tcHQYAiABKAkSMQoHb3B0aW9ucxgDIAMoCzIgLmFnZW50LnYxLkFza1F1ZXN0aW9uQXJnc19PcHRpb24SFgoOYWxsb3dfbXVsdGlwbGUYBCABKAgiMwoWQXNrUXVlc3Rpb25BcmdzX09wdGlvbhIKCgJpZBgBIAEoCRINCgVsYWJlbBgCIAEoCSISChBBc2tRdWVzdGlvbkFzeW5jItsBChFBc2tRdWVzdGlvblJlc3VsdBIvCgdzdWNjZXNzGAEgASgLMhwuYWdlbnQudjEuQXNrUXVlc3Rpb25TdWNjZXNzSAASKwoFZXJyb3IYAiABKAsyGi5hZ2VudC52MS5Bc2tRdWVzdGlvbkVycm9ySAASMQoIcmVqZWN0ZWQYAyABKAsyHS5hZ2VudC52MS5Bc2tRdWVzdGlvblJlamVjdGVkSAASKwoFYXN5bmMYBCABKAsyGi5hZ2VudC52MS5Bc2tRdWVzdGlvbkFzeW5jSABCCAoGcmVzdWx0IkoKEkFza1F1ZXN0aW9uU3VjY2VzcxI0CgdhbnN3ZXJzGAEgAygLMiMuYWdlbnQudjEuQXNrUXVlc3Rpb25TdWNjZXNzX0Fuc3dlciJNChlBc2tRdWVzdGlvblN1Y2Nlc3NfQW5zd2VyEhMKC3F1ZXN0aW9uX2lkGAEgASgJEhsKE3NlbGVjdGVkX29wdGlvbl9pZHMYAiADKAkiKQoQQXNrUXVlc3Rpb25FcnJvchIVCg1lcnJvcl9tZXNzYWdlGAEgASgJIiUKE0Fza1F1ZXN0aW9uUmVqZWN0ZWQSDgoGcmVhc29uGAEgASgJIv0FChhCYWNrZ3JvdW5kU2hlbGxTcGF3bkFyZ3MSDwoHY29tbWFuZBgBIAEoCRIZChF3b3JraW5nX2RpcmVjdG9yeRgCIAEoCRIUCgx0b29sX2NhbGxfaWQYAyABKAkSOwoOcGFyc2luZ19yZXN1bHQYBCABKAsyIy5hZ2VudC52MS5TaGVsbENvbW1hbmRQYXJzaW5nUmVzdWx0EjQKDnNhbmRib3hfcG9saWN5GAUgASgLMhcuYWdlbnQudjEuU2FuZGJveFBvbGljeUgAiAEBEiUKHWVuYWJsZV93cml0ZV9zaGVsbF9zdGRpbl90b29sGAYgASgIEhgKC2Rlc2NyaXB0aW9uGAcgASgJSAGIAQESQQoRY2xhc3NpZmllcl9yZXN1bHQYCCABKAsyIS5hZ2VudC52MS5Db21tYW5kQ2xhc3NpZmllclJlc3VsdEgCiAEBEkkKE291dHB1dF9ub3RpZmljYXRpb24YCSABKAsyJy5hZ2VudC52MS5TaGVsbE91dHB1dE5vdGlmaWNhdGlvbkNvbmZpZ0gDiAEBEj0KE3NtYXJ0X21vZGVfYXBwcm92YWwYCiABKAsyGy5hZ2VudC52MS5TbWFydE1vZGVBcHByb3ZhbEgEiAEBEk4KGWhvb2tfYXBwcm92YWxfcmVxdWlyZW1lbnQYCyABKAsyJi5hZ2VudC52MS5TaGVsbEhvb2tBcHByb3ZhbFJlcXVpcmVtZW50SAWIAQESFQoNc2tpcF9hcHByb3ZhbBgMIAEoCBIcCg9jb252ZXJzYXRpb25faWQYDSABKAlIBogBAUIRCg9fc2FuZGJveF9wb2xpY3lCDgoMX2Rlc2NyaXB0aW9uQhQKEl9jbGFzc2lmaWVyX3Jlc3VsdEIWChRfb3V0cHV0X25vdGlmaWNhdGlvbkIWChRfc21hcnRfbW9kZV9hcHByb3ZhbEIcChpfaG9va19hcHByb3ZhbF9yZXF1aXJlbWVudEISChBfY29udmVyc2F0aW9uX2lkIoECChpCYWNrZ3JvdW5kU2hlbGxTcGF3blJlc3VsdBI4CgdzdWNjZXNzGAEgASgLMiUuYWdlbnQudjEuQmFja2dyb3VuZFNoZWxsU3Bhd25TdWNjZXNzSAASNAoFZXJyb3IYAiABKAsyIy5hZ2VudC52MS5CYWNrZ3JvdW5kU2hlbGxTcGF3bkVycm9ySAASKwoIcmVqZWN0ZWQYAyABKAsyFy5hZ2VudC52MS5TaGVsbFJlamVjdGVkSAASPAoRcGVybWlzc2lvbl9kZW5pZWQYBCABKAsyHy5hZ2VudC52MS5TaGVsbFBlcm1pc3Npb25EZW5pZWRIAEIICgZyZXN1bHQidQobQmFja2dyb3VuZFNoZWxsU3Bhd25TdWNjZXNzEhAKCHNoZWxsX2lkGAEgASgNEg8KB2NvbW1hbmQYAiABKAkSGQoRd29ya2luZ19kaXJlY3RvcnkYAyABKAkSEAoDcGlkGAQgASgNSACIAQFCBgoEX3BpZCJWChlCYWNrZ3JvdW5kU2hlbGxTcGF3bkVycm9yEg8KB2NvbW1hbmQYASABKAkSGQoRd29ya2luZ19kaXJlY3RvcnkYAiABKAkSDQoFZXJyb3IYAyABKAkiNgoTV3JpdGVTaGVsbFN0ZGluQXJncxIQCghzaGVsbF9pZBgBIAEoDRINCgVjaGFycxgCIAEoCSKHAQoVV3JpdGVTaGVsbFN0ZGluUmVzdWx0EjMKB3N1Y2Nlc3MYASABKAsyIC5hZ2VudC52MS5Xcml0ZVNoZWxsU3RkaW5TdWNjZXNzSAASLwoFZXJyb3IYAiABKAsyHi5hZ2VudC52MS5Xcml0ZVNoZWxsU3RkaW5FcnJvckgAQggKBnJlc3VsdCJdChZXcml0ZVNoZWxsU3RkaW5TdWNjZXNzEhAKCHNoZWxsX2lkGAEgASgNEjEKKXRlcm1pbmFsX2ZpbGVfbGVuZ3RoX2JlZm9yZV9pbnB1dF93cml0dGVuGAIgASgNIiUKFFdyaXRlU2hlbGxTdGRpbkVycm9yEg0KBWVycm9yGAEgASgJIiIKCkNvb3JkaW5hdGUSCQoBeBgBIAEoBRIJCgF5GAIgASgFIlUKD0NvbXB1dGVyVXNlQXJncxIUCgx0b29sX2NhbGxfaWQYASABKAkSLAoHYWN0aW9ucxgCIAMoCzIbLmFnZW50LnYxLkNvbXB1dGVyVXNlQWN0aW9uIoEEChFDb21wdXRlclVzZUFjdGlvbhIvCgptb3VzZV9tb3ZlGAEgASgLMhkuYWdlbnQudjEuTW91c2VNb3ZlQWN0aW9uSAASJgoFY2xpY2sYAiABKAsyFS5hZ2VudC52MS5DbGlja0FjdGlvbkgAEi8KCm1vdXNlX2Rvd24YAyABKAsyGS5hZ2VudC52MS5Nb3VzZURvd25BY3Rpb25IABIrCghtb3VzZV91cBgEIAEoCzIXLmFnZW50LnYxLk1vdXNlVXBBY3Rpb25IABIkCgRkcmFnGAUgASgLMhQuYWdlbnQudjEuRHJhZ0FjdGlvbkgAEigKBnNjcm9sbBgGIAEoCzIWLmFnZW50LnYxLlNjcm9sbEFjdGlvbkgAEiQKBHR5cGUYByABKAsyFC5hZ2VudC52MS5UeXBlQWN0aW9uSAASIgoDa2V5GAggASgLMhMuYWdlbnQudjEuS2V5QWN0aW9uSAASJAoEd2FpdBgJIAEoCzIULmFnZW50LnYxLldhaXRBY3Rpb25IABIwCgpzY3JlZW5zaG90GAogASgLMhouYWdlbnQudjEuU2NyZWVuc2hvdEFjdGlvbkgAEjkKD2N1cnNvcl9wb3NpdGlvbhgLIAEoCzIeLmFnZW50LnYxLkN1cnNvclBvc2l0aW9uQWN0aW9uSABCCAoGYWN0aW9uIjsKD01vdXNlTW92ZUFjdGlvbhIoCgpjb29yZGluYXRlGAEgASgLMhQuYWdlbnQudjEuQ29vcmRpbmF0ZSKYAQoLQ2xpY2tBY3Rpb24SLQoKY29vcmRpbmF0ZRgBIAEoCzIULmFnZW50LnYxLkNvb3JkaW5hdGVIAIgBARIOCgZidXR0b24YAiABKAUSDQoFY291bnQYAyABKAUSGgoNbW9kaWZpZXJfa2V5cxgEIAEoCUgBiAEBQg0KC19jb29yZGluYXRlQhAKDl9tb2RpZmllcl9rZXlzIiEKD01vdXNlRG93bkFjdGlvbhIOCgZidXR0b24YASABKAUiHwoNTW91c2VVcEFjdGlvbhIOCgZidXR0b24YASABKAUiQAoKRHJhZ0FjdGlvbhIiCgRwYXRoGAEgAygLMhQuYWdlbnQudjEuQ29vcmRpbmF0ZRIOCgZidXR0b24YAiABKAUinQEKDFNjcm9sbEFjdGlvbhItCgpjb29yZGluYXRlGAEgASgLMhQuYWdlbnQudjEuQ29vcmRpbmF0ZUgAiAEBEhEKCWRpcmVjdGlvbhgCIAEoBRIOCgZhbW91bnQYAyABKAUSGgoNbW9kaWZpZXJfa2V5cxgEIAEoCUgBiAEBQg0KC19jb29yZGluYXRlQhAKDl9tb2RpZmllcl9rZXlzIhoKClR5cGVBY3Rpb24SDAoEdGV4dBgBIAEoCSJMCglLZXlBY3Rpb24SCwoDa2V5GAEgASgJEh0KEGhvbGRfZHVyYXRpb25fbXMYAiABKAVIAIgBAUITChFfaG9sZF9kdXJhdGlvbl9tcyIhCgpXYWl0QWN0aW9uEhMKC2R1cmF0aW9uX21zGAEgASgFIhIKEFNjcmVlbnNob3RBY3Rpb24iFgoUQ3Vyc29yUG9zaXRpb25BY3Rpb24iewoRQ29tcHV0ZXJVc2VSZXN1bHQSLwoHc3VjY2VzcxgBIAEoCzIcLmFnZW50LnYxLkNvbXB1dGVyVXNlU3VjY2Vzc0gAEisKBWVycm9yGAIgASgLMhouYWdlbnQudjEuQ29tcHV0ZXJVc2VFcnJvckgAQggKBnJlc3VsdCL7AQoSQ29tcHV0ZXJVc2VTdWNjZXNzEhQKDGFjdGlvbl9jb3VudBgBIAEoBRITCgtkdXJhdGlvbl9tcxgCIAEoBRIXCgpzY3JlZW5zaG90GAMgASgJSACIAQESEAoDbG9nGAQgASgJSAGIAQESHAoPc2NyZWVuc2hvdF9wYXRoGAUgASgJSAKIAQESMgoPY3Vyc29yX3Bvc2l0aW9uGAYgASgLMhQuYWdlbnQudjEuQ29vcmRpbmF0ZUgDiAEBQg0KC19zY3JlZW5zaG90QgYKBF9sb2dCEgoQX3NjcmVlbnNob3RfcGF0aEISChBfY3Vyc29yX3Bvc2l0aW9uIsABChBDb21wdXRlclVzZUVycm9yEg0KBWVycm9yGAEgASgJEhQKDGFjdGlvbl9jb3VudBgCIAEoBRITCgtkdXJhdGlvbl9tcxgDIAEoBRIQCgNsb2cYBCABKAlIAIgBARIXCgpzY3JlZW5zaG90GAUgASgJSAGIAQESHAoPc2NyZWVuc2hvdF9wYXRoGAYgASgJSAKIAQFCBgoEX2xvZ0INCgtfc2NyZWVuc2hvdEISChBfc2NyZWVuc2hvdF9wYXRoImsKE0NvbXB1dGVyVXNlVG9vbENhbGwSJwoEYXJncxgBIAEoCzIZLmFnZW50LnYxLkNvbXB1dGVyVXNlQXJncxIrCgZyZXN1bHQYAiABKAsyGy5hZ2VudC52MS5Db21wdXRlclVzZVJlc3VsdCJoChJDcmVhdGVQbGFuVG9vbENhbGwSJgoEYXJncxgBIAEoCzIYLmFnZW50LnYxLkNyZWF0ZVBsYW5BcmdzEioKBnJlc3VsdBgCIAEoCzIaLmFnZW50LnYxLkNyZWF0ZVBsYW5SZXN1bHQiOAoFUGhhc2USDAoEbmFtZRgBIAEoCRIhCgV0b2RvcxgCIAMoCzISLmFnZW50LnYxLlRvZG9JdGVtIpYBCg5DcmVhdGVQbGFuQXJncxIMCgRwbGFuGAEgASgJEiEKBXRvZG9zGAIgAygLMhIuYWdlbnQudjEuVG9kb0l0ZW0SEAoIb3ZlcnZpZXcYAyABKAkSDAoEbmFtZRgEIAEoCRISCgppc19wcm9qZWN0GAUgASgIEh8KBnBoYXNlcxgGIAMoCzIPLmFnZW50LnYxLlBoYXNlIooBChBDcmVhdGVQbGFuUmVzdWx0EhAKCHBsYW5fdXJpGAMgASgJEi4KB3N1Y2Nlc3MYASABKAsyGy5hZ2VudC52MS5DcmVhdGVQbGFuU3VjY2Vzc0gAEioKBWVycm9yGAIgASgLMhkuYWdlbnQudjEuQ3JlYXRlUGxhbkVycm9ySABCCAoGcmVzdWx0IhMKEUNyZWF0ZVBsYW5TdWNjZXNzIiAKD0NyZWF0ZVBsYW5FcnJvchINCgVlcnJvchgBIAEoCSJWChZDcmVhdGVQbGFuUmVxdWVzdFF1ZXJ5EiYKBGFyZ3MYASABKAsyGC5hZ2VudC52MS5DcmVhdGVQbGFuQXJncxIUCgx0b29sX2NhbGxfaWQYAiABKAkiRwoZQ3JlYXRlUGxhblJlcXVlc3RSZXNwb25zZRIqCgZyZXN1bHQYASABKAsyGi5hZ2VudC52MS5DcmVhdGVQbGFuUmVzdWx0IhYKFEN1cnNvclJ1bGVUeXBlR2xvYmFsIigKF0N1cnNvclJ1bGVUeXBlRmlsZUdsb2JzEg0KBWdsb2JzGAEgAygJIjEKGkN1cnNvclJ1bGVUeXBlQWdlbnRGZXRjaGVkEhMKC2Rlc2NyaXB0aW9uGAEgASgJIiAKHkN1cnNvclJ1bGVUeXBlTWFudWFsbHlBdHRhY2hlZCKLAgoOQ3Vyc29yUnVsZVR5cGUSMAoGZ2xvYmFsGAEgASgLMh4uYWdlbnQudjEuQ3Vyc29yUnVsZVR5cGVHbG9iYWxIABI5CgxmaWxlX2dsb2JiZWQYAiABKAsyIS5hZ2VudC52MS5DdXJzb3JSdWxlVHlwZUZpbGVHbG9ic0gAEj0KDWFnZW50X2ZldGNoZWQYAyABKAsyJC5hZ2VudC52MS5DdXJzb3JSdWxlVHlwZUFnZW50RmV0Y2hlZEgAEkUKEW1hbnVhbGx5X2F0dGFjaGVkGAQgASgLMiguYWdlbnQudjEuQ3Vyc29yUnVsZVR5cGVNYW51YWxseUF0dGFjaGVkSABCBgoEdHlwZSLIAQoKQ3Vyc29yUnVsZRIRCglmdWxsX3BhdGgYASABKAkSDwoHY29udGVudBgCIAEoCRImCgR0eXBlGAMgASgLMhguYWdlbnQudjEuQ3Vyc29yUnVsZVR5cGUSDgoGc291cmNlGAQgASgFEh4KEWdpdF9yZW1vdGVfb3JpZ2luGAUgASgJSACIAQESGAoLcGFyc2VfZXJyb3IYBiABKAlIAYgBAUIUChJfZ2l0X3JlbW90ZV9vcmlnaW5CDgoMX3BhcnNlX2Vycm9yIjAKCkRlbGV0ZUFyZ3MSDAoEcGF0aBgBIAEoCRIUCgx0b29sX2NhbGxfaWQYAiABKAki7QIKDERlbGV0ZVJlc3VsdBIqCgdzdWNjZXNzGAEgASgLMhcuYWdlbnQudjEuRGVsZXRlU3VjY2Vzc0gAEjYKDmZpbGVfbm90X2ZvdW5kGAIgASgLMhwuYWdlbnQudjEuRGVsZXRlRmlsZU5vdEZvdW5kSAASKwoIbm90X2ZpbGUYAyABKAsyFy5hZ2VudC52MS5EZWxldGVOb3RGaWxlSAASPQoRcGVybWlzc2lvbl9kZW5pZWQYBCABKAsyIC5hZ2VudC52MS5EZWxldGVQZXJtaXNzaW9uRGVuaWVkSAASLQoJZmlsZV9idXN5GAUgASgLMhguYWdlbnQudjEuRGVsZXRlRmlsZUJ1c3lIABIsCghyZWplY3RlZBgGIAEoCzIYLmFnZW50LnYxLkRlbGV0ZVJlamVjdGVkSAASJgoFZXJyb3IYByABKAsyFS5hZ2VudC52MS5EZWxldGVFcnJvckgAQggKBnJlc3VsdCJcCg1EZWxldGVTdWNjZXNzEgwKBHBhdGgYASABKAkSFAoMZGVsZXRlZF9maWxlGAIgASgJEhEKCWZpbGVfc2l6ZRgDIAEoAxIUCgxwcmV2X2NvbnRlbnQYBCABKAkiIgoSRGVsZXRlRmlsZU5vdEZvdW5kEgwKBHBhdGgYASABKAkiMgoNRGVsZXRlTm90RmlsZRIMCgRwYXRoGAEgASgJEhMKC2FjdHVhbF90eXBlGAIgASgJIlkKFkRlbGV0ZVBlcm1pc3Npb25EZW5pZWQSDAoEcGF0aBgBIAEoCRIcChRjbGllbnRfdmlzaWJsZV9lcnJvchgCIAEoCRITCgtpc19yZWFkb25seRgDIAEoCCIeCg5EZWxldGVGaWxlQnVzeRIMCgRwYXRoGAEgASgJIi4KDkRlbGV0ZVJlamVjdGVkEgwKBHBhdGgYASABKAkSDgoGcmVhc29uGAIgASgJIioKC0RlbGV0ZUVycm9yEgwKBHBhdGgYASABKAkSDQoFZXJyb3IYAiABKAkiXAoORGVsZXRlVG9vbENhbGwSIgoEYXJncxgBIAEoCzIULmFnZW50LnYxLkRlbGV0ZUFyZ3MSJgoGcmVzdWx0GAIgASgLMhYuYWdlbnQudjEuRGVsZXRlUmVzdWx0IjUKD0RpYWdub3N0aWNzQXJncxIMCgRwYXRoGAEgASgJEhQKDHRvb2xfY2FsbF9pZBgCIAEoCSKvAgoRRGlhZ25vc3RpY3NSZXN1bHQSLwoHc3VjY2VzcxgBIAEoCzIcLmFnZW50LnYxLkRpYWdub3N0aWNzU3VjY2Vzc0gAEisKBWVycm9yGAIgASgLMhouYWdlbnQudjEuRGlhZ25vc3RpY3NFcnJvckgAEjEKCHJlamVjdGVkGAMgASgLMh0uYWdlbnQudjEuRGlhZ25vc3RpY3NSZWplY3RlZEgAEjsKDmZpbGVfbm90X2ZvdW5kGAQgASgLMiEuYWdlbnQudjEuRGlhZ25vc3RpY3NGaWxlTm90Rm91bmRIABJCChFwZXJtaXNzaW9uX2RlbmllZBgFIAEoCzIlLmFnZW50LnYxLkRpYWdub3N0aWNzUGVybWlzc2lvbkRlbmllZEgAQggKBnJlc3VsdCJoChJEaWFnbm9zdGljc1N1Y2Nlc3MSDAoEcGF0aBgBIAEoCRIpCgtkaWFnbm9zdGljcxgCIAMoCzIULmFnZW50LnYxLkRpYWdub3N0aWMSGQoRdG90YWxfZGlhZ25vc3RpY3MYAyABKAUifwoKRGlhZ25vc3RpYxIQCghzZXZlcml0eRgBIAEoBRIeCgVyYW5nZRgCIAEoCzIPLmFnZW50LnYxLlJhbmdlEg8KB21lc3NhZ2UYAyABKAkSDgoGc291cmNlGAQgASgJEgwKBGNvZGUYBSABKAkSEAoIaXNfc3RhbGUYBiABKAgiLwoQRGlhZ25vc3RpY3NFcnJvchIMCgRwYXRoGAEgASgJEg0KBWVycm9yGAIgASgJIjMKE0RpYWdub3N0aWNzUmVqZWN0ZWQSDAoEcGF0aBgBIAEoCRIOCgZyZWFzb24YAiABKAkiJwoXRGlhZ25vc3RpY3NGaWxlTm90Rm91bmQSDAoEcGF0aBgBIAEoCSIrChtEaWFnbm9zdGljc1Blcm1pc3Npb25EZW5pZWQSDAoEcGF0aBgBIAEoCSJICghFZGl0QXJncxIMCgRwYXRoGAEgASgJEhsKDnN0cmVhbV9jb250ZW50GAYgASgJSACIAQFCEQoPX3N0cmVhbV9jb250ZW50ItYCCgpFZGl0UmVzdWx0EigKB3N1Y2Nlc3MYASABKAsyFS5hZ2VudC52MS5FZGl0U3VjY2Vzc0gAEjQKDmZpbGVfbm90X2ZvdW5kGAIgASgLMhouYWdlbnQudjEuRWRpdEZpbGVOb3RGb3VuZEgAEkQKFnJlYWRfcGVybWlzc2lvbl9kZW5pZWQYAyABKAsyIi5hZ2VudC52MS5FZGl0UmVhZFBlcm1pc3Npb25EZW5pZWRIABJGChd3cml0ZV9wZXJtaXNzaW9uX2RlbmllZBgEIAEoCzIjLmFnZW50LnYxLkVkaXRXcml0ZVBlcm1pc3Npb25EZW5pZWRIABIqCghyZWplY3RlZBgGIAEoCzIWLmFnZW50LnYxLkVkaXRSZWplY3RlZEgAEiQKBWVycm9yGAcgASgLMhMuYWdlbnQudjEuRWRpdEVycm9ySABCCAoGcmVzdWx0IqQCCgtFZGl0U3VjY2VzcxIMCgRwYXRoGAEgASgJEhgKC2xpbmVzX2FkZGVkGAMgASgFSACIAQESGgoNbGluZXNfcmVtb3ZlZBgEIAEoBUgBiAEBEhgKC2RpZmZfc3RyaW5nGAUgASgJSAKIAQESJQoYYmVmb3JlX2Z1bGxfZmlsZV9jb250ZW50GAYgASgJSAOIAQESHwoXYWZ0ZXJfZnVsbF9maWxlX2NvbnRlbnQYByABKAkSFAoHbWVzc2FnZRgIIAEoCUgEiAEBQg4KDF9saW5lc19hZGRlZEIQCg5fbGluZXNfcmVtb3ZlZEIOCgxfZGlmZl9zdHJpbmdCGwoZX2JlZm9yZV9mdWxsX2ZpbGVfY29udGVudEIKCghfbWVzc2FnZSIgChBFZGl0RmlsZU5vdEZvdW5kEgwKBHBhdGgYASABKAkiKAoYRWRpdFJlYWRQZXJtaXNzaW9uRGVuaWVkEgwKBHBhdGgYASABKAkiTQoZRWRpdFdyaXRlUGVybWlzc2lvbkRlbmllZBIMCgRwYXRoGAEgASgJEg0KBWVycm9yGAIgASgJEhMKC2lzX3JlYWRvbmx5GAMgASgIIiwKDEVkaXRSZWplY3RlZBIMCgRwYXRoGAEgASgJEg4KBnJlYXNvbhgCIAEoCSJiCglFZGl0RXJyb3ISDAoEcGF0aBgBIAEoCRINCgVlcnJvchgCIAEoCRIgChNtb2RlbF92aXNpYmxlX2Vycm9yGAUgASgJSACIAQFCFgoUX21vZGVsX3Zpc2libGVfZXJyb3IiVgoMRWRpdFRvb2xDYWxsEiAKBGFyZ3MYASABKAsyEi5hZ2VudC52MS5FZGl0QXJncxIkCgZyZXN1bHQYAiABKAsyFC5hZ2VudC52MS5FZGl0UmVzdWx0IjEKEUVkaXRUb29sQ2FsbERlbHRhEhwKFHN0cmVhbV9jb250ZW50X2RlbHRhGAEgASgJIjEKDEV4YUZldGNoQXJncxILCgNpZHMYASADKAkSFAoMdG9vbF9jYWxsX2lkGAIgASgJIqIBCg5FeGFGZXRjaFJlc3VsdBIsCgdzdWNjZXNzGAEgASgLMhkuYWdlbnQudjEuRXhhRmV0Y2hTdWNjZXNzSAASKAoFZXJyb3IYAiABKAsyFy5hZ2VudC52MS5FeGFGZXRjaEVycm9ySAASLgoIcmVqZWN0ZWQYAyABKAsyGi5hZ2VudC52MS5FeGFGZXRjaFJlamVjdGVkSABCCAoGcmVzdWx0Ij4KD0V4YUZldGNoU3VjY2VzcxIrCghjb250ZW50cxgBIAMoCzIZLmFnZW50LnYxLkV4YUZldGNoQ29udGVudCIeCg1FeGFGZXRjaEVycm9yEg0KBWVycm9yGAEgASgJIiIKEEV4YUZldGNoUmVqZWN0ZWQSDgoGcmVhc29uGAEgASgJIlMKD0V4YUZldGNoQ29udGVudBINCgV0aXRsZRgBIAEoCRILCgN1cmwYAiABKAkSDAoEdGV4dBgDIAEoCRIWCg5wdWJsaXNoZWRfZGF0ZRgEIAEoCSJiChBFeGFGZXRjaFRvb2xDYWxsEiQKBGFyZ3MYASABKAsyFi5hZ2VudC52MS5FeGFGZXRjaEFyZ3MSKAoGcmVzdWx0GAIgASgLMhguYWdlbnQudjEuRXhhRmV0Y2hSZXN1bHQiPAoURXhhRmV0Y2hSZXF1ZXN0UXVlcnkSJAoEYXJncxgBIAEoCzIWLmFnZW50LnYxLkV4YUZldGNoQXJncyKjAQoXRXhhRmV0Y2hSZXF1ZXN0UmVzcG9uc2USPgoIYXBwcm92ZWQYASABKAsyKi5hZ2VudC52MS5FeGFGZXRjaFJlcXVlc3RSZXNwb25zZV9BcHByb3ZlZEgAEj4KCHJlamVjdGVkGAIgASgLMiouYWdlbnQudjEuRXhhRmV0Y2hSZXF1ZXN0UmVzcG9uc2VfUmVqZWN0ZWRIAEIICgZyZXN1bHQiIgogRXhhRmV0Y2hSZXF1ZXN0UmVzcG9uc2VfQXBwcm92ZWQiMgogRXhhRmV0Y2hSZXF1ZXN0UmVzcG9uc2VfUmVqZWN0ZWQSDgoGcmVhc29uGAEgASgJIlcKDUV4YVNlYXJjaEFyZ3MSDQoFcXVlcnkYASABKAkSDAoEdHlwZRgCIAEoCRITCgtudW1fcmVzdWx0cxgDIAEoBRIUCgx0b29sX2NhbGxfaWQYBCABKAkipgEKD0V4YVNlYXJjaFJlc3VsdBItCgdzdWNjZXNzGAEgASgLMhouYWdlbnQudjEuRXhhU2VhcmNoU3VjY2Vzc0gAEikKBWVycm9yGAIgASgLMhguYWdlbnQudjEuRXhhU2VhcmNoRXJyb3JIABIvCghyZWplY3RlZBgDIAEoCzIbLmFnZW50LnYxLkV4YVNlYXJjaFJlamVjdGVkSABCCAoGcmVzdWx0IkQKEEV4YVNlYXJjaFN1Y2Nlc3MSMAoKcmVmZXJlbmNlcxgBIAMoCzIcLmFnZW50LnYxLkV4YVNlYXJjaFJlZmVyZW5jZSIfCg5FeGFTZWFyY2hFcnJvchINCgVlcnJvchgBIAEoCSIjChFFeGFTZWFyY2hSZWplY3RlZBIOCgZyZWFzb24YASABKAkiVgoSRXhhU2VhcmNoUmVmZXJlbmNlEg0KBXRpdGxlGAEgASgJEgsKA3VybBgCIAEoCRIMCgR0ZXh0GAMgASgJEhYKDnB1Ymxpc2hlZF9kYXRlGAQgASgJImUKEUV4YVNlYXJjaFRvb2xDYWxsEiUKBGFyZ3MYASABKAsyFy5hZ2VudC52MS5FeGFTZWFyY2hBcmdzEikKBnJlc3VsdBgCIAEoCzIZLmFnZW50LnYxLkV4YVNlYXJjaFJlc3VsdCI+ChVFeGFTZWFyY2hSZXF1ZXN0UXVlcnkSJQoEYXJncxgBIAEoCzIXLmFnZW50LnYxLkV4YVNlYXJjaEFyZ3MipgEKGEV4YVNlYXJjaFJlcXVlc3RSZXNwb25zZRI/CghhcHByb3ZlZBgBIAEoCzIrLmFnZW50LnYxLkV4YVNlYXJjaFJlcXVlc3RSZXNwb25zZV9BcHByb3ZlZEgAEj8KCHJlamVjdGVkGAIgASgLMisuYWdlbnQudjEuRXhhU2VhcmNoUmVxdWVzdFJlc3BvbnNlX1JlamVjdGVkSABCCAoGcmVzdWx0IiMKIUV4YVNlYXJjaFJlcXVlc3RSZXNwb25zZV9BcHByb3ZlZCIzCiFFeGFTZWFyY2hSZXF1ZXN0UmVzcG9uc2VfUmVqZWN0ZWQSDgoGcmVhc29uGAEgASgJIiMKFUV4ZWNDbGllbnRTdHJlYW1DbG9zZRIKCgJpZBgBIAEoDSJ+Cg9FeGVjQ2xpZW50VGhyb3cSCgoCaWQYASABKA0SDQoFZXJyb3IYAiABKAkSGAoLc3RhY2tfdHJhY2UYAyABKAlIAIgBARIXCgplcnJvcl9jb2RlGAQgASgJSAGIAQFCDgoMX3N0YWNrX3RyYWNlQg0KC19lcnJvcl9jb2RlIiEKE0V4ZWNDbGllbnRIZWFydGJlYXQSCgoCaWQYASABKA0ivgEKGEV4ZWNDbGllbnRDb250cm9sTWVzc2FnZRI3CgxzdHJlYW1fY2xvc2UYASABKAsyHy5hZ2VudC52MS5FeGVjQ2xpZW50U3RyZWFtQ2xvc2VIABIqCgV0aHJvdxgCIAEoCzIZLmFnZW50LnYxLkV4ZWNDbGllbnRUaHJvd0gAEjIKCWhlYXJ0YmVhdBgDIAEoCzIdLmFnZW50LnYxLkV4ZWNDbGllbnRIZWFydGJlYXRIAEIJCgdtZXNzYWdlIoQBCgtTcGFuQ29udGV4dBIQCgh0cmFjZV9pZBgBIAEoCRIPCgdzcGFuX2lkGAIgASgJEhgKC3RyYWNlX2ZsYWdzGAMgASgNSACIAQESGAoLdHJhY2Vfc3RhdGUYBCABKAlIAYgBAUIOCgxfdHJhY2VfZmxhZ3NCDgoMX3RyYWNlX3N0YXRlIgsKCUFib3J0QXJncyINCgtBYm9ydFJlc3VsdCLaEwoRRXhlY1NlcnZlck1lc3NhZ2USCgoCaWQYASABKA0SDwoHZXhlY19pZBgPIAEoCRIwCgxzcGFuX2NvbnRleHQYEyABKAsyFS5hZ2VudC52MS5TcGFuQ29udGV4dEgBiAEBEikKCnNoZWxsX2FyZ3MYAiABKAsyEy5hZ2VudC52MS5TaGVsbEFyZ3NIABIpCgp3cml0ZV9hcmdzGAMgASgLMhMuYWdlbnQudjEuV3JpdGVBcmdzSAASKwoLZGVsZXRlX2FyZ3MYBCABKAsyFC5hZ2VudC52MS5EZWxldGVBcmdzSAASJwoJZ3JlcF9hcmdzGAUgASgLMhIuYWdlbnQudjEuR3JlcEFyZ3NIABInCglyZWFkX2FyZ3MYByABKAsyEi5hZ2VudC52MS5SZWFkQXJnc0gAEiMKB2xzX2FyZ3MYCCABKAsyEC5hZ2VudC52MS5Mc0FyZ3NIABI1ChBkaWFnbm9zdGljc19hcmdzGAkgASgLMhkuYWdlbnQudjEuRGlhZ25vc3RpY3NBcmdzSAASPAoUcmVxdWVzdF9jb250ZXh0X2FyZ3MYCiABKAsyHC5hZ2VudC52MS5SZXF1ZXN0Q29udGV4dEFyZ3NIABIlCghtY3BfYXJncxgLIAEoCzIRLmFnZW50LnYxLk1jcEFyZ3NIABIwChFzaGVsbF9zdHJlYW1fYXJncxgOIAEoCzITLmFnZW50LnYxLlNoZWxsQXJnc0gAEkkKG2JhY2tncm91bmRfc2hlbGxfc3Bhd25fYXJncxgQIAEoCzIiLmFnZW50LnYxLkJhY2tncm91bmRTaGVsbFNwYXduQXJnc0gAEkoKHGxpc3RfbWNwX3Jlc291cmNlc19leGVjX2FyZ3MYESABKAsyIi5hZ2VudC52MS5MaXN0TWNwUmVzb3VyY2VzRXhlY0FyZ3NIABJIChtyZWFkX21jcF9yZXNvdXJjZV9leGVjX2FyZ3MYEiABKAsyIS5hZ2VudC52MS5SZWFkTWNwUmVzb3VyY2VFeGVjQXJnc0gAEikKCmZldGNoX2FyZ3MYFCABKAsyEy5hZ2VudC52MS5GZXRjaEFyZ3NIABI4ChJyZWNvcmRfc2NyZWVuX2FyZ3MYFSABKAsyGi5hZ2VudC52MS5SZWNvcmRTY3JlZW5BcmdzSAASNgoRY29tcHV0ZXJfdXNlX2FyZ3MYFiABKAsyGS5hZ2VudC52MS5Db21wdXRlclVzZUFyZ3NIABI/ChZ3cml0ZV9zaGVsbF9zdGRpbl9hcmdzGBcgASgLMh0uYWdlbnQudjEuV3JpdGVTaGVsbFN0ZGluQXJnc0gAEjAKEnJlZGFjdGVkX3JlYWRfYXJncxgdIAEoCzISLmFnZW50LnYxLlJlYWRBcmdzSAASOQoTbWNwX3N0YXRlX2V4ZWNfYXJncxgkIAEoCzIaLmFnZW50LnYxLk1jcFN0YXRlRXhlY0FyZ3NIABI2ChFleGVjdXRlX2hvb2tfYXJncxgbIAEoCzIZLmFnZW50LnYxLkV4ZWN1dGVIb29rQXJnc0gAEi8KDXN1YmFnZW50X2FyZ3MYHCABKAsyFi5hZ2VudC52MS5TdWJhZ2VudEFyZ3NIABJJChtmb3JjZV9iYWNrZ3JvdW5kX3NoZWxsX2FyZ3MYHiABKAsyIi5hZ2VudC52MS5Gb3JjZUJhY2tncm91bmRTaGVsbEFyZ3NIABJPCh5mb3JjZV9iYWNrZ3JvdW5kX3N1YmFnZW50X2FyZ3MYHyABKAsyJS5hZ2VudC52MS5Gb3JjZUJhY2tncm91bmRTdWJhZ2VudEFyZ3NIABI6ChNzdWJhZ2VudF9hd2FpdF9hcmdzGCUgASgLMhsuYWdlbnQudjEuU3ViYWdlbnRBd2FpdEFyZ3NIABJHChpzbWFydF9tb2RlX2NsYXNzaWZpZXJfYXJncxgmIAEoCzIhLmFnZW50LnYxLlNtYXJ0TW9kZUNsYXNzaWZpZXJBcmdzSAASQgoXY2FudmFzX2RpYWdub3N0aWNzX2FyZ3MYKCABKAsyHy5hZ2VudC52MS5DYW52YXNEaWFnbm9zdGljc0FyZ3NIABJNCh1zaGVsbF9hbGxvd2xpc3RfcHJlY2hlY2tfYXJncxgpIAEoCzIkLmFnZW50LnYxLlNoZWxsQWxsb3dsaXN0UHJlY2hlY2tBcmdzSAASSQobbWNwX2FsbG93bGlzdF9wcmVjaGVja19hcmdzGCogASgLMiIuYWdlbnQudjEuTWNwQWxsb3dsaXN0UHJlY2hlY2tBcmdzSAASVAohd2ViX2ZldGNoX2FsbG93bGlzdF9wcmVjaGVja19hcmdzGCsgASgLMicuYWdlbnQudjEuV2ViRmV0Y2hBbGxvd2xpc3RQcmVjaGVja0FyZ3NIABI0ChBnaXRfZGlmZl9yZXF1ZXN0GCwgASgLMhguYWdlbnQudjEuR2V0RGlmZlJlcXVlc3RIABIwCgxwaV9yZWFkX2FyZ3MYLSABKAsyGC5hZ2VudC52MS5QaVJlYWRFeGVjQXJnc0gAEjAKDHBpX2Jhc2hfYXJncxguIAEoCzIYLmFnZW50LnYxLlBpQmFzaEV4ZWNBcmdzSAASMAoMcGlfZWRpdF9hcmdzGC8gASgLMhguYWdlbnQudjEuUGlFZGl0RXhlY0FyZ3NIABIyCg1waV93cml0ZV9hcmdzGDAgASgLMhkuYWdlbnQudjEuUGlXcml0ZUV4ZWNBcmdzSAASMAoMcGlfZ3JlcF9hcmdzGDEgASgLMhguYWdlbnQudjEuUGlHcmVwRXhlY0FyZ3NIABIwCgxwaV9maW5kX2FyZ3MYMiABKAsyGC5hZ2VudC52MS5QaUZpbmRFeGVjQXJnc0gAEiwKCnBpX2xzX2FyZ3MYMyABKAsyFi5hZ2VudC52MS5QaUxzRXhlY0FyZ3NIABI3ChhtaW5pX3N3ZV9hZ2VudF9iYXNoX2FyZ3MYNCABKAsyEy5hZ2VudC52MS5TaGVsbEFyZ3NIABJEChhjb252ZXJzYXRpb25fc2VhcmNoX2FyZ3MYNSABKAsyIC5hZ2VudC52MS5Db252ZXJzYXRpb25TZWFyY2hBcmdzSAASRQoZYWdlbnRfc3RvcmVfY29uZmxpY3RfYXJncxg2IAEoCzIgLmFnZW50LnYxLkFnZW50U3RvcmVDb25mbGljdEFyZ3NIABIsCh9hY2NlcHRfaG9va19hZGRpdGlvbmFsX2NvbnRleHRzGDcgASgISAKIAQFCCQoHbWVzc2FnZUIPCg1fc3Bhbl9jb250ZXh0QiIKIF9hY2NlcHRfaG9va19hZGRpdGlvbmFsX2NvbnRleHRzIuEUChFFeGVjQ2xpZW50TWVzc2FnZRIKCgJpZBgBIAEoDRIPCgdleGVjX2lkGA8gASgJEi0KDHNoZWxsX3Jlc3VsdBgCIAEoCzIVLmFnZW50LnYxLlNoZWxsUmVzdWx0SAASLQoMd3JpdGVfcmVzdWx0GAMgASgLMhUuYWdlbnQudjEuV3JpdGVSZXN1bHRIABIvCg1kZWxldGVfcmVzdWx0GAQgASgLMhYuYWdlbnQudjEuRGVsZXRlUmVzdWx0SAASKwoLZ3JlcF9yZXN1bHQYBSABKAsyFC5hZ2VudC52MS5HcmVwUmVzdWx0SAASKwoLcmVhZF9yZXN1bHQYByABKAsyFC5hZ2VudC52MS5SZWFkUmVzdWx0SAASJwoJbHNfcmVzdWx0GAggASgLMhIuYWdlbnQudjEuTHNSZXN1bHRIABI5ChJkaWFnbm9zdGljc19yZXN1bHQYCSABKAsyGy5hZ2VudC52MS5EaWFnbm9zdGljc1Jlc3VsdEgAEkAKFnJlcXVlc3RfY29udGV4dF9yZXN1bHQYCiABKAsyHi5hZ2VudC52MS5SZXF1ZXN0Q29udGV4dFJlc3VsdEgAEikKCm1jcF9yZXN1bHQYCyABKAsyEy5hZ2VudC52MS5NY3BSZXN1bHRIABItCgxzaGVsbF9zdHJlYW0YDiABKAsyFS5hZ2VudC52MS5TaGVsbFN0cmVhbUgAEk0KHWJhY2tncm91bmRfc2hlbGxfc3Bhd25fcmVzdWx0GBAgASgLMiQuYWdlbnQudjEuQmFja2dyb3VuZFNoZWxsU3Bhd25SZXN1bHRIABJOCh5saXN0X21jcF9yZXNvdXJjZXNfZXhlY19yZXN1bHQYESABKAsyJC5hZ2VudC52MS5MaXN0TWNwUmVzb3VyY2VzRXhlY1Jlc3VsdEgAEkwKHXJlYWRfbWNwX3Jlc291cmNlX2V4ZWNfcmVzdWx0GBIgASgLMiMuYWdlbnQudjEuUmVhZE1jcFJlc291cmNlRXhlY1Jlc3VsdEgAEi0KDGZldGNoX3Jlc3VsdBgUIAEoCzIVLmFnZW50LnYxLkZldGNoUmVzdWx0SAASPAoUcmVjb3JkX3NjcmVlbl9yZXN1bHQYFSABKAsyHC5hZ2VudC52MS5SZWNvcmRTY3JlZW5SZXN1bHRIABI6ChNjb21wdXRlcl91c2VfcmVzdWx0GBYgASgLMhsuYWdlbnQudjEuQ29tcHV0ZXJVc2VSZXN1bHRIABJDChh3cml0ZV9zaGVsbF9zdGRpbl9yZXN1bHQYFyABKAsyHy5hZ2VudC52MS5Xcml0ZVNoZWxsU3RkaW5SZXN1bHRIABI0ChRyZWRhY3RlZF9yZWFkX3Jlc3VsdBgdIAEoCzIULmFnZW50LnYxLlJlYWRSZXN1bHRIABI9ChVtY3Bfc3RhdGVfZXhlY19yZXN1bHQYJCABKAsyHC5hZ2VudC52MS5NY3BTdGF0ZUV4ZWNSZXN1bHRIABI6ChNleGVjdXRlX2hvb2tfcmVzdWx0GBsgASgLMhsuYWdlbnQudjEuRXhlY3V0ZUhvb2tSZXN1bHRIABIzCg9zdWJhZ2VudF9yZXN1bHQYHCABKAsyGC5hZ2VudC52MS5TdWJhZ2VudFJlc3VsdEgAEk0KHWZvcmNlX2JhY2tncm91bmRfc2hlbGxfcmVzdWx0GB4gASgLMiQuYWdlbnQudjEuRm9yY2VCYWNrZ3JvdW5kU2hlbGxSZXN1bHRIABJTCiBmb3JjZV9iYWNrZ3JvdW5kX3N1YmFnZW50X3Jlc3VsdBgfIAEoCzInLmFnZW50LnYxLkZvcmNlQmFja2dyb3VuZFN1YmFnZW50UmVzdWx0SAASPgoVc3ViYWdlbnRfYXdhaXRfcmVzdWx0GCUgASgLMh0uYWdlbnQudjEuU3ViYWdlbnRBd2FpdFJlc3VsdEgAEksKHHNtYXJ0X21vZGVfY2xhc3NpZmllcl9yZXN1bHQYJiABKAsyIy5hZ2VudC52MS5TbWFydE1vZGVDbGFzc2lmaWVyUmVzdWx0SAASRgoZY2FudmFzX2RpYWdub3N0aWNzX3Jlc3VsdBgoIAEoCzIhLmFnZW50LnYxLkNhbnZhc0RpYWdub3N0aWNzUmVzdWx0SAASUQofc2hlbGxfYWxsb3dsaXN0X3ByZWNoZWNrX3Jlc3VsdBgpIAEoCzImLmFnZW50LnYxLlNoZWxsQWxsb3dsaXN0UHJlY2hlY2tSZXN1bHRIABJNCh1tY3BfYWxsb3dsaXN0X3ByZWNoZWNrX3Jlc3VsdBgqIAEoCzIkLmFnZW50LnYxLk1jcEFsbG93bGlzdFByZWNoZWNrUmVzdWx0SAASWAojd2ViX2ZldGNoX2FsbG93bGlzdF9wcmVjaGVja19yZXN1bHQYKyABKAsyKS5hZ2VudC52MS5XZWJGZXRjaEFsbG93bGlzdFByZWNoZWNrUmVzdWx0SAASNgoRZ2l0X2RpZmZfcmVzcG9uc2UYLCABKAsyGS5hZ2VudC52MS5HZXREaWZmUmVzcG9uc2VIABI0Cg5waV9yZWFkX3Jlc3VsdBguIAEoCzIaLmFnZW50LnYxLlBpUmVhZEV4ZWNSZXN1bHRIABI0Cg5waV9iYXNoX3Jlc3VsdBgvIAEoCzIaLmFnZW50LnYxLlBpQmFzaEV4ZWNSZXN1bHRIABI0Cg5waV9lZGl0X3Jlc3VsdBgwIAEoCzIaLmFnZW50LnYxLlBpRWRpdEV4ZWNSZXN1bHRIABI2Cg9waV93cml0ZV9yZXN1bHQYMSABKAsyGy5hZ2VudC52MS5QaVdyaXRlRXhlY1Jlc3VsdEgAEjQKDnBpX2dyZXBfcmVzdWx0GDIgASgLMhouYWdlbnQudjEuUGlHcmVwRXhlY1Jlc3VsdEgAEjQKDnBpX2ZpbmRfcmVzdWx0GDMgASgLMhouYWdlbnQudjEuUGlGaW5kRXhlY1Jlc3VsdEgAEjAKDHBpX2xzX3Jlc3VsdBg0IAEoCzIYLmFnZW50LnYxLlBpTHNFeGVjUmVzdWx0SAASSAoaY29udmVyc2F0aW9uX3NlYXJjaF9yZXN1bHQYNSABKAsyIi5hZ2VudC52MS5Db252ZXJzYXRpb25TZWFyY2hSZXN1bHRIABJJChthZ2VudF9zdG9yZV9jb25mbGljdF9yZXN1bHQYNiABKAsyIi5hZ2VudC52MS5BZ2VudFN0b3JlQ29uZmxpY3RSZXN1bHRIABI7ChptaW5pX3N3ZV9hZ2VudF9iYXNoX3Jlc3VsdBg3IAEoCzIVLmFnZW50LnYxLlNoZWxsUmVzdWx0SAASJAoXbG9jYWxfZXhlY3V0aW9uX3RpbWVfbXMYJyABKAVIAYgBARJBChhob29rX2FkZGl0aW9uYWxfY29udGV4dHMYLSADKAsyHy5hZ2VudC52MS5Ib29rQWRkaXRpb25hbENvbnRleHRCCQoHbWVzc2FnZUIaChhfbG9jYWxfZXhlY3V0aW9uX3RpbWVfbXMiLgoJRmV0Y2hBcmdzEgsKA3VybBgBIAEoCRIUCgx0b29sX2NhbGxfaWQYAiABKAkiaQoLRmV0Y2hSZXN1bHQSKQoHc3VjY2VzcxgBIAEoCzIWLmFnZW50LnYxLkZldGNoU3VjY2Vzc0gAEiUKBWVycm9yGAIgASgLMhQuYWdlbnQudjEuRmV0Y2hFcnJvckgAQggKBnJlc3VsdCJXCgxGZXRjaFN1Y2Nlc3MSCwoDdXJsGAEgASgJEg8KB2NvbnRlbnQYAiABKAkSEwoLc3RhdHVzX2NvZGUYAyABKAUSFAoMY29udGVudF90eXBlGAQgASgJIigKCkZldGNoRXJyb3ISCwoDdXJsGAEgASgJEg0KBWVycm9yGAIgASgJIm0KEUdlbmVyYXRlSW1hZ2VBcmdzEhMKC2Rlc2NyaXB0aW9uGAEgASgJEhYKCWZpbGVfcGF0aBgCIAEoCUgAiAEBEh0KFXJlZmVyZW5jZV9pbWFnZV9wYXRocxgFIAMoCUIMCgpfZmlsZV9wYXRoIoEBChNHZW5lcmF0ZUltYWdlUmVzdWx0EjEKB3N1Y2Nlc3MYASABKAsyHi5hZ2VudC52MS5HZW5lcmF0ZUltYWdlU3VjY2Vzc0gAEi0KBWVycm9yGAIgASgLMhwuYWdlbnQudjEuR2VuZXJhdGVJbWFnZUVycm9ySABCCAoGcmVzdWx0Ij0KFEdlbmVyYXRlSW1hZ2VTdWNjZXNzEhEKCWZpbGVfcGF0aBgBIAEoCRISCgppbWFnZV9kYXRhGAIgASgJIiMKEkdlbmVyYXRlSW1hZ2VFcnJvchINCgVlcnJvchgBIAEoCSJxChVHZW5lcmF0ZUltYWdlVG9vbENhbGwSKQoEYXJncxgBIAEoCzIbLmFnZW50LnYxLkdlbmVyYXRlSW1hZ2VBcmdzEi0KBnJlc3VsdBgCIAEoCzIdLmFnZW50LnYxLkdlbmVyYXRlSW1hZ2VSZXN1bHQi5gQKCEdyZXBBcmdzEg8KB3BhdHRlcm4YASABKAkSEQoEcGF0aBgCIAEoCUgAiAEBEhEKBGdsb2IYAyABKAlIAYgBARIYCgtvdXRwdXRfbW9kZRgEIAEoCUgCiAEBEhsKDmNvbnRleHRfYmVmb3JlGAUgASgFSAOIAQESGgoNY29udGV4dF9hZnRlchgGIAEoBUgEiAEBEhQKB2NvbnRleHQYByABKAVIBYgBARIdChBjYXNlX2luc2Vuc2l0aXZlGAggASgISAaIAQESEQoEdHlwZRgJIAEoCUgHiAEBEhcKCmhlYWRfbGltaXQYCiABKAVICIgBARIWCgltdWx0aWxpbmUYCyABKAhICYgBARIRCgRzb3J0GAwgASgJSAqIAQESGwoOc29ydF9hc2NlbmRpbmcYDSABKAhIC4gBARIUCgx0b29sX2NhbGxfaWQYDiABKAkSNAoOc2FuZGJveF9wb2xpY3kYDyABKAsyFy5hZ2VudC52MS5TYW5kYm94UG9saWN5SAyIAQESEwoGb2Zmc2V0GBAgASgFSA2IAQFCBwoFX3BhdGhCBwoFX2dsb2JCDgoMX291dHB1dF9tb2RlQhEKD19jb250ZXh0X2JlZm9yZUIQCg5fY29udGV4dF9hZnRlckIKCghfY29udGV4dEITChFfY2FzZV9pbnNlbnNpdGl2ZUIHCgVfdHlwZUINCgtfaGVhZF9saW1pdEIMCgpfbXVsdGlsaW5lQgcKBV9zb3J0QhEKD19zb3J0X2FzY2VuZGluZ0IRCg9fc2FuZGJveF9wb2xpY3lCCQoHX29mZnNldCJmCgpHcmVwUmVzdWx0EigKB3N1Y2Nlc3MYASABKAsyFS5hZ2VudC52MS5HcmVwU3VjY2Vzc0gAEiQKBWVycm9yGAIgASgLMhMuYWdlbnQudjEuR3JlcEVycm9ySABCCAoGcmVzdWx0IhoKCUdyZXBFcnJvchINCgVlcnJvchgBIAEoCSK0AgoLR3JlcFN1Y2Nlc3MSDwoHcGF0dGVybhgBIAEoCRIMCgRwYXRoGAIgASgJEhMKC291dHB1dF9tb2RlGAMgASgJEkYKEXdvcmtzcGFjZV9yZXN1bHRzGAQgAygLMisuYWdlbnQudjEuR3JlcFN1Y2Nlc3MuV29ya3NwYWNlUmVzdWx0c0VudHJ5EjwKFGFjdGl2ZV9lZGl0b3JfcmVzdWx0GAUgASgLMhkuYWdlbnQudjEuR3JlcFVuaW9uUmVzdWx0SACIAQEaUgoVV29ya3NwYWNlUmVzdWx0c0VudHJ5EgsKA2tleRgBIAEoCRIoCgV2YWx1ZRgCIAEoCzIZLmFnZW50LnYxLkdyZXBVbmlvblJlc3VsdDoCOAFCFwoVX2FjdGl2ZV9lZGl0b3JfcmVzdWx0IqMBCg9HcmVwVW5pb25SZXN1bHQSKgoFY291bnQYASABKAsyGS5hZ2VudC52MS5HcmVwQ291bnRSZXN1bHRIABIqCgVmaWxlcxgCIAEoCzIZLmFnZW50LnYxLkdyZXBGaWxlc1Jlc3VsdEgAEi4KB2NvbnRlbnQYAyABKAsyGy5hZ2VudC52MS5HcmVwQ29udGVudFJlc3VsdEgAQggKBnJlc3VsdCKDAgoPR3JlcENvdW50UmVzdWx0EicKBmNvdW50cxgBIAMoCzIXLmFnZW50LnYxLkdyZXBGaWxlQ291bnQSEwoLdG90YWxfZmlsZXMYAiABKAUSFQoNdG90YWxfbWF0Y2hlcxgDIAEoBRIYChBjbGllbnRfdHJ1bmNhdGVkGAQgASgIEhkKEXJpcGdyZXBfdHJ1bmNhdGVkGAUgASgIEh8KEmhlYWRfbGltaXRfYXBwbGllZBgGIAEoBUgAiAEBEhsKDm9mZnNldF9hcHBsaWVkGAcgASgFSAGIAQFCFQoTX2hlYWRfbGltaXRfYXBwbGllZEIRCg9fb2Zmc2V0X2FwcGxpZWQiLAoNR3JlcEZpbGVDb3VudBIMCgRmaWxlGAEgASgJEg0KBWNvdW50GAIgASgFItIBCg9HcmVwRmlsZXNSZXN1bHQSDQoFZmlsZXMYASADKAkSEwoLdG90YWxfZmlsZXMYAiABKAUSGAoQY2xpZW50X3RydW5jYXRlZBgDIAEoCBIZChFyaXBncmVwX3RydW5jYXRlZBgEIAEoCBIfChJoZWFkX2xpbWl0X2FwcGxpZWQYBSABKAVIAIgBARIbCg5vZmZzZXRfYXBwbGllZBgGIAEoBUgBiAEBQhUKE19oZWFkX2xpbWl0X2FwcGxpZWRCEQoPX29mZnNldF9hcHBsaWVkIowCChFHcmVwQ29udGVudFJlc3VsdBIoCgdtYXRjaGVzGAEgAygLMhcuYWdlbnQudjEuR3JlcEZpbGVNYXRjaBITCgt0b3RhbF9saW5lcxgCIAEoBRIbChN0b3RhbF9tYXRjaGVkX2xpbmVzGAMgASgFEhgKEGNsaWVudF90cnVuY2F0ZWQYBCABKAgSGQoRcmlwZ3JlcF90cnVuY2F0ZWQYBSABKAgSHwoSaGVhZF9saW1pdF9hcHBsaWVkGAYgASgFSACIAQESGwoOb2Zmc2V0X2FwcGxpZWQYByABKAVIAYgBAUIVChNfaGVhZF9saW1pdF9hcHBsaWVkQhEKD19vZmZzZXRfYXBwbGllZCJKCg1HcmVwRmlsZU1hdGNoEgwKBGZpbGUYASABKAkSKwoHbWF0Y2hlcxgCIAMoCzIaLmFnZW50LnYxLkdyZXBDb250ZW50TWF0Y2gibAoQR3JlcENvbnRlbnRNYXRjaBITCgtsaW5lX251bWJlchgBIAEoBRIPCgdjb250ZW50GAIgASgJEhkKEWNvbnRlbnRfdHJ1bmNhdGVkGAMgASgIEhcKD2lzX2NvbnRleHRfbGluZRgEIAEoCCIdCgpHcmVwU3RyZWFtEg8KB3BhdHRlcm4YASABKAkiVgoMR3JlcFRvb2xDYWxsEiAKBGFyZ3MYASABKAsyEi5hZ2VudC52MS5HcmVwQXJncxIkCgZyZXN1bHQYAiABKAsyFC5hZ2VudC52MS5HcmVwUmVzdWx0Ih4KC0dldEJsb2JBcmdzEg8KB2Jsb2JfaWQYASABKAwiNQoNR2V0QmxvYlJlc3VsdBIWCglibG9iX2RhdGEYASABKAxIAIgBAUIMCgpfYmxvYl9kYXRhIjEKC1NldEJsb2JBcmdzEg8KB2Jsb2JfaWQYASABKAwSEQoJYmxvYl9kYXRhGAIgASgMIj4KDVNldEJsb2JSZXN1bHQSIwoFZXJyb3IYASABKAsyDy5hZ2VudC52MS5FcnJvckgAiAEBQggKBl9lcnJvciLLAQoPS3ZTZXJ2ZXJNZXNzYWdlEgoKAmlkGAEgASgNEjAKDHNwYW5fY29udGV4dBgEIAEoCzIVLmFnZW50LnYxLlNwYW5Db250ZXh0SAGIAQESLgoNZ2V0X2Jsb2JfYXJncxgCIAEoCzIVLmFnZW50LnYxLkdldEJsb2JBcmdzSAASLgoNc2V0X2Jsb2JfYXJncxgDIAEoCzIVLmFnZW50LnYxLlNldEJsb2JBcmdzSABCCQoHbWVzc2FnZUIPCg1fc3Bhbl9jb250ZXh0IpABCg9LdkNsaWVudE1lc3NhZ2USCgoCaWQYASABKA0SMgoPZ2V0X2Jsb2JfcmVzdWx0GAIgASgLMhcuYWdlbnQudjEuR2V0QmxvYlJlc3VsdEgAEjIKD3NldF9ibG9iX3Jlc3VsdBgDIAEoCzIXLmFnZW50LnYxLlNldEJsb2JSZXN1bHRIAEIJCgdtZXNzYWdlIq0BCgZMc0FyZ3MSDAoEcGF0aBgBIAEoCRIOCgZpZ25vcmUYAiADKAkSFAoMdG9vbF9jYWxsX2lkGAMgASgJEjQKDnNhbmRib3hfcG9saWN5GAQgASgLMhcuYWdlbnQudjEuU2FuZGJveFBvbGljeUgAiAEBEhcKCnRpbWVvdXRfbXMYBSABKA1IAYgBAUIRCg9fc2FuZGJveF9wb2xpY3lCDQoLX3RpbWVvdXRfbXMisgEKCExzUmVzdWx0EiYKB3N1Y2Nlc3MYASABKAsyEy5hZ2VudC52MS5Mc1N1Y2Nlc3NIABIiCgVlcnJvchgCIAEoCzIRLmFnZW50LnYxLkxzRXJyb3JIABIoCghyZWplY3RlZBgDIAEoCzIULmFnZW50LnYxLkxzUmVqZWN0ZWRIABImCgd0aW1lb3V0GAQgASgLMhMuYWdlbnQudjEuTHNUaW1lb3V0SABCCAoGcmVzdWx0IkcKCUxzU3VjY2VzcxI6ChNkaXJlY3RvcnlfdHJlZV9yb290GAEgASgLMh0uYWdlbnQudjEuTHNEaXJlY3RvcnlUcmVlTm9kZSL2AgoTTHNEaXJlY3RvcnlUcmVlTm9kZRIQCghhYnNfcGF0aBgBIAEoCRI0Cg1jaGlsZHJlbl9kaXJzGAIgAygLMh0uYWdlbnQudjEuTHNEaXJlY3RvcnlUcmVlTm9kZRI6Cg5jaGlsZHJlbl9maWxlcxgDIAMoCzIiLmFnZW50LnYxLkxzRGlyZWN0b3J5VHJlZU5vZGVfRmlsZRIfChdjaGlsZHJlbl93ZXJlX3Byb2Nlc3NlZBgEIAEoCBJkCh1mdWxsX3N1YnRyZWVfZXh0ZW5zaW9uX2NvdW50cxgFIAMoCzI9LmFnZW50LnYxLkxzRGlyZWN0b3J5VHJlZU5vZGUuRnVsbFN1YnRyZWVFeHRlbnNpb25Db3VudHNFbnRyeRIRCgludW1fZmlsZXMYBiABKAUaQQofRnVsbFN1YnRyZWVFeHRlbnNpb25Db3VudHNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAU6AjgBInoKGExzRGlyZWN0b3J5VHJlZU5vZGVfRmlsZRIMCgRuYW1lGAEgASgJEjoKEXRlcm1pbmFsX21ldGFkYXRhGAIgASgLMhouYWdlbnQudjEuVGVybWluYWxNZXRhZGF0YUgAiAEBQhQKEl90ZXJtaW5hbF9tZXRhZGF0YSImCgdMc0Vycm9yEgwKBHBhdGgYASABKAkSDQoFZXJyb3IYAiABKAkiKgoKTHNSZWplY3RlZBIMCgRwYXRoGAEgASgJEg4KBnJlYXNvbhgCIAEoCSJHCglMc1RpbWVvdXQSOgoTZGlyZWN0b3J5X3RyZWVfcm9vdBgBIAEoCzIdLmFnZW50LnYxLkxzRGlyZWN0b3J5VHJlZU5vZGUi8QEKEFRlcm1pbmFsTWV0YWRhdGESEAoDY3dkGAEgASgJSACIAQESOQoNbGFzdF9jb21tYW5kcxgCIAMoCzIiLmFnZW50LnYxLlRlcm1pbmFsTWV0YWRhdGFfQ29tbWFuZBIdChBsYXN0X21vZGlmaWVkX21zGAMgASgDSAGIAQESQAoPY3VycmVudF9jb21tYW5kGAQgASgLMiIuYWdlbnQudjEuVGVybWluYWxNZXRhZGF0YV9Db21tYW5kSAKIAQFCBgoEX2N3ZEITChFfbGFzdF9tb2RpZmllZF9tc0ISChBfY3VycmVudF9jb21tYW5kIqcBChhUZXJtaW5hbE1ldGFkYXRhX0NvbW1hbmQSDwoHY29tbWFuZBgBIAEoCRIWCglleGl0X2NvZGUYAiABKAVIAIgBARIZCgx0aW1lc3RhbXBfbXMYAyABKANIAYgBARIYCgtkdXJhdGlvbl9tcxgEIAEoA0gCiAEBQgwKCl9leGl0X2NvZGVCDwoNX3RpbWVzdGFtcF9tc0IOCgxfZHVyYXRpb25fbXMiUAoKTHNUb29sQ2FsbBIeCgRhcmdzGAEgASgLMhAuYWdlbnQudjEuTHNBcmdzEiIKBnJlc3VsdBgCIAEoCzISLmFnZW50LnYxLkxzUmVzdWx0IuACCgdNY3BBcmdzEgwKBG5hbWUYASABKAkSKQoEYXJncxgCIAMoCzIbLmFnZW50LnYxLk1jcEFyZ3MuQXJnc0VudHJ5EhQKDHRvb2xfY2FsbF9pZBgDIAEoCRIbChNwcm92aWRlcl9pZGVudGlmaWVyGAQgASgJEhEKCXRvb2xfbmFtZRgFIAEoCRI9ChNzbWFydF9tb2RlX2FwcHJvdmFsGAYgASgLMhsuYWdlbnQudjEuU21hcnRNb2RlQXBwcm92YWxIAIgBARIgChhzbWFydF9tb2RlX2FwcHJvdmFsX29ubHkYByABKAgSFQoNc2tpcF9hcHByb3ZhbBgIIAEoCBIZChFzZXJ2ZXJfaWRlbnRpZmllchgJIAEoCRorCglBcmdzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgMOgI4AUIWChRfc21hcnRfbW9kZV9hcHByb3ZhbCLjAgoJTWNwUmVzdWx0EicKB3N1Y2Nlc3MYASABKAsyFC5hZ2VudC52MS5NY3BTdWNjZXNzSAASIwoFZXJyb3IYAiABKAsyEi5hZ2VudC52MS5NY3BFcnJvckgAEikKCHJlamVjdGVkGAMgASgLMhUuYWdlbnQudjEuTWNwUmVqZWN0ZWRIABI6ChFwZXJtaXNzaW9uX2RlbmllZBgEIAEoCzIdLmFnZW50LnYxLk1jcFBlcm1pc3Npb25EZW5pZWRIABIzCg50b29sX25vdF9mb3VuZBgFIAEoCzIZLmFnZW50LnYxLk1jcFRvb2xOb3RGb3VuZEgAEjcKEHNlcnZlcl9ub3RfZm91bmQYBiABKAsyGy5hZ2VudC52MS5NY3BTZXJ2ZXJOb3RGb3VuZEgAEikKCGFwcHJvdmVkGAcgASgLMhUuYWdlbnQudjEuTWNwQXBwcm92ZWRIAEIICgZyZXN1bHQiOAoPTWNwVG9vbE5vdEZvdW5kEgwKBG5hbWUYASABKAkSFwoPYXZhaWxhYmxlX3Rvb2xzGAIgAygJImoKDk1jcFRleHRDb250ZW50EgwKBHRleHQYASABKAkSNgoPb3V0cHV0X2xvY2F0aW9uGAIgASgLMhguYWdlbnQudjEuT3V0cHV0TG9jYXRpb25IAIgBAUISChBfb3V0cHV0X2xvY2F0aW9uIjIKD01jcEltYWdlQ29udGVudBIMCgRkYXRhGAEgASgMEhEKCW1pbWVfdHlwZRgCIAEoCSJ7ChhNY3BUb29sUmVzdWx0Q29udGVudEl0ZW0SKAoEdGV4dBgBIAEoCzIYLmFnZW50LnYxLk1jcFRleHRDb250ZW50SAASKgoFaW1hZ2UYAiABKAsyGS5hZ2VudC52MS5NY3BJbWFnZUNvbnRlbnRIAEIJCgdjb250ZW50IlMKCk1jcFN1Y2Nlc3MSMwoHY29udGVudBgBIAMoCzIiLmFnZW50LnYxLk1jcFRvb2xSZXN1bHRDb250ZW50SXRlbRIQCghpc19lcnJvchgCIAEoCCIZCghNY3BFcnJvchINCgVlcnJvchgBIAEoCSIyCgtNY3BSZWplY3RlZBIOCgZyZWFzb24YASABKAkSEwoLaXNfcmVhZG9ubHkYAiABKAgiOQoTTWNwUGVybWlzc2lvbkRlbmllZBINCgVlcnJvchgBIAEoCRITCgtpc19yZWFkb25seRgCIAEoCCI6ChhMaXN0TWNwUmVzb3VyY2VzRXhlY0FyZ3MSEwoGc2VydmVyGAEgASgJSACIAQFCCQoHX3NlcnZlciLGAQoaTGlzdE1jcFJlc291cmNlc0V4ZWNSZXN1bHQSNAoHc3VjY2VzcxgBIAEoCzIhLmFnZW50LnYxLkxpc3RNY3BSZXNvdXJjZXNTdWNjZXNzSAASMAoFZXJyb3IYAiABKAsyHy5hZ2VudC52MS5MaXN0TWNwUmVzb3VyY2VzRXJyb3JIABI2CghyZWplY3RlZBgDIAEoCzIiLmFnZW50LnYxLkxpc3RNY3BSZXNvdXJjZXNSZWplY3RlZEgAQggKBnJlc3VsdCK9AgomTGlzdE1jcFJlc291cmNlc0V4ZWNSZXN1bHRfTWNwUmVzb3VyY2USCwoDdXJpGAEgASgJEhEKBG5hbWUYAiABKAlIAIgBARIYCgtkZXNjcmlwdGlvbhgDIAEoCUgBiAEBEhYKCW1pbWVfdHlwZRgEIAEoCUgCiAEBEg4KBnNlcnZlchgFIAEoCRJWCgthbm5vdGF0aW9ucxgGIAMoCzJBLmFnZW50LnYxLkxpc3RNY3BSZXNvdXJjZXNFeGVjUmVzdWx0X01jcFJlc291cmNlLkFubm90YXRpb25zRW50cnkaMgoQQW5ub3RhdGlvbnNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBQgcKBV9uYW1lQg4KDF9kZXNjcmlwdGlvbkIMCgpfbWltZV90eXBlIl4KF0xpc3RNY3BSZXNvdXJjZXNTdWNjZXNzEkMKCXJlc291cmNlcxgBIAMoCzIwLmFnZW50LnYxLkxpc3RNY3BSZXNvdXJjZXNFeGVjUmVzdWx0X01jcFJlc291cmNlIiYKFUxpc3RNY3BSZXNvdXJjZXNFcnJvchINCgVlcnJvchgBIAEoCSIqChhMaXN0TWNwUmVzb3VyY2VzUmVqZWN0ZWQSDgoGcmVhc29uGAEgASgJItEBChdSZWFkTWNwUmVzb3VyY2VFeGVjQXJncxIOCgZzZXJ2ZXIYASABKAkSCwoDdXJpGAIgASgJEhoKDWRvd25sb2FkX3BhdGgYAyABKAlIAIgBARIUCgx0b29sX2NhbGxfaWQYBCABKAkSPQoTc21hcnRfbW9kZV9hcHByb3ZhbBgFIAEoCzIbLmFnZW50LnYxLlNtYXJ0TW9kZUFwcHJvdmFsSAGIAQFCEAoOX2Rvd25sb2FkX3BhdGhCFgoUX3NtYXJ0X21vZGVfYXBwcm92YWwi+gEKGVJlYWRNY3BSZXNvdXJjZUV4ZWNSZXN1bHQSMwoHc3VjY2VzcxgBIAEoCzIgLmFnZW50LnYxLlJlYWRNY3BSZXNvdXJjZVN1Y2Nlc3NIABIvCgVlcnJvchgCIAEoCzIeLmFnZW50LnYxLlJlYWRNY3BSZXNvdXJjZUVycm9ySAASNQoIcmVqZWN0ZWQYAyABKAsyIS5hZ2VudC52MS5SZWFkTWNwUmVzb3VyY2VSZWplY3RlZEgAEjYKCW5vdF9mb3VuZBgEIAEoCzIhLmFnZW50LnYxLlJlYWRNY3BSZXNvdXJjZU5vdEZvdW5kSABCCAoGcmVzdWx0IrIDChZSZWFkTWNwUmVzb3VyY2VTdWNjZXNzEgsKA3VyaRgBIAEoCRIRCgRuYW1lGAIgASgJSAGIAQESGAoLZGVzY3JpcHRpb24YAyABKAlIAogBARIWCgltaW1lX3R5cGUYBCABKAlIA4gBARJGCgthbm5vdGF0aW9ucxgHIAMoCzIxLmFnZW50LnYxLlJlYWRNY3BSZXNvdXJjZVN1Y2Nlc3MuQW5ub3RhdGlvbnNFbnRyeRIaCg1kb3dubG9hZF9wYXRoGAggASgJSASIAQESDgoEdGV4dBgFIAEoCUgAEg4KBGJsb2IYBiABKAxIABI2Cg9vdXRwdXRfbG9jYXRpb24YCSABKAsyGC5hZ2VudC52MS5PdXRwdXRMb2NhdGlvbkgFiAEBGjIKEEFubm90YXRpb25zRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4AUIJCgdjb250ZW50QgcKBV9uYW1lQg4KDF9kZXNjcmlwdGlvbkIMCgpfbWltZV90eXBlQhAKDl9kb3dubG9hZF9wYXRoQhIKEF9vdXRwdXRfbG9jYXRpb24iMgoUUmVhZE1jcFJlc291cmNlRXJyb3ISCwoDdXJpGAEgASgJEg0KBWVycm9yGAIgASgJIjYKF1JlYWRNY3BSZXNvdXJjZVJlamVjdGVkEgsKA3VyaRgBIAEoCRIOCgZyZWFzb24YAiABKAkiJgoXUmVhZE1jcFJlc291cmNlTm90Rm91bmQSCwoDdXJpGAEgASgJIrIBChFNY3BUb29sRGVmaW5pdGlvbhIMCgRuYW1lGAEgASgJEhsKE3Byb3ZpZGVyX2lkZW50aWZpZXIYBCABKAkSEQoJdG9vbF9uYW1lGAUgASgJEhMKC2Rlc2NyaXB0aW9uGAIgASgJEhQKDGlucHV0X3NjaGVtYRgDIAEoDBIeChFpbnB1dF9zY2hlbWFfanNvbhgGIAEoCUgAiAEBQhQKEl9pbnB1dF9zY2hlbWFfanNvbiI6CghNY3BUb29scxIuCgltY3BfdG9vbHMYASADKAsyGy5hZ2VudC52MS5NY3BUb29sRGVmaW5pdGlvbiJXCg9NY3BJbnN0cnVjdGlvbnMSEwoLc2VydmVyX25hbWUYASABKAkSFAoMaW5zdHJ1Y3Rpb25zGAIgASgJEhkKEXNlcnZlcl9pZGVudGlmaWVyGAMgASgJIv0CCg1NY3BEZXNjcmlwdG9yEhMKC3NlcnZlcl9uYW1lGAEgASgJEhkKEXNlcnZlcl9pZGVudGlmaWVyGAIgASgJEhgKC2ZvbGRlcl9wYXRoGAMgASgJSACIAQESJAoXc2VydmVyX3VzZV9pbnN0cnVjdGlvbnMYBCABKAlIAYgBARIqCgV0b29scxgFIAMoCzIbLmFnZW50LnYxLk1jcFRvb2xEZXNjcmlwdG9yEhMKBnBsdWdpbhgHIAEoCUgCiAEBEhgKC21hcmtldHBsYWNlGAggASgJSAOIAQESGQoMcGx1Z2luX2RiX2lkGAkgASgJSASIAQESGwoObWFya2V0cGxhY2VfaWQYCiABKAlIBYgBAUIOCgxfZm9sZGVyX3BhdGhCGgoYX3NlcnZlcl91c2VfaW5zdHJ1Y3Rpb25zQgkKB19wbHVnaW5CDgoMX21hcmtldHBsYWNlQg8KDV9wbHVnaW5fZGJfaWRCEQoPX21hcmtldHBsYWNlX2lkIrgBChFNY3BUb29sRGVzY3JpcHRvchIRCgl0b29sX25hbWUYASABKAkSHAoPZGVmaW5pdGlvbl9wYXRoGAIgASgJSACIAQESGAoLZGVzY3JpcHRpb24YAyABKAlIAYgBARIeChFpbnB1dF9zY2hlbWFfanNvbhgFIAEoCUgCiAEBQhIKEF9kZWZpbml0aW9uX3BhdGhCDgoMX2Rlc2NyaXB0aW9uQhQKEl9pbnB1dF9zY2hlbWFfanNvbiJ4ChRNY3BGaWxlU3lzdGVtT3B0aW9ucxIPCgdlbmFibGVkGAEgASgIEh0KFXdvcmtzcGFjZV9wcm9qZWN0X2RpchgCIAEoCRIwCg9tY3BfZGVzY3JpcHRvcnMYAyADKAsyFy5hZ2VudC52MS5NY3BEZXNjcmlwdG9yIpoBCghSZWFkQXJncxIMCgRwYXRoGAEgASgJEhQKDHRvb2xfY2FsbF9pZBgCIAEoCRITCgZvZmZzZXQYBCABKAVIAIgBARISCgVsaW1pdBgFIAEoDUgBiAEBEhoKDWVuY29kaW5nX2hpbnQYBiABKAlIAogBAUIJCgdfb2Zmc2V0QggKBl9saW1pdEIQCg5fZW5jb2RpbmdfaGludCK4AgoKUmVhZFJlc3VsdBIoCgdzdWNjZXNzGAEgASgLMhUuYWdlbnQudjEuUmVhZFN1Y2Nlc3NIABIkCgVlcnJvchgCIAEoCzITLmFnZW50LnYxLlJlYWRFcnJvckgAEioKCHJlamVjdGVkGAMgASgLMhYuYWdlbnQudjEuUmVhZFJlamVjdGVkSAASNAoOZmlsZV9ub3RfZm91bmQYBCABKAsyGi5hZ2VudC52MS5SZWFkRmlsZU5vdEZvdW5kSAASOwoRcGVybWlzc2lvbl9kZW5pZWQYBSABKAsyHi5hZ2VudC52MS5SZWFkUGVybWlzc2lvbkRlbmllZEgAEjEKDGludmFsaWRfZmlsZRgGIAEoCzIZLmFnZW50LnYxLlJlYWRJbnZhbGlkRmlsZUgAQggKBnJlc3VsdCLKAQoLUmVhZFN1Y2Nlc3MSDAoEcGF0aBgBIAEoCRITCgt0b3RhbF9saW5lcxgDIAEoBRIRCglmaWxlX3NpemUYBCABKAMSEQoJdHJ1bmNhdGVkGAYgASgIEhsKDm91dHB1dF9ibG9iX2lkGAcgASgMSAGIAQESEQoHY29udGVudBgCIAEoCUgAEg4KBGRhdGEYBSABKAxIABIVCg1yYW5nZV9hcHBsaWVkGAggASgIQggKBm91dHB1dEIRCg9fb3V0cHV0X2Jsb2JfaWQiKAoJUmVhZEVycm9yEgwKBHBhdGgYASABKAkSDQoFZXJyb3IYAiABKAkiLAoMUmVhZFJlamVjdGVkEgwKBHBhdGgYASABKAkSDgoGcmVhc29uGAIgASgJIiAKEFJlYWRGaWxlTm90Rm91bmQSDAoEcGF0aBgBIAEoCSIkChRSZWFkUGVybWlzc2lvbkRlbmllZBIMCgRwYXRoGAEgASgJIi8KD1JlYWRJbnZhbGlkRmlsZRIMCgRwYXRoGAEgASgJEg4KBnJlYXNvbhgCIAEoCSJeCgxSZWFkVG9vbENhbGwSJAoEYXJncxgBIAEoCzIWLmFnZW50LnYxLlJlYWRUb29sQXJncxIoCgZyZXN1bHQYAiABKAsyGC5hZ2VudC52MS5SZWFkVG9vbFJlc3VsdCJaCgxSZWFkVG9vbEFyZ3MSDAoEcGF0aBgBIAEoCRITCgZvZmZzZXQYAiABKAVIAIgBARISCgVsaW1pdBgDIAEoBUgBiAEBQgkKB19vZmZzZXRCCAoGX2xpbWl0InIKDlJlYWRUb29sUmVzdWx0EiwKB3N1Y2Nlc3MYASABKAsyGS5hZ2VudC52MS5SZWFkVG9vbFN1Y2Nlc3NIABIoCgVlcnJvchgCIAEoCzIXLmFnZW50LnYxLlJlYWRUb29sRXJyb3JIAEIICgZyZXN1bHQiMQoJUmVhZFJhbmdlEhIKCnN0YXJ0X2xpbmUYASABKA0SEAoIZW5kX2xpbmUYAiABKA0ijgIKD1JlYWRUb29sU3VjY2VzcxIQCghpc19lbXB0eRgCIAEoCBIWCg5leGNlZWRlZF9saW1pdBgDIAEoCBITCgt0b3RhbF9saW5lcxgEIAEoDRIRCglmaWxlX3NpemUYBSABKA0SDAoEcGF0aBgHIAEoCRIsCgpyZWFkX3JhbmdlGAggASgLMhMuYWdlbnQudjEuUmVhZFJhbmdlSAGIAQESEQoHY29udGVudBgBIAEoCUgAEg4KBGRhdGEYBiABKAxIABIWCgxkYXRhX2Jsb2JfaWQYCSABKAxIABIZCg9jb250ZW50X2Jsb2JfaWQYCiABKAxIAEIICgZvdXRwdXRCDQoLX3JlYWRfcmFuZ2UiJgoNUmVhZFRvb2xFcnJvchIVCg1lcnJvcl9tZXNzYWdlGAEgASgJImoKEFJlY29yZFNjcmVlbkFyZ3MSDAoEbW9kZRgBIAEoBRIUCgx0b29sX2NhbGxfaWQYAiABKAkSHQoQc2F2ZV9hc19maWxlbmFtZRgDIAEoCUgAiAEBQhMKEV9zYXZlX2FzX2ZpbGVuYW1lIokCChJSZWNvcmRTY3JlZW5SZXN1bHQSOwoNc3RhcnRfc3VjY2VzcxgBIAEoCzIiLmFnZW50LnYxLlJlY29yZFNjcmVlblN0YXJ0U3VjY2Vzc0gAEjkKDHNhdmVfc3VjY2VzcxgCIAEoCzIhLmFnZW50LnYxLlJlY29yZFNjcmVlblNhdmVTdWNjZXNzSAASPwoPZGlzY2FyZF9zdWNjZXNzGAMgASgLMiQuYWdlbnQudjEuUmVjb3JkU2NyZWVuRGlzY2FyZFN1Y2Nlc3NIABIwCgdmYWlsdXJlGAQgASgLMh0uYWdlbnQudjEuUmVjb3JkU2NyZWVuRmFpbHVyZUgAQggKBnJlc3VsdCJnChhSZWNvcmRTY3JlZW5TdGFydFN1Y2Nlc3MSJQodd2FzX3ByaW9yX3JlY29yZGluZ19jYW5jZWxsZWQYASABKAgSJAocd2FzX3NhdmVfYXNfZmlsZW5hbWVfaWdub3JlZBgCIAEoCCKgAQoXUmVjb3JkU2NyZWVuU2F2ZVN1Y2Nlc3MSDAoEcGF0aBgBIAEoCRIdChVyZWNvcmRpbmdfZHVyYXRpb25fbXMYAiABKAMSMAojcmVxdWVzdGVkX2ZpbGVfcGF0aF9yZWplY3RlZF9yZWFzb24YAyABKAVIAIgBAUImCiRfcmVxdWVzdGVkX2ZpbGVfcGF0aF9yZWplY3RlZF9yZWFzb24iHAoaUmVjb3JkU2NyZWVuRGlzY2FyZFN1Y2Nlc3MiJAoTUmVjb3JkU2NyZWVuRmFpbHVyZRINCgVlcnJvchgBIAEoCSI2ChNDdXJzb3JQYWNrYWdlUHJvbXB0EgwKBG5hbWUYASABKAkSEQoJZmlsZV9wYXRoGAIgASgJIuIBCg1DdXJzb3JQYWNrYWdlEgwKBG5hbWUYASABKAkSEwoLZGVzY3JpcHRpb24YAiABKAkSEwoLZm9sZGVyX3BhdGgYAyABKAkSDwoHZW5hYmxlZBgEIAEoCBIYCgtwYXJzZV9lcnJvchgFIAEoCUgAiAEBEi4KB3Byb21wdHMYBiADKAsyHS5hZ2VudC52MS5DdXJzb3JQYWNrYWdlUHJvbXB0EhgKEHJlYWRtZV9maWxlX3BhdGgYByABKAkSFAoMcGFja2FnZV90eXBlGAggASgFQg4KDF9wYXJzZV9lcnJvciKrAgoWUmVwb3NpdG9yeUluZGV4aW5nSW5mbxIfChdyZWxhdGl2ZV93b3Jrc3BhY2VfcGF0aBgBIAEoCRITCgtyZW1vdGVfdXJscxgCIAMoCRIUCgxyZW1vdGVfbmFtZXMYAyADKAkSEQoJcmVwb19uYW1lGAQgASgJEhIKCnJlcG9fb3duZXIYBSABKAkSEgoKaXNfdHJhY2tlZBgGIAEoCBIQCghpc19sb2NhbBgHIAEoCBImChlvcnRob2dvbmFsX3RyYW5zZm9ybV9zZWVkGAggASgBSACIAQESFQoNd29ya3NwYWNlX3VyaRgJIAEoCRIbChNwYXRoX2VuY3J5cHRpb25fa2V5GAogASgJQhwKGl9vcnRob2dvbmFsX3RyYW5zZm9ybV9zZWVkIqwCChJSZXF1ZXN0Q29udGV4dEFyZ3MSHQoQbm90ZXNfc2Vzc2lvbl9pZBgCIAEoCUgAiAEBEhkKDHdvcmtzcGFjZV9pZBgDIAEoCUgBiAEBEiYKGXJlYWRfb25seV9waW5uZWRfdHJlZV9zaGEYBCABKAlIAogBARIoChtyZWFkX29ubHlfcGx1Z2luX2NhY2hlX3Jvb3QYBSABKAlIA4gBARIXCgp1c2VfY2FjaGVkGAcgASgISASIAQFCEwoRX25vdGVzX3Nlc3Npb25faWRCDwoNX3dvcmtzcGFjZV9pZEIcChpfcmVhZF9vbmx5X3Bpbm5lZF90cmVlX3NoYUIeChxfcmVhZF9vbmx5X3BsdWdpbl9jYWNoZV9yb290Qg0KC191c2VfY2FjaGVkIroBChRSZXF1ZXN0Q29udGV4dFJlc3VsdBIyCgdzdWNjZXNzGAEgASgLMh8uYWdlbnQudjEuUmVxdWVzdENvbnRleHRTdWNjZXNzSAASLgoFZXJyb3IYAiABKAsyHS5hZ2VudC52MS5SZXF1ZXN0Q29udGV4dEVycm9ySAASNAoIcmVqZWN0ZWQYAyABKAsyIC5hZ2VudC52MS5SZXF1ZXN0Q29udGV4dFJlamVjdGVkSABCCAoGcmVzdWx0IooBChVSZXF1ZXN0Q29udGV4dFN1Y2Nlc3MSMQoPcmVxdWVzdF9jb250ZXh0GAEgASgLMhguYWdlbnQudjEuUmVxdWVzdENvbnRleHQSIwoWc2VydmVkX2Zyb21fZGlza19jYWNoZRgCIAEoCEgAiAEBQhkKF19zZXJ2ZWRfZnJvbV9kaXNrX2NhY2hlIiQKE1JlcXVlc3RDb250ZXh0RXJyb3ISDQoFZXJyb3IYASABKAkiKAoWUmVxdWVzdENvbnRleHRSZWplY3RlZBIOCgZyZWFzb24YASABKAkiwgEKCkltYWdlUHJvdG8SDAoEZGF0YRgBIAEoDBIMCgR1dWlkGAIgASgJEgwKBHBhdGgYAyABKAkSMQoJZGltZW5zaW9uGAQgASgLMh4uYWdlbnQudjEuSW1hZ2VQcm90b19EaW1lbnNpb24SJgoZdGFza19zcGVjaWZpY19kZXNjcmlwdGlvbhgGIAEoCUgAiAEBEhEKCW1pbWVfdHlwZRgHIAEoCUIcChpfdGFza19zcGVjaWZpY19kZXNjcmlwdGlvbiI1ChRJbWFnZVByb3RvX0RpbWVuc2lvbhINCgV3aWR0aBgBIAEoBRIOCgZoZWlnaHQYAiABKAUiaAoLR2l0UmVwb0luZm8SDAoEcGF0aBgBIAEoCRIOCgZzdGF0dXMYAiABKAkSEwoLYnJhbmNoX25hbWUYAyABKAkSFwoKcmVtb3RlX3VybBgEIAEoCUgAiAEBQg0KC19yZW1vdGVfdXJsIpsCChFSZXF1ZXN0Q29udGV4dEVudhISCgpvc192ZXJzaW9uGAEgASgJEhcKD3dvcmtzcGFjZV9wYXRocxgCIAMoCRINCgVzaGVsbBgDIAEoCRIXCg9zYW5kYm94X2VuYWJsZWQYBSABKAgSGAoQdGVybWluYWxzX2ZvbGRlchgHIAEoCRIhChlhZ2VudF9zaGFyZWRfbm90ZXNfZm9sZGVyGAggASgJEicKH2FnZW50X2NvbnZlcnNhdGlvbl9ub3Rlc19mb2xkZXIYCSABKAkSEQoJdGltZV96b25lGAogASgJEhYKDnByb2plY3RfZm9sZGVyGAsgASgJEiAKGGFnZW50X3RyYW5zY3JpcHRzX2ZvbGRlchgMIAEoCSI8Cg9EZWJ1Z01vZGVDb25maWcSEAoIbG9nX3BhdGgYASABKAkSFwoPc2VydmVyX2VuZHBvaW50GAIgASgJIrQBCg9Ta2lsbERlc2NyaXB0b3ISDAoEbmFtZRgBIAEoCRITCgtkZXNjcmlwdGlvbhgCIAEoCRITCgtmb2xkZXJfcGF0aBgDIAEoCRIPCgdlbmFibGVkGAQgASgIEhgKC3BhcnNlX2Vycm9yGAUgASgJSACIAQESGAoQcmVhZG1lX2ZpbGVfcGF0aBgGIAEoCRIUCgxwYWNrYWdlX3R5cGUYByABKAVCDgoMX3BhcnNlX2Vycm9yIkQKDFNraWxsT3B0aW9ucxI0ChFza2lsbF9kZXNjcmlwdG9ycxgBIAMoCzIZLmFnZW50LnYxLlNraWxsRGVzY3JpcHRvciL2CAoOUmVxdWVzdENvbnRleHQSIwoFcnVsZXMYAiADKAsyFC5hZ2VudC52MS5DdXJzb3JSdWxlEigKA2VudhgEIAEoCzIbLmFnZW50LnYxLlJlcXVlc3RDb250ZXh0RW52EjkKD3JlcG9zaXRvcnlfaW5mbxgGIAMoCzIgLmFnZW50LnYxLlJlcG9zaXRvcnlJbmRleGluZ0luZm8SKgoFdG9vbHMYByADKAsyGy5hZ2VudC52MS5NY3BUb29sRGVmaW5pdGlvbhInChpjb252ZXJzYXRpb25fbm90ZXNfbGlzdGluZxgIIAEoCUgAiAEBEiEKFHNoYXJlZF9ub3Rlc19saXN0aW5nGAkgASgJSAGIAQESKAoJZ2l0X3JlcG9zGAsgAygLMhUuYWdlbnQudjEuR2l0UmVwb0luZm8SNgoPcHJvamVjdF9sYXlvdXRzGA0gAygLMh0uYWdlbnQudjEuTHNEaXJlY3RvcnlUcmVlTm9kZRIzChBtY3BfaW5zdHJ1Y3Rpb25zGA4gAygLMhkuYWdlbnQudjEuTWNwSW5zdHJ1Y3Rpb25zEjkKEWRlYnVnX21vZGVfY29uZmlnGA8gASgLMhkuYWdlbnQudjEuRGVidWdNb2RlQ29uZmlnSAKIAQESFwoKY2xvdWRfcnVsZRgQIAEoCUgDiAEBEh8KEndlYl9zZWFyY2hfZW5hYmxlZBgRIAEoCEgEiAEBEjIKDXNraWxsX29wdGlvbnMYEiABKAsyFi5hZ2VudC52MS5Ta2lsbE9wdGlvbnNIBYgBARIuCiFyZXBvc2l0b3J5X2luZm9fc2hvdWxkX3F1ZXJ5X3Byb2QYEyABKAhIBogBARJBCg1maWxlX2NvbnRlbnRzGBQgAygLMiouYWdlbnQudjEuUmVxdWVzdENvbnRleHQuRmlsZUNvbnRlbnRzRW50cnkSIAoTdXNlcl9pbnRlbnRfc3VtbWFyeRgVIAEoCUgHiAEBEjIKEGN1c3RvbV9zdWJhZ2VudHMYFiADKAsyGC5hZ2VudC52MS5DdXN0b21TdWJhZ2VudBJEChdtY3BfZmlsZV9zeXN0ZW1fb3B0aW9ucxgXIAEoCzIeLmFnZW50LnYxLk1jcEZpbGVTeXN0ZW1PcHRpb25zSAiIAQEaMwoRRmlsZUNvbnRlbnRzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4AUIdChtfY29udmVyc2F0aW9uX25vdGVzX2xpc3RpbmdCFwoVX3NoYXJlZF9ub3Rlc19saXN0aW5nQhQKEl9kZWJ1Z19tb2RlX2NvbmZpZ0INCgtfY2xvdWRfcnVsZUIVChNfd2ViX3NlYXJjaF9lbmFibGVkQhAKDl9za2lsbF9vcHRpb25zQiQKIl9yZXBvc2l0b3J5X2luZm9fc2hvdWxkX3F1ZXJ5X3Byb2RCFgoUX3VzZXJfaW50ZW50X3N1bW1hcnlCGgoYX21jcF9maWxlX3N5c3RlbV9vcHRpb25zIrICCg1TYW5kYm94UG9saWN5EgwKBHR5cGUYASABKAUSGwoObmV0d29ya19hY2Nlc3MYAiABKAhIAIgBARIiChphZGRpdGlvbmFsX3JlYWR3cml0ZV9wYXRocxgDIAMoCRIhChlhZGRpdGlvbmFsX3JlYWRvbmx5X3BhdGhzGAQgAygJEh0KEGRlYnVnX291dHB1dF9kaXIYBSABKAlIAYgBARIdChBibG9ja19naXRfd3JpdGVzGAYgASgISAKIAQESHgoRZGlzYWJsZV90bXBfd3JpdGUYByABKAhIA4gBAUIRCg9fbmV0d29ya19hY2Nlc3NCEwoRX2RlYnVnX291dHB1dF9kaXJCEwoRX2Jsb2NrX2dpdF93cml0ZXNCFAoSX2Rpc2FibGVfdG1wX3dyaXRlIu8BCg1TZWxlY3RlZEltYWdlEgwKBHV1aWQYAiABKAkSDAoEcGF0aBgDIAEoCRI0CglkaW1lbnNpb24YBCABKAsyIS5hZ2VudC52MS5TZWxlY3RlZEltYWdlX0RpbWVuc2lvbhIRCgltaW1lX3R5cGUYByABKAkSEQoHYmxvYl9pZBgBIAEoDEgAEg4KBGRhdGEYCCABKAxIABJDChFibG9iX2lkX3dpdGhfZGF0YRgJIAEoCzImLmFnZW50LnYxLlNlbGVjdGVkSW1hZ2VfQmxvYklkV2l0aERhdGFIAEIRCg9kYXRhX29yX2Jsb2JfaWQiPQocU2VsZWN0ZWRJbWFnZV9CbG9iSWRXaXRoRGF0YRIPCgdibG9iX2lkGAEgASgMEgwKBGRhdGEYAiABKAwiOAoXU2VsZWN0ZWRJbWFnZV9EaW1lbnNpb24SDQoFd2lkdGgYASABKAUSDgoGaGVpZ2h0GAIgASgFIkkKEUV4dHJhQ29udGV4dEVudHJ5Eg4KBGRhdGEYASABKAlIABIRCgdibG9iX2lkGAIgASgMSABCEQoPZGF0YV9vcl9ibG9iX2lkIlsKDFNlbGVjdGVkRmlsZRIPCgdjb250ZW50GAEgASgJEgwKBHBhdGgYAiABKAkSGgoNcmVsYXRpdmVfcGF0aBgDIAEoCUgAiAEBQhAKDl9yZWxhdGl2ZV9wYXRoIoQBChVTZWxlY3RlZENvZGVTZWxlY3Rpb24SDwoHY29udGVudBgBIAEoCRIMCgRwYXRoGAIgASgJEhoKDXJlbGF0aXZlX3BhdGgYAyABKAlIAIgBARIeCgVyYW5nZRgEIAEoCzIPLmFnZW50LnYxLlJhbmdlQhAKDl9yZWxhdGl2ZV9wYXRoIl0KEFNlbGVjdGVkVGVybWluYWwSDwoHY29udGVudBgBIAEoCRISCgV0aXRsZRgCIAEoCUgAiAEBEhEKBHBhdGgYAyABKAlIAYgBAUIICgZfdGl0bGVCBwoFX3BhdGgihgEKGVNlbGVjdGVkVGVybWluYWxTZWxlY3Rpb24SDwoHY29udGVudBgBIAEoCRISCgV0aXRsZRgCIAEoCUgAiAEBEhEKBHBhdGgYAyABKAlIAYgBARIeCgVyYW5nZRgEIAEoCzIPLmFnZW50LnYxLlJhbmdlQggKBl90aXRsZUIHCgVfcGF0aCKDAQoOU2VsZWN0ZWRGb2xkZXISDAoEcGF0aBgBIAEoCRIaCg1yZWxhdGl2ZV9wYXRoGAIgASgJSACIAQESNQoOZGlyZWN0b3J5X3RyZWUYAyABKAsyHS5hZ2VudC52MS5Mc0RpcmVjdG9yeVRyZWVOb2RlQhAKDl9yZWxhdGl2ZV9wYXRoIp8BChRTZWxlY3RlZEV4dGVybmFsTGluaxILCgN1cmwYASABKAkSDAoEdXVpZBgCIAEoCRIYCgtwZGZfY29udGVudBgDIAEoCUgAiAEBEhMKBmlzX3BkZhgEIAEoCEgBiAEBEhUKCGZpbGVuYW1lGAUgASgJSAKIAQFCDgoMX3BkZl9jb250ZW50QgkKB19pc19wZGZCCwoJX2ZpbGVuYW1lIjgKElNlbGVjdGVkQ3Vyc29yUnVsZRIiCgRydWxlGAEgASgLMhQuYWdlbnQudjEuQ3Vyc29yUnVsZSIiCg9TZWxlY3RlZEdpdERpZmYSDwoHY29udGVudBgBIAEoCSIyCh9TZWxlY3RlZEdpdERpZmZGcm9tQnJhbmNoVG9NYWluEg8KB2NvbnRlbnQYASABKAkiaQoRU2VsZWN0ZWRHaXRDb21taXQSCwoDc2hhGAEgASgJEg8KB21lc3NhZ2UYAiABKAkSGAoLZGVzY3JpcHRpb24YAyABKAlIAIgBARIMCgRkaWZmGAQgASgJQg4KDF9kZXNjcmlwdGlvbiLdAQoTU2VsZWN0ZWRQdWxsUmVxdWVzdBIOCgZudW1iZXIYASABKAUSCwoDdXJsGAIgASgJEhIKBXRpdGxlGAMgASgJSACIAQESEwoLZm9sZGVyX3BhdGgYBCABKAkSGQoMc3VtbWFyeV9qc29uGAUgASgJSAGIAQESGAoLZGVzY3JpcHRpb24YBiABKAlIAogBARIUCgdibG9iX2lkGAcgASgMSAOIAQFCCAoGX3RpdGxlQg8KDV9zdW1tYXJ5X2pzb25CDgoMX2Rlc2NyaXB0aW9uQgoKCF9ibG9iX2lkIrMBChpTZWxlY3RlZEdpdFBSRGlmZlNlbGVjdGlvbhIOCgZwcl91cmwYASABKAkSEQoJZmlsZV9wYXRoGAIgASgJEhIKCnN0YXJ0X2xpbmUYAyABKAUSEAoIZW5kX2xpbmUYBCABKAUSGQoMZGlmZl9jb250ZW50GAUgASgJSACIAQESFAoHYmxvYl9pZBgGIAEoDEgBiAEBQg8KDV9kaWZmX2NvbnRlbnRCCgoIX2Jsb2JfaWQiNgoVU2VsZWN0ZWRDdXJzb3JDb21tYW5kEgwKBG5hbWUYASABKAkSDwoHY29udGVudBgCIAEoCSI1ChVTZWxlY3RlZERvY3VtZW50YXRpb24SDgoGZG9jX2lkGAEgASgJEgwKBG5hbWUYAiABKAkiMgoQU2VsZWN0ZWRQYXN0Q2hhdBIQCghhZ2VudF9pZBgBIAEoCRIMCgRuYW1lGAIgASgJIqsBCglDYWxsRnJhbWUSGgoNZnVuY3Rpb25fbmFtZRgBIAEoCUgAiAEBEhAKA3VybBgCIAEoCUgBiAEBEhgKC2xpbmVfbnVtYmVyGAMgASgFSAKIAQESGgoNY29sdW1uX251bWJlchgEIAEoBUgDiAEBQhAKDl9mdW5jdGlvbl9uYW1lQgYKBF91cmxCDgoMX2xpbmVfbnVtYmVyQhAKDl9jb2x1bW5fbnVtYmVyImgKClN0YWNrVHJhY2USKAoLY2FsbF9mcmFtZXMYASADKAsyEy5hZ2VudC52MS5DYWxsRnJhbWUSHAoPcmF3X3N0YWNrX3RyYWNlGAIgASgJSACIAQFCEgoQX3Jhd19zdGFja190cmFjZSLkAQoSU2VsZWN0ZWRDb25zb2xlTG9nEg8KB21lc3NhZ2UYASABKAkSEQoJdGltZXN0YW1wGAIgASgBEg0KBWxldmVsGAMgASgJEhMKC2NsaWVudF9uYW1lGAQgASgJEhIKCnNlc3Npb25faWQYBSABKAkSLgoLc3RhY2tfdHJhY2UYBiABKAsyFC5hZ2VudC52MS5TdGFja1RyYWNlSACIAQESHQoQb2JqZWN0X2RhdGFfanNvbhgHIAEoCUgBiAEBQg4KDF9zdGFja190cmFjZUITChFfb2JqZWN0X2RhdGFfanNvbiK6AQoRU2VsZWN0ZWRVSUVsZW1lbnQSDwoHZWxlbWVudBgBIAEoCRINCgV4cGF0aBgCIAEoCRIUCgx0ZXh0X2NvbnRlbnQYAyABKAkSDQoFZXh0cmEYBCABKAkSFgoJY29tcG9uZW50GAUgASgJSACIAQESIQoUY29tcG9uZW50X3Byb3BzX2pzb24YBiABKAlIAYgBAUIMCgpfY29tcG9uZW50QhcKFV9jb21wb25lbnRfcHJvcHNfanNvbiIgChBTZWxlY3RlZFN1YmFnZW50EgwKBG5hbWUYASABKAkiggoKD1NlbGVjdGVkQ29udGV4dBIwCg9zZWxlY3RlZF9pbWFnZXMYASADKAsyFy5hZ2VudC52MS5TZWxlY3RlZEltYWdlEjwKEmludm9jYXRpb25fY29udGV4dBgCIAEoCzIbLmFnZW50LnYxLkludm9jYXRpb25Db250ZXh0SACIAQESFQoNZXh0cmFfY29udGV4dBgDIAMoCRI6ChVleHRyYV9jb250ZXh0X2VudHJpZXMYECADKAsyGy5hZ2VudC52MS5FeHRyYUNvbnRleHRFbnRyeRIlCgVmaWxlcxgEIAMoCzIWLmFnZW50LnYxLlNlbGVjdGVkRmlsZRI4Cg9jb2RlX3NlbGVjdGlvbnMYBSADKAsyHy5hZ2VudC52MS5TZWxlY3RlZENvZGVTZWxlY3Rpb24SLQoJdGVybWluYWxzGAYgAygLMhouYWdlbnQudjEuU2VsZWN0ZWRUZXJtaW5hbBJAChN0ZXJtaW5hbF9zZWxlY3Rpb25zGAcgAygLMiMuYWdlbnQudjEuU2VsZWN0ZWRUZXJtaW5hbFNlbGVjdGlvbhIpCgdmb2xkZXJzGAggAygLMhguYWdlbnQudjEuU2VsZWN0ZWRGb2xkZXISNgoOZXh0ZXJuYWxfbGlua3MYCSADKAsyHi5hZ2VudC52MS5TZWxlY3RlZEV4dGVybmFsTGluaxIyCgxjdXJzb3JfcnVsZXMYCiADKAsyHC5hZ2VudC52MS5TZWxlY3RlZEN1cnNvclJ1bGUSMAoIZ2l0X2RpZmYYEiABKAsyGS5hZ2VudC52MS5TZWxlY3RlZEdpdERpZmZIAYgBARJUChxnaXRfZGlmZl9mcm9tX2JyYW5jaF90b19tYWluGAsgASgLMikuYWdlbnQudjEuU2VsZWN0ZWRHaXREaWZmRnJvbUJyYW5jaFRvTWFpbkgCiAEBEjgKD2N1cnNvcl9jb21tYW5kcxgMIAMoCzIfLmFnZW50LnYxLlNlbGVjdGVkQ3Vyc29yQ29tbWFuZBI3Cg5kb2N1bWVudGF0aW9ucxgNIAMoCzIfLmFnZW50LnYxLlNlbGVjdGVkRG9jdW1lbnRhdGlvbhIwCgt1aV9lbGVtZW50cxgOIAMoCzIbLmFnZW50LnYxLlNlbGVjdGVkVUlFbGVtZW50EjIKDGNvbnNvbGVfbG9ncxgPIAMoCzIcLmFnZW50LnYxLlNlbGVjdGVkQ29uc29sZUxvZxIwCgtnaXRfY29tbWl0cxgRIAMoCzIbLmFnZW50LnYxLlNlbGVjdGVkR2l0Q29tbWl0Ei4KCnBhc3RfY2hhdHMYEyADKAsyGi5hZ2VudC52MS5TZWxlY3RlZFBhc3RDaGF0EkQKFmdpdF9wcl9kaWZmX3NlbGVjdGlvbnMYFCADKAsyJC5hZ2VudC52MS5TZWxlY3RlZEdpdFBSRGlmZlNlbGVjdGlvbhI9ChZzZWxlY3RlZF9wdWxsX3JlcXVlc3RzGBUgAygLMh0uYWdlbnQudjEuU2VsZWN0ZWRQdWxsUmVxdWVzdBI2ChJzZWxlY3RlZF9zdWJhZ2VudHMYFiADKAsyGi5hZ2VudC52MS5TZWxlY3RlZFN1YmFnZW50QhUKE19pbnZvY2F0aW9uX2NvbnRleHRCCwoJX2dpdF9kaWZmQh8KHV9naXRfZGlmZl9mcm9tX2JyYW5jaF90b19tYWluIuUBChFJbnZvY2F0aW9uQ29udGV4dBI/CgxzbGFja190aHJlYWQYASABKAsyJy5hZ2VudC52MS5JbnZvY2F0aW9uQ29udGV4dF9TbGFja1RocmVhZEgAEjkKCWdpdGh1Yl9wchgCIAEoCzIkLmFnZW50LnYxLkludm9jYXRpb25Db250ZXh0X0dpdGh1YlBSSAASOQoJaWRlX3N0YXRlGAMgASgLMiQuYWdlbnQudjEuSW52b2NhdGlvbkNvbnRleHRfSWRlU3RhdGVIABIRCgdibG9iX2lkGAogASgMSABCBgoEZGF0YSK7AQodSW52b2NhdGlvbkNvbnRleHRfU2xhY2tUaHJlYWQSDgoGdGhyZWFkGAEgASgJEhkKDGNoYW5uZWxfbmFtZRgCIAEoCUgAiAEBEhwKD2NoYW5uZWxfcHVycG9zZRgDIAEoCUgBiAEBEhoKDWNoYW5uZWxfdG9waWMYBCABKAlIAogBAUIPCg1fY2hhbm5lbF9uYW1lQhIKEF9jaGFubmVsX3B1cnBvc2VCEAoOX2NoYW5uZWxfdG9waWMifAoaSW52b2NhdGlvbkNvbnRleHRfR2l0aHViUFISDQoFdGl0bGUYASABKAkSEwoLZGVzY3JpcHRpb24YAiABKAkSEAoIY29tbWVudHMYAyABKAkSGAoLY2lfZmFpbHVyZXMYBCABKAlIAIgBAUIOCgxfY2lfZmFpbHVyZXMi/gEKGkludm9jYXRpb25Db250ZXh0X0lkZVN0YXRlEkAKDXZpc2libGVfZmlsZXMYASADKAsyKS5hZ2VudC52MS5JbnZvY2F0aW9uQ29udGV4dF9JZGVTdGF0ZV9GaWxlEkgKFXJlY2VudGx5X3ZpZXdlZF9maWxlcxgCIAMoCzIpLmFnZW50LnYxLkludm9jYXRpb25Db250ZXh0X0lkZVN0YXRlX0ZpbGUSVAoUY3VycmVudGx5X3ZpZXdlZF9wcnMYAyADKAsyNi5hZ2VudC52MS5JbnZvY2F0aW9uQ29udGV4dF9JZGVTdGF0ZV9WaWV3ZWRQdWxsUmVxdWVzdCKOAgofSW52b2NhdGlvbkNvbnRleHRfSWRlU3RhdGVfRmlsZRIMCgRwYXRoGAEgASgJEhoKDXJlbGF0aXZlX3BhdGgYAiABKAlIAIgBARJWCg9jdXJzb3JfcG9zaXRpb24YAyABKAsyOC5hZ2VudC52MS5JbnZvY2F0aW9uQ29udGV4dF9JZGVTdGF0ZV9GaWxlX0N1cnNvclBvc2l0aW9uSAGIAQESEwoLdG90YWxfbGluZXMYBCABKAUSGwoOYWN0aXZlX2NvbW1hbmQYBSABKAlIAogBAUIQCg5fcmVsYXRpdmVfcGF0aEISChBfY3Vyc29yX3Bvc2l0aW9uQhEKD19hY3RpdmVfY29tbWFuZCJMCi5JbnZvY2F0aW9uQ29udGV4dF9JZGVTdGF0ZV9GaWxlX0N1cnNvclBvc2l0aW9uEgwKBGxpbmUYASABKAUSDAoEdGV4dBgCIAEoCSLpAQosSW52b2NhdGlvbkNvbnRleHRfSWRlU3RhdGVfVmlld2VkUHVsbFJlcXVlc3QSDgoGbnVtYmVyGAEgASgFEgsKA3VybBgCIAEoCRISCgV0aXRsZRgDIAEoCUgAiAEBEhgKC2ZvbGRlcl9wYXRoGAQgASgJSAGIAQESGQoMc3VtbWFyeV9qc29uGAUgASgJSAKIAQESGAoLZGVzY3JpcHRpb24YBiABKAlIA4gBAUIICgZfdGl0bGVCDgoMX2ZvbGRlcl9wYXRoQg8KDV9zdW1tYXJ5X2pzb25CDgoMX2Rlc2NyaXB0aW9uIkgKFlNldHVwVm1FbnZpcm9ubWVudEFyZ3MSFwoPaW5zdGFsbF9jb21tYW5kGAIgASgJEhUKDXN0YXJ0X2NvbW1hbmQYAyABKAkiXAoYU2V0dXBWbUVudmlyb25tZW50UmVzdWx0EjYKB3N1Y2Nlc3MYASABKAsyIy5hZ2VudC52MS5TZXR1cFZtRW52aXJvbm1lbnRTdWNjZXNzSABCCAoGcmVzdWx0IhsKGVNldHVwVm1FbnZpcm9ubWVudFN1Y2Nlc3MigAEKGlNldHVwVm1FbnZpcm9ubWVudFRvb2xDYWxsEi4KBGFyZ3MYASABKAsyIC5hZ2VudC52MS5TZXR1cFZtRW52aXJvbm1lbnRBcmdzEjIKBnJlc3VsdBgCIAEoCzIiLmFnZW50LnYxLlNldHVwVm1FbnZpcm9ubWVudFJlc3VsdCLJAgoZU2hlbGxDb21tYW5kUGFyc2luZ1Jlc3VsdBIWCg5wYXJzaW5nX2ZhaWxlZBgBIAEoCBJSChNleGVjdXRhYmxlX2NvbW1hbmRzGAIgAygLMjUuYWdlbnQudjEuU2hlbGxDb21tYW5kUGFyc2luZ1Jlc3VsdF9FeGVjdXRhYmxlQ29tbWFuZBIVCg1oYXNfcmVkaXJlY3RzGAMgASgIEiAKGGhhc19jb21tYW5kX3N1YnN0aXR1dGlvbhgEIAEoCBInChphbGxfcmVkaXJlY3RzX2FyZV9kZXZfbnVsbBgFIAEoCEgAiAEBEj8KCXJlZGlyZWN0cxgGIAMoCzIsLmFnZW50LnYxLlNoZWxsQ29tbWFuZFBhcnNpbmdSZXN1bHRfUmVkaXJlY3RCHQobX2FsbF9yZWRpcmVjdHNfYXJlX2Rldl9udWxsIk0KLlNoZWxsQ29tbWFuZFBhcnNpbmdSZXN1bHRfRXhlY3V0YWJsZUNvbW1hbmRBcmcSDAoEdHlwZRgBIAEoCRINCgV2YWx1ZRgCIAEoCSKWAQorU2hlbGxDb21tYW5kUGFyc2luZ1Jlc3VsdF9FeGVjdXRhYmxlQ29tbWFuZBIMCgRuYW1lGAEgASgJEkYKBGFyZ3MYAiADKAsyOC5hZ2VudC52MS5TaGVsbENvbW1hbmRQYXJzaW5nUmVzdWx0X0V4ZWN1dGFibGVDb21tYW5kQXJnEhEKCWZ1bGxfdGV4dBgDIAEoCSL6BwoJU2hlbGxBcmdzEg8KB2NvbW1hbmQYASABKAkSGQoRd29ya2luZ19kaXJlY3RvcnkYAiABKAkSDwoHdGltZW91dBgDIAEoBRIUCgx0b29sX2NhbGxfaWQYBCABKAkSFwoPc2ltcGxlX2NvbW1hbmRzGAUgAygJEhoKEmhhc19pbnB1dF9yZWRpcmVjdBgGIAEoCBIbChNoYXNfb3V0cHV0X3JlZGlyZWN0GAcgASgIEjsKDnBhcnNpbmdfcmVzdWx0GAggASgLMiMuYWdlbnQudjEuU2hlbGxDb21tYW5kUGFyc2luZ1Jlc3VsdBI+ChhyZXF1ZXN0ZWRfc2FuZGJveF9wb2xpY3kYCSABKAsyFy5hZ2VudC52MS5TYW5kYm94UG9saWN5SACIAQESKAobZmlsZV9vdXRwdXRfdGhyZXNob2xkX2J5dGVzGAogASgESAGIAQESFQoNaXNfYmFja2dyb3VuZBgLIAEoCBIVCg1za2lwX2FwcHJvdmFsGAwgASgIEhgKEHRpbWVvdXRfYmVoYXZpb3IYDSABKAUSGQoMaGFyZF90aW1lb3V0GA4gASgFSAKIAQESGAoLZGVzY3JpcHRpb24YDyABKAlIA4gBARJBChFjbGFzc2lmaWVyX3Jlc3VsdBgQIAEoCzIhLmFnZW50LnYxLkNvbW1hbmRDbGFzc2lmaWVyUmVzdWx0SASIAQESEwoLY2xvc2Vfc3RkaW4YESABKAgSSQoTb3V0cHV0X25vdGlmaWNhdGlvbhgSIAEoCzInLmFnZW50LnYxLlNoZWxsT3V0cHV0Tm90aWZpY2F0aW9uQ29uZmlnSAWIAQESPQoTc21hcnRfbW9kZV9hcHByb3ZhbBgTIAEoCzIbLmFnZW50LnYxLlNtYXJ0TW9kZUFwcHJvdmFsSAaIAQESTgoZaG9va19hcHByb3ZhbF9yZXF1aXJlbWVudBgUIAEoCzImLmFnZW50LnYxLlNoZWxsSG9va0FwcHJvdmFsUmVxdWlyZW1lbnRIB4gBARIcCg9jb252ZXJzYXRpb25faWQYFSABKAlICIgBAUIbChlfcmVxdWVzdGVkX3NhbmRib3hfcG9saWN5Qh4KHF9maWxlX291dHB1dF90aHJlc2hvbGRfYnl0ZXNCDwoNX2hhcmRfdGltZW91dEIOCgxfZGVzY3JpcHRpb25CFAoSX2NsYXNzaWZpZXJfcmVzdWx0QhYKFF9vdXRwdXRfbm90aWZpY2F0aW9uQhYKFF9zbWFydF9tb2RlX2FwcHJvdmFsQhwKGl9ob29rX2FwcHJvdmFsX3JlcXVpcmVtZW50QhIKEF9jb252ZXJzYXRpb25faWQi+gMKC1NoZWxsUmVzdWx0EjQKDnNhbmRib3hfcG9saWN5GGUgASgLMhcuYWdlbnQudjEuU2FuZGJveFBvbGljeUgBiAEBEhoKDWlzX2JhY2tncm91bmQYZiABKAhIAogBARIdChB0ZXJtaW5hbHNfZm9sZGVyGGcgASgJSAOIAQESEAoDcGlkGGggASgNSASIAQESKQoHc3VjY2VzcxgBIAEoCzIWLmFnZW50LnYxLlNoZWxsU3VjY2Vzc0gAEikKB2ZhaWx1cmUYAiABKAsyFi5hZ2VudC52MS5TaGVsbEZhaWx1cmVIABIpCgd0aW1lb3V0GAMgASgLMhYuYWdlbnQudjEuU2hlbGxUaW1lb3V0SAASKwoIcmVqZWN0ZWQYBCABKAsyFy5hZ2VudC52MS5TaGVsbFJlamVjdGVkSAASMAoLc3Bhd25fZXJyb3IYBSABKAsyGS5hZ2VudC52MS5TaGVsbFNwYXduRXJyb3JIABI8ChFwZXJtaXNzaW9uX2RlbmllZBgHIAEoCzIfLmFnZW50LnYxLlNoZWxsUGVybWlzc2lvbkRlbmllZEgAQggKBnJlc3VsdEIRCg9fc2FuZGJveF9wb2xpY3lCEAoOX2lzX2JhY2tncm91bmRCEwoRX3Rlcm1pbmFsc19mb2xkZXJCBgoEX3BpZCIhChFTaGVsbFN0cmVhbVN0ZG91dBIMCgRkYXRhGAEgASgJIiEKEVNoZWxsU3RyZWFtU3RkZXJyEgwKBGRhdGEYASABKAki9wEKD1NoZWxsU3RyZWFtRXhpdBIMCgRjb2RlGAEgASgNEgsKA2N3ZBgCIAEoCRI2Cg9vdXRwdXRfbG9jYXRpb24YAyABKAsyGC5hZ2VudC52MS5PdXRwdXRMb2NhdGlvbkgAiAEBEg8KB2Fib3J0ZWQYBCABKAgSGQoMYWJvcnRfcmVhc29uGAUgASgFSAGIAQESJAoXbG9jYWxfZXhlY3V0aW9uX3RpbWVfbXMYBiABKAVIAogBAUISChBfb3V0cHV0X2xvY2F0aW9uQg8KDV9hYm9ydF9yZWFzb25CGgoYX2xvY2FsX2V4ZWN1dGlvbl90aW1lX21zIlsKEFNoZWxsU3RyZWFtU3RhcnQSNAoOc2FuZGJveF9wb2xpY3kYASABKAsyFy5hZ2VudC52MS5TYW5kYm94UG9saWN5SACIAQFCEQoPX3NhbmRib3hfcG9saWN5ItoBChdTaGVsbFN0cmVhbUJhY2tncm91bmRlZBIQCghzaGVsbF9pZBgBIAEoDRIPCgdjb21tYW5kGAIgASgJEhkKEXdvcmtpbmdfZGlyZWN0b3J5GAMgASgJEhAKA3BpZBgEIAEoDUgAiAEBEhcKCm1zX3RvX3dhaXQYBSABKAVIAYgBARI0CgZyZWFzb24YBiABKA4yHy5hZ2VudC52MS5TaGVsbEJhY2tncm91bmRSZWFzb25IAogBAUIGCgRfcGlkQg0KC19tc190b193YWl0QgkKB19yZWFzb24irAMKC1NoZWxsU3RyZWFtEi0KBnN0ZG91dBgBIAEoCzIbLmFnZW50LnYxLlNoZWxsU3RyZWFtU3Rkb3V0SAASLQoGc3RkZXJyGAIgASgLMhsuYWdlbnQudjEuU2hlbGxTdHJlYW1TdGRlcnJIABIpCgRleGl0GAMgASgLMhkuYWdlbnQudjEuU2hlbGxTdHJlYW1FeGl0SAASKwoFc3RhcnQYBCABKAsyGi5hZ2VudC52MS5TaGVsbFN0cmVhbVN0YXJ0SAASKwoIcmVqZWN0ZWQYBSABKAsyFy5hZ2VudC52MS5TaGVsbFJlamVjdGVkSAASPAoRcGVybWlzc2lvbl9kZW5pZWQYBiABKAsyHy5hZ2VudC52MS5TaGVsbFBlcm1pc3Npb25EZW5pZWRIABI5CgxiYWNrZ3JvdW5kZWQYByABKAsyIS5hZ2VudC52MS5TaGVsbFN0cmVhbUJhY2tncm91bmRlZEgAEjgKDGhvb2tfY29udGV4dBgIIAEoCzIgLmFnZW50LnYxLlNoZWxsU3RyZWFtSG9va0NvbnRleHRIAEIHCgVldmVudCJLCg5PdXRwdXRMb2NhdGlvbhIRCglmaWxlX3BhdGgYASABKAkSEgoKc2l6ZV9ieXRlcxgCIAEoAxISCgpsaW5lX2NvdW50GAMgASgDIpgFCgxTaGVsbFN1Y2Nlc3MSDwoHY29tbWFuZBgBIAEoCRIZChF3b3JraW5nX2RpcmVjdG9yeRgCIAEoCRIRCglleGl0X2NvZGUYAyABKAUSDgoGc2lnbmFsGAQgASgJEg4KBnN0ZG91dBgFIAEoCRIOCgZzdGRlcnIYBiABKAkSFgoOZXhlY3V0aW9uX3RpbWUYByABKAUSNgoPb3V0cHV0X2xvY2F0aW9uGAggASgLMhguYWdlbnQudjEuT3V0cHV0TG9jYXRpb25IAIgBARIVCghzaGVsbF9pZBgJIAEoDUgBiAEBEh8KEmludGVybGVhdmVkX291dHB1dBgKIAEoCUgCiAEBEhAKA3BpZBgLIAEoDUgDiAEBEhcKCm1zX3RvX3dhaXQYDCABKAVIBIgBARIkChdsb2NhbF9leGVjdXRpb25fdGltZV9tcxgNIAEoBUgFiAEBEj8KEWJhY2tncm91bmRfcmVhc29uGA4gASgOMh8uYWdlbnQudjEuU2hlbGxCYWNrZ3JvdW5kUmVhc29uSAaIAQESGAoLb3V0cHV0X2hlYWQYDyABKAlIB4gBARIYCgtvdXRwdXRfdGFpbBgQIAEoCUgIiAEBEhkKDGVsaWRlZF9jaGFycxgRIAEoDUgJiAEBQhIKEF9vdXRwdXRfbG9jYXRpb25CCwoJX3NoZWxsX2lkQhUKE19pbnRlcmxlYXZlZF9vdXRwdXRCBgoEX3BpZEINCgtfbXNfdG9fd2FpdEIaChhfbG9jYWxfZXhlY3V0aW9uX3RpbWVfbXNCFAoSX2JhY2tncm91bmRfcmVhc29uQg4KDF9vdXRwdXRfaGVhZEIOCgxfb3V0cHV0X3RhaWxCDwoNX2VsaWRlZF9jaGFycyKYBAoMU2hlbGxGYWlsdXJlEg8KB2NvbW1hbmQYASABKAkSGQoRd29ya2luZ19kaXJlY3RvcnkYAiABKAkSEQoJZXhpdF9jb2RlGAMgASgFEg4KBnNpZ25hbBgEIAEoCRIOCgZzdGRvdXQYBSABKAkSDgoGc3RkZXJyGAYgASgJEhYKDmV4ZWN1dGlvbl90aW1lGAcgASgFEjYKD291dHB1dF9sb2NhdGlvbhgIIAEoCzIYLmFnZW50LnYxLk91dHB1dExvY2F0aW9uSACIAQESHwoSaW50ZXJsZWF2ZWRfb3V0cHV0GAkgASgJSAGIAQESGQoMYWJvcnRfcmVhc29uGAogASgFSAKIAQESDwoHYWJvcnRlZBgLIAEoCBIkChdsb2NhbF9leGVjdXRpb25fdGltZV9tcxgMIAEoBUgDiAEBEhgKC291dHB1dF9oZWFkGA0gASgJSASIAQESGAoLb3V0cHV0X3RhaWwYDiABKAlIBYgBARIZCgxlbGlkZWRfY2hhcnMYDyABKA1IBogBAUISChBfb3V0cHV0X2xvY2F0aW9uQhUKE19pbnRlcmxlYXZlZF9vdXRwdXRCDwoNX2Fib3J0X3JlYXNvbkIaChhfbG9jYWxfZXhlY3V0aW9uX3RpbWVfbXNCDgoMX291dHB1dF9oZWFkQg4KDF9vdXRwdXRfdGFpbEIPCg1fZWxpZGVkX2NoYXJzIk4KDFNoZWxsVGltZW91dBIPCgdjb21tYW5kGAEgASgJEhkKEXdvcmtpbmdfZGlyZWN0b3J5GAIgASgJEhIKCnRpbWVvdXRfbXMYAyABKAUiYAoNU2hlbGxSZWplY3RlZBIPCgdjb21tYW5kGAEgASgJEhkKEXdvcmtpbmdfZGlyZWN0b3J5GAIgASgJEg4KBnJlYXNvbhgDIAEoCRITCgtpc19yZWFkb25seRgEIAEoCCJnChVTaGVsbFBlcm1pc3Npb25EZW5pZWQSDwoHY29tbWFuZBgBIAEoCRIZChF3b3JraW5nX2RpcmVjdG9yeRgCIAEoCRINCgVlcnJvchgDIAEoCRITCgtpc19yZWFkb25seRgEIAEoCCJMCg9TaGVsbFNwYXduRXJyb3ISDwoHY29tbWFuZBgBIAEoCRIZChF3b3JraW5nX2RpcmVjdG9yeRgCIAEoCRINCgVlcnJvchgDIAEoCSJAChJTaGVsbFBhcnRpYWxSZXN1bHQSFAoMc3Rkb3V0X2RlbHRhGAEgASgJEhQKDHN0ZGVycl9kZWx0YRgCIAEoCSJZCg1TaGVsbFRvb2xDYWxsEiEKBGFyZ3MYASABKAsyEy5hZ2VudC52MS5TaGVsbEFyZ3MSJQoGcmVzdWx0GAIgASgLMhUuYWdlbnQudjEuU2hlbGxSZXN1bHQiKwoYU2hlbGxUb29sQ2FsbFN0ZG91dERlbHRhEg8KB2NvbnRlbnQYASABKAkiKwoYU2hlbGxUb29sQ2FsbFN0ZGVyckRlbHRhEg8KB2NvbnRlbnQYASABKAkiiQEKElNoZWxsVG9vbENhbGxEZWx0YRI0CgZzdGRvdXQYASABKAsyIi5hZ2VudC52MS5TaGVsbFRvb2xDYWxsU3Rkb3V0RGVsdGFIABI0CgZzdGRlcnIYAiABKAsyIi5hZ2VudC52MS5TaGVsbFRvb2xDYWxsU3RkZXJyRGVsdGFIAEIHCgVkZWx0YSLtAQoMU3ViYWdlbnRUeXBlEjgKC3Vuc3BlY2lmaWVkGAEgASgLMiEuYWdlbnQudjEuU3ViYWdlbnRUeXBlVW5zcGVjaWZpZWRIABI5Cgxjb21wdXRlcl91c2UYAiABKAsyIS5hZ2VudC52MS5TdWJhZ2VudFR5cGVDb21wdXRlclVzZUgAEi4KBmN1c3RvbRgDIAEoCzIcLmFnZW50LnYxLlN1YmFnZW50VHlwZUN1c3RvbUgAEjAKB2V4cGxvcmUYBCABKAsyHS5hZ2VudC52MS5TdWJhZ2VudFR5cGVFeHBsb3JlSABCBgoEdHlwZSIZChdTdWJhZ2VudFR5cGVVbnNwZWNpZmllZCIZChdTdWJhZ2VudFR5cGVDb21wdXRlclVzZSIVChNTdWJhZ2VudFR5cGVFeHBsb3JlIiIKElN1YmFnZW50VHlwZUN1c3RvbRIMCgRuYW1lGAEgASgJIo0BCg5DdXN0b21TdWJhZ2VudBIRCglmdWxsX3BhdGgYASABKAkSDAoEbmFtZRgCIAEoCRITCgtkZXNjcmlwdGlvbhgDIAEoCRINCgV0b29scxgEIAMoCRINCgVtb2RlbBgFIAEoCRIOCgZwcm9tcHQYBiABKAkSFwoPcGVybWlzc2lvbl9tb2RlGAcgASgFImgKDlN3aXRjaE1vZGVBcmdzEhYKDnRhcmdldF9tb2RlX2lkGAEgASgJEhgKC2V4cGxhbmF0aW9uGAIgASgJSACIAQESFAoMdG9vbF9jYWxsX2lkGAMgASgJQg4KDF9leHBsYW5hdGlvbiKqAQoQU3dpdGNoTW9kZVJlc3VsdBIuCgdzdWNjZXNzGAEgASgLMhsuYWdlbnQudjEuU3dpdGNoTW9kZVN1Y2Nlc3NIABIqCgVlcnJvchgCIAEoCzIZLmFnZW50LnYxLlN3aXRjaE1vZGVFcnJvckgAEjAKCHJlamVjdGVkGAMgASgLMhwuYWdlbnQudjEuU3dpdGNoTW9kZVJlamVjdGVkSABCCAoGcmVzdWx0Ij0KEVN3aXRjaE1vZGVTdWNjZXNzEhQKDGZyb21fbW9kZV9pZBgBIAEoCRISCgp0b19tb2RlX2lkGAIgASgJIiAKD1N3aXRjaE1vZGVFcnJvchINCgVlcnJvchgBIAEoCSIkChJTd2l0Y2hNb2RlUmVqZWN0ZWQSDgoGcmVhc29uGAEgASgJImgKElN3aXRjaE1vZGVUb29sQ2FsbBImCgRhcmdzGAEgASgLMhguYWdlbnQudjEuU3dpdGNoTW9kZUFyZ3MSKgoGcmVzdWx0GAIgASgLMhouYWdlbnQudjEuU3dpdGNoTW9kZVJlc3VsdCJAChZTd2l0Y2hNb2RlUmVxdWVzdFF1ZXJ5EiYKBGFyZ3MYASABKAsyGC5hZ2VudC52MS5Td2l0Y2hNb2RlQXJncyKpAQoZU3dpdGNoTW9kZVJlcXVlc3RSZXNwb25zZRJACghhcHByb3ZlZBgBIAEoCzIsLmFnZW50LnYxLlN3aXRjaE1vZGVSZXF1ZXN0UmVzcG9uc2VfQXBwcm92ZWRIABJACghyZWplY3RlZBgCIAEoCzIsLmFnZW50LnYxLlN3aXRjaE1vZGVSZXF1ZXN0UmVzcG9uc2VfUmVqZWN0ZWRIAEIICgZyZXN1bHQiJAoiU3dpdGNoTW9kZVJlcXVlc3RSZXNwb25zZV9BcHByb3ZlZCI0CiJTd2l0Y2hNb2RlUmVxdWVzdFJlc3BvbnNlX1JlamVjdGVkEg4KBnJlYXNvbhgBIAEoCSJ1CghUb2RvSXRlbRIKCgJpZBgBIAEoCRIPCgdjb250ZW50GAIgASgJEg4KBnN0YXR1cxgDIAEoBRISCgpjcmVhdGVkX2F0GAQgASgDEhIKCnVwZGF0ZWRfYXQYBSABKAMSFAoMZGVwZW5kZW5jaWVzGAYgAygJImsKE1VwZGF0ZVRvZG9zVG9vbENhbGwSJwoEYXJncxgBIAEoCzIZLmFnZW50LnYxLlVwZGF0ZVRvZG9zQXJncxIrCgZyZXN1bHQYAiABKAsyGy5hZ2VudC52MS5VcGRhdGVUb2Rvc1Jlc3VsdCJDCg9VcGRhdGVUb2Rvc0FyZ3MSIQoFdG9kb3MYASADKAsyEi5hZ2VudC52MS5Ub2RvSXRlbRINCgVtZXJnZRgCIAEoCCJ7ChFVcGRhdGVUb2Rvc1Jlc3VsdBIvCgdzdWNjZXNzGAEgASgLMhwuYWdlbnQudjEuVXBkYXRlVG9kb3NTdWNjZXNzSAASKwoFZXJyb3IYAiABKAsyGi5hZ2VudC52MS5VcGRhdGVUb2Rvc0Vycm9ySABCCAoGcmVzdWx0Il8KElVwZGF0ZVRvZG9zU3VjY2VzcxIhCgV0b2RvcxgBIAMoCzISLmFnZW50LnYxLlRvZG9JdGVtEhMKC3RvdGFsX2NvdW50GAIgASgFEhEKCXdhc19tZXJnZRgDIAEoCCIhChBVcGRhdGVUb2Rvc0Vycm9yEg0KBWVycm9yGAEgASgJImUKEVJlYWRUb2Rvc1Rvb2xDYWxsEiUKBGFyZ3MYASABKAsyFy5hZ2VudC52MS5SZWFkVG9kb3NBcmdzEikKBnJlc3VsdBgCIAEoCzIZLmFnZW50LnYxLlJlYWRUb2Rvc1Jlc3VsdCI5Cg1SZWFkVG9kb3NBcmdzEhUKDXN0YXR1c19maWx0ZXIYASADKAUSEQoJaWRfZmlsdGVyGAIgAygJInUKD1JlYWRUb2Rvc1Jlc3VsdBItCgdzdWNjZXNzGAEgASgLMhouYWdlbnQudjEuUmVhZFRvZG9zU3VjY2Vzc0gAEikKBWVycm9yGAIgASgLMhguYWdlbnQudjEuUmVhZFRvZG9zRXJyb3JIAEIICgZyZXN1bHQiSgoQUmVhZFRvZG9zU3VjY2VzcxIhCgV0b2RvcxgBIAMoCzISLmFnZW50LnYxLlRvZG9JdGVtEhMKC3RvdGFsX2NvdW50GAIgASgFIh8KDlJlYWRUb2Rvc0Vycm9yEg0KBWVycm9yGAEgASgJIksKBVJhbmdlEiEKBXN0YXJ0GAEgASgLMhIuYWdlbnQudjEuUG9zaXRpb24SHwoDZW5kGAIgASgLMhIuYWdlbnQudjEuUG9zaXRpb24iKAoIUG9zaXRpb24SDAoEbGluZRgBIAEoDRIOCgZjb2x1bW4YAiABKA0iGAoFRXJyb3ISDwoHbWVzc2FnZRgBIAEoCSI6Cg1XZWJTZWFyY2hBcmdzEhMKC3NlYXJjaF90ZXJtGAEgASgJEhQKDHRvb2xfY2FsbF9pZBgCIAEoCSKmAQoPV2ViU2VhcmNoUmVzdWx0Ei0KB3N1Y2Nlc3MYASABKAsyGi5hZ2VudC52MS5XZWJTZWFyY2hTdWNjZXNzSAASKQoFZXJyb3IYAiABKAsyGC5hZ2VudC52MS5XZWJTZWFyY2hFcnJvckgAEi8KCHJlamVjdGVkGAMgASgLMhsuYWdlbnQudjEuV2ViU2VhcmNoUmVqZWN0ZWRIAEIICgZyZXN1bHQiRAoQV2ViU2VhcmNoU3VjY2VzcxIwCgpyZWZlcmVuY2VzGAEgAygLMhwuYWdlbnQudjEuV2ViU2VhcmNoUmVmZXJlbmNlIh8KDldlYlNlYXJjaEVycm9yEg0KBWVycm9yGAEgASgJIiMKEVdlYlNlYXJjaFJlamVjdGVkEg4KBnJlYXNvbhgBIAEoCSI/ChJXZWJTZWFyY2hSZWZlcmVuY2USDQoFdGl0bGUYASABKAkSCwoDdXJsGAIgASgJEg0KBWNodW5rGAMgASgJImUKEVdlYlNlYXJjaFRvb2xDYWxsEiUKBGFyZ3MYASABKAsyFy5hZ2VudC52MS5XZWJTZWFyY2hBcmdzEikKBnJlc3VsdBgCIAEoCzIZLmFnZW50LnYxLldlYlNlYXJjaFJlc3VsdCI+ChVXZWJTZWFyY2hSZXF1ZXN0UXVlcnkSJQoEYXJncxgBIAEoCzIXLmFnZW50LnYxLldlYlNlYXJjaEFyZ3MipgEKGFdlYlNlYXJjaFJlcXVlc3RSZXNwb25zZRI/CghhcHByb3ZlZBgBIAEoCzIrLmFnZW50LnYxLldlYlNlYXJjaFJlcXVlc3RSZXNwb25zZV9BcHByb3ZlZEgAEj8KCHJlamVjdGVkGAIgASgLMisuYWdlbnQudjEuV2ViU2VhcmNoUmVxdWVzdFJlc3BvbnNlX1JlamVjdGVkSABCCAoGcmVzdWx0IiMKIVdlYlNlYXJjaFJlcXVlc3RSZXNwb25zZV9BcHByb3ZlZCIzCiFXZWJTZWFyY2hSZXF1ZXN0UmVzcG9uc2VfUmVqZWN0ZWQSDgoGcmVhc29uGAEgASgJIq0BCglXcml0ZUFyZ3MSDAoEcGF0aBgBIAEoCRIRCglmaWxlX3RleHQYAiABKAkSFAoMdG9vbF9jYWxsX2lkGAMgASgJEicKH3JldHVybl9maWxlX2NvbnRlbnRfYWZ0ZXJfd3JpdGUYBCABKAgSEgoKZmlsZV9ieXRlcxgFIAEoDBIaCg1lbmNvZGluZ19oaW50GAYgASgJSACIAQFCEAoOX2VuY29kaW5nX2hpbnQigAIKC1dyaXRlUmVzdWx0EikKB3N1Y2Nlc3MYASABKAsyFi5hZ2VudC52MS5Xcml0ZVN1Y2Nlc3NIABI8ChFwZXJtaXNzaW9uX2RlbmllZBgDIAEoCzIfLmFnZW50LnYxLldyaXRlUGVybWlzc2lvbkRlbmllZEgAEioKCG5vX3NwYWNlGAQgASgLMhYuYWdlbnQudjEuV3JpdGVOb1NwYWNlSAASJQoFZXJyb3IYBSABKAsyFC5hZ2VudC52MS5Xcml0ZUVycm9ySAASKwoIcmVqZWN0ZWQYBiABKAsyFy5hZ2VudC52MS5Xcml0ZVJlamVjdGVkSABCCAoGcmVzdWx0IooBCgxXcml0ZVN1Y2Nlc3MSDAoEcGF0aBgBIAEoCRIVCg1saW5lc19jcmVhdGVkGAIgASgFEhEKCWZpbGVfc2l6ZRgDIAEoBRIlChhmaWxlX2NvbnRlbnRfYWZ0ZXJfd3JpdGUYBCABKAlIAIgBAUIbChlfZmlsZV9jb250ZW50X2FmdGVyX3dyaXRlIm8KFVdyaXRlUGVybWlzc2lvbkRlbmllZBIMCgRwYXRoGAEgASgJEhEKCWRpcmVjdG9yeRgCIAEoCRIRCglvcGVyYXRpb24YAyABKAkSDQoFZXJyb3IYBCABKAkSEwoLaXNfcmVhZG9ubHkYBSABKAgiHAoMV3JpdGVOb1NwYWNlEgwKBHBhdGgYASABKAkiKQoKV3JpdGVFcnJvchIMCgRwYXRoGAEgASgJEg0KBWVycm9yGAIgASgJIi0KDVdyaXRlUmVqZWN0ZWQSDAoEcGF0aBgBIAEoCRIOCgZyZWFzb24YAiABKAkigwEKF0Jvb3RzdHJhcFN0YXRzaWdSZXF1ZXN0Eh4KEWlnbm9yZV9kZXZfc3RhdHVzGAEgASgISACIAQESHQoQb3BlcmF0aW5nX3N5c3RlbRgCIAEoBUgBiAEBQhQKEl9pZ25vcmVfZGV2X3N0YXR1c0ITChFfb3BlcmF0aW5nX3N5c3RlbSIOCgxQaW5nUmVzcG9uc2UitwEKC0V4ZWNSZXF1ZXN0Eg8KB2NvbW1hbmQYASABKAkSEAoDY3dkGAIgASgJSACIAQESDAoEYXJncxgDIAMoCRI7CgtlbnZpcm9ubWVudBgEIAMoCzImLmFnZW50LnYxLkV4ZWNSZXF1ZXN0LkVudmlyb25tZW50RW50cnkaMgoQRW52aXJvbm1lbnRFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBQgYKBF9jd2QioAEKDEV4ZWNSZXNwb25zZRItCgxzdGRvdXRfZXZlbnQYASABKAsyFS5hZ2VudC52MS5TdGRvdXRFdmVudEgAEi0KDHN0ZGVycl9ldmVudBgCIAEoCzIVLmFnZW50LnYxLlN0ZGVyckV2ZW50SAASKQoKZXhpdF9ldmVudBgDIAEoCzITLmFnZW50LnYxLkV4aXRFdmVudEgAQgcKBWV2ZW50IhsKC1N0ZG91dEV2ZW50EgwKBGRhdGEYASABKAkiGwoLU3RkZXJyRXZlbnQSDAoEZGF0YRgBIAEoCSIeCglFeGl0RXZlbnQSEQoJZXhpdF9jb2RlGAEgASgFIiMKE1JlYWRUZXh0RmlsZVJlcXVlc3QSDAoEcGF0aBgBIAEoCSInChRSZWFkVGV4dEZpbGVSZXNwb25zZRIPCgdjb250ZW50GAEgASgJIjUKFFdyaXRlVGV4dEZpbGVSZXF1ZXN0EgwKBHBhdGgYASABKAkSDwoHY29udGVudBgCIAEoCSIXChVXcml0ZVRleHRGaWxlUmVzcG9uc2UiJQoVUmVhZEJpbmFyeUZpbGVSZXF1ZXN0EgwKBHBhdGgYASABKAkiKQoWUmVhZEJpbmFyeUZpbGVSZXNwb25zZRIPCgdjb250ZW50GAEgASgMIjcKFldyaXRlQmluYXJ5RmlsZVJlcXVlc3QSDAoEcGF0aBgBIAEoCRIPCgdjb250ZW50GAIgASgMIhkKF1dyaXRlQmluYXJ5RmlsZVJlc3BvbnNlIkUKHkdldFdvcmtzcGFjZUNoYW5nZXNIYXNoUmVxdWVzdBIRCglyb290X3BhdGgYASABKAkSEAoIYmFzZV9yZWYYAiABKAkiLwofR2V0V29ya3NwYWNlQ2hhbmdlc0hhc2hSZXNwb25zZRIMCgRoYXNoGAEgASgJIlAKH1JlZnJlc2hHaXRodWJBY2Nlc3NUb2tlblJlcXVlc3QSGwoTZ2l0aHViX2FjY2Vzc190b2tlbhgBIAEoCRIQCghob3N0bmFtZRgCIAEoCSIiCiBSZWZyZXNoR2l0aHViQWNjZXNzVG9rZW5SZXNwb25zZSJXCh1XYXJtUmVtb3RlQWNjZXNzU2VydmVyUmVxdWVzdBIOCgZjb21taXQYASABKAkSDAoEcG9ydBgCIAEoBRIYChBjb25uZWN0aW9uX3Rva2VuGAMgASgJIiAKHldhcm1SZW1vdGVBY2Nlc3NTZXJ2ZXJSZXNwb25zZSIWChRMaXN0QXJ0aWZhY3RzUmVxdWVzdCKKAgoWQXJ0aWZhY3RVcGxvYWRNZXRhZGF0YRIVCg1hYnNvbHV0ZV9wYXRoGAEgASgJEhIKCnNpemVfYnl0ZXMYAiABKAQSGgoSdXBkYXRlZF9hdF91bml4X21zGAMgASgDEg4KBnN0YXR1cxgEIAEoBRIWCg5ieXRlc191cGxvYWRlZBgFIAEoBBISCgpsYXN0X2Vycm9yGAYgASgJEhcKD3VwbG9hZF9hdHRlbXB0cxgHIAEoDRIfChdsYXN0X3N0YXJ0ZWRfYXRfdW5peF9tcxgIIAEoAxIgChhsYXN0X2ZpbmlzaGVkX2F0X3VuaXhfbXMYCSABKAMSEQoJdXBsb2FkX2lkGAogASgJIkwKFUxpc3RBcnRpZmFjdHNSZXNwb25zZRIzCglhcnRpZmFjdHMYASADKAsyIC5hZ2VudC52MS5BcnRpZmFjdFVwbG9hZE1ldGFkYXRhIk4KFlVwbG9hZEFydGlmYWN0c1JlcXVlc3QSNAoHdXBsb2FkcxgBIAMoCzIjLmFnZW50LnYxLkFydGlmYWN0VXBsb2FkSW5zdHJ1Y3Rpb24i1wIKGUFydGlmYWN0VXBsb2FkSW5zdHJ1Y3Rpb24SFQoNYWJzb2x1dGVfcGF0aBgBIAEoCRISCgp1cGxvYWRfdXJsGAIgASgJEg4KBm1ldGhvZBgDIAEoCRJBCgdoZWFkZXJzGAQgAygLMjAuYWdlbnQudjEuQXJ0aWZhY3RVcGxvYWRJbnN0cnVjdGlvbi5IZWFkZXJzRW50cnkSGQoMY29udGVudF90eXBlGAUgASgJSACIAQESHQoQc2xhY2tfdXBsb2FkX3VybBgGIAEoCUgBiAEBEhoKDXNsYWNrX2ZpbGVfaWQYByABKAlIAogBARouCgxIZWFkZXJzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4AUIPCg1fY29udGVudF90eXBlQhMKEV9zbGFja191cGxvYWRfdXJsQhAKDl9zbGFja19maWxlX2lkIoQBChxBcnRpZmFjdFVwbG9hZERpc3BhdGNoUmVzdWx0EhUKDWFic29sdXRlX3BhdGgYASABKAkSDgoGc3RhdHVzGAIgASgFEg8KB21lc3NhZ2UYAyABKAkSGgoNc2xhY2tfZmlsZV9pZBgEIAEoCUgAiAEBQhAKDl9zbGFja19maWxlX2lkIlIKF1VwbG9hZEFydGlmYWN0c1Jlc3BvbnNlEjcKB3Jlc3VsdHMYASADKAsyJi5hZ2VudC52MS5BcnRpZmFjdFVwbG9hZERpc3BhdGNoUmVzdWx0IhwKGkdldE1jcFJlZnJlc2hUb2tlbnNSZXF1ZXN0IqUBChtHZXRNY3BSZWZyZXNoVG9rZW5zUmVzcG9uc2USUAoOcmVmcmVzaF90b2tlbnMYASADKAsyOC5hZ2VudC52MS5HZXRNY3BSZWZyZXNoVG9rZW5zUmVzcG9uc2UuUmVmcmVzaFRva2Vuc0VudHJ5GjQKElJlZnJlc2hUb2tlbnNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIqMBCiFVcGRhdGVFbnZpcm9ubWVudFZhcmlhYmxlc1JlcXVlc3QSQQoDZW52GAEgAygLMjQuYWdlbnQudjEuVXBkYXRlRW52aXJvbm1lbnRWYXJpYWJsZXNSZXF1ZXN0LkVudkVudHJ5Eg8KB3JlcGxhY2UYAiABKAgaKgoIRW52RW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASJGCiJVcGRhdGVFbnZpcm9ubWVudFZhcmlhYmxlc1Jlc3BvbnNlEg8KB2FwcGxpZWQYASABKA0SDwoHcmVtb3ZlZBgCIAEoDSKDAQoSTWNwT0F1dGhTdG9yZWREYXRhEhUKDXJlZnJlc2hfdG9rZW4YASABKAkSEQoJY2xpZW50X2lkGAIgASgJEhoKDWNsaWVudF9zZWNyZXQYAyABKAlIAIgBARIVCg1yZWRpcmVjdF91cmlzGAQgAygJQhAKDl9jbGllbnRfc2VjcmV0Ik4KBUZyYW1lEgoKAmlkGAEgASgJEg4KBm1ldGhvZBgCIAEoCRIMCgRkYXRhGAMgASgMEgwKBGtpbmQYBCABKAUSDQoFZXJyb3IYBSABKAkiBwoFRW1wdHkiIwoNQmlkaVJlcXVlc3RJZBISCgpyZXF1ZXN0X2lkGAEgASgJIoECCgxQaVRydW5jYXRpb24SEQoJdHJ1bmNhdGVkGAEgASgIEhQKDHRydW5jYXRlZF9ieRgCIAEoCRITCgt0b3RhbF9saW5lcxgDIAEoDRIUCgxvdXRwdXRfbGluZXMYBCABKA0SFAoMb3V0cHV0X2J5dGVzGAUgASgNEhYKCW1heF9saW5lcxgGIAEoDUgAiAEBEhYKCW1heF9ieXRlcxgHIAEoDUgBiAEBEiAKGGZpcnN0X2xpbmVfZXhjZWVkc19saW1pdBgIIAEoCBIZChFsYXN0X2xpbmVfcGFydGlhbBgJIAEoCEIMCgpfbWF4X2xpbmVzQgwKCl9tYXhfYnl0ZXMiNwoRUGlFZGl0UmVwbGFjZW1lbnQSEAoIb2xkX3RleHQYASABKAkSEAoIbmV3X3RleHQYAiABKAkiXAoOUGlSZWFkRXhlY0FyZ3MSDAoEcGF0aBgBIAEoCRITCgZvZmZzZXQYAiABKAVIAIgBARISCgVsaW1pdBgDIAEoBUgBiAEBQgkKB19vZmZzZXRCCAoGX2xpbWl0ImMKEVBpUmVhZEV4ZWNTdWNjZXNzEg4KBm91dHB1dBgBIAEoCRIvCgp0cnVuY2F0aW9uGAIgASgLMhYuYWdlbnQudjEuUGlUcnVuY2F0aW9uSACIAQFCDQoLX3RydW5jYXRpb24iIAoPUGlSZWFkRXhlY0Vycm9yEg0KBWVycm9yGAEgASgJIngKEFBpUmVhZEV4ZWNSZXN1bHQSLgoHc3VjY2VzcxgBIAEoCzIbLmFnZW50LnYxLlBpUmVhZEV4ZWNTdWNjZXNzSAASKgoFZXJyb3IYAiABKAsyGS5hZ2VudC52MS5QaVJlYWRFeGVjRXJyb3JIAEIICgZyZXN1bHQiQwoOUGlCYXNoRXhlY0FyZ3MSDwoHY29tbWFuZBgBIAEoCRIUCgd0aW1lb3V0GAIgASgBSACIAQFCCgoIX3RpbWVvdXQilwEKEVBpQmFzaEV4ZWNTdWNjZXNzEg4KBm91dHB1dBgBIAEoCRIvCgp0cnVuY2F0aW9uGAIgASgLMhYuYWdlbnQudjEuUGlUcnVuY2F0aW9uSACIAQESHQoQZnVsbF9vdXRwdXRfcGF0aBgDIAEoCUgBiAEBQg0KC190cnVuY2F0aW9uQhMKEV9mdWxsX291dHB1dF9wYXRoIpQBCg9QaUJhc2hFeGVjRXJyb3ISDQoFZXJyb3IYASABKAkSLwoKdHJ1bmNhdGlvbhgCIAEoCzIWLmFnZW50LnYxLlBpVHJ1bmNhdGlvbkgAiAEBEh0KEGZ1bGxfb3V0cHV0X3BhdGgYAyABKAlIAYgBAUINCgtfdHJ1bmNhdGlvbkITChFfZnVsbF9vdXRwdXRfcGF0aCJ4ChBQaUJhc2hFeGVjUmVzdWx0Ei4KB3N1Y2Nlc3MYASABKAsyGy5hZ2VudC52MS5QaUJhc2hFeGVjU3VjY2Vzc0gAEioKBWVycm9yGAIgASgLMhkuYWdlbnQudjEuUGlCYXNoRXhlY0Vycm9ySABCCAoGcmVzdWx0IkoKDlBpRWRpdEV4ZWNBcmdzEgwKBHBhdGgYASABKAkSKgoFZWRpdHMYAiADKAsyGy5hZ2VudC52MS5QaUVkaXRSZXBsYWNlbWVudCJ4ChFQaUVkaXRFeGVjU3VjY2VzcxIOCgZvdXRwdXQYASABKAkSDAoEZGlmZhgCIAEoCRINCgVwYXRjaBgDIAEoCRIfChJmaXJzdF9jaGFuZ2VkX2xpbmUYBCABKA1IAIgBAUIVChNfZmlyc3RfY2hhbmdlZF9saW5lIiAKD1BpRWRpdEV4ZWNFcnJvchINCgVlcnJvchgBIAEoCSIkChJQaUVkaXRFeGVjUmVqZWN0ZWQSDgoGcmVhc29uGAEgASgJIqoBChBQaUVkaXRFeGVjUmVzdWx0Ei4KB3N1Y2Nlc3MYASABKAsyGy5hZ2VudC52MS5QaUVkaXRFeGVjU3VjY2Vzc0gAEioKBWVycm9yGAIgASgLMhkuYWdlbnQudjEuUGlFZGl0RXhlY0Vycm9ySAASMAoIcmVqZWN0ZWQYAyABKAsyHC5hZ2VudC52MS5QaUVkaXRFeGVjUmVqZWN0ZWRIAEIICgZyZXN1bHQiMAoPUGlXcml0ZUV4ZWNBcmdzEgwKBHBhdGgYASABKAkSDwoHY29udGVudBgCIAEoCSIkChJQaVdyaXRlRXhlY1N1Y2Nlc3MSDgoGb3V0cHV0GAEgASgJIiEKEFBpV3JpdGVFeGVjRXJyb3ISDQoFZXJyb3IYASABKAkiJQoTUGlXcml0ZUV4ZWNSZWplY3RlZBIOCgZyZWFzb24YASABKAkirgEKEVBpV3JpdGVFeGVjUmVzdWx0Ei8KB3N1Y2Nlc3MYASABKAsyHC5hZ2VudC52MS5QaVdyaXRlRXhlY1N1Y2Nlc3NIABIrCgVlcnJvchgCIAEoCzIaLmFnZW50LnYxLlBpV3JpdGVFeGVjRXJyb3JIABIxCghyZWplY3RlZBgDIAEoCzIdLmFnZW50LnYxLlBpV3JpdGVFeGVjUmVqZWN0ZWRIAEIICgZyZXN1bHQi5QEKDlBpR3JlcEV4ZWNBcmdzEg8KB3BhdHRlcm4YASABKAkSEQoEcGF0aBgCIAEoCUgAiAEBEhEKBGdsb2IYAyABKAlIAYgBARIYCgtpZ25vcmVfY2FzZRgEIAEoCEgCiAEBEhQKB2xpdGVyYWwYBSABKAhIA4gBARIUCgdjb250ZXh0GAYgASgFSASIAQESEgoFbGltaXQYByABKAVIBYgBAUIHCgVfcGF0aEIHCgVfZ2xvYkIOCgxfaWdub3JlX2Nhc2VCCgoIX2xpdGVyYWxCCgoIX2NvbnRleHRCCAoGX2xpbWl0IrYBChFQaUdyZXBFeGVjU3VjY2VzcxIOCgZvdXRwdXQYASABKAkSLwoKdHJ1bmNhdGlvbhgCIAEoCzIWLmFnZW50LnYxLlBpVHJ1bmNhdGlvbkgAiAEBEiAKE21hdGNoX2xpbWl0X3JlYWNoZWQYAyABKA1IAYgBARIXCg9saW5lc190cnVuY2F0ZWQYBCABKAhCDQoLX3RydW5jYXRpb25CFgoUX21hdGNoX2xpbWl0X3JlYWNoZWQiIAoPUGlHcmVwRXhlY0Vycm9yEg0KBWVycm9yGAEgASgJIngKEFBpR3JlcEV4ZWNSZXN1bHQSLgoHc3VjY2VzcxgBIAEoCzIbLmFnZW50LnYxLlBpR3JlcEV4ZWNTdWNjZXNzSAASKgoFZXJyb3IYAiABKAsyGS5hZ2VudC52MS5QaUdyZXBFeGVjRXJyb3JIAEIICgZyZXN1bHQiWwoOUGlGaW5kRXhlY0FyZ3MSDwoHcGF0dGVybhgBIAEoCRIRCgRwYXRoGAIgASgJSACIAQESEgoFbGltaXQYAyABKAVIAYgBAUIHCgVfcGF0aEIICgZfbGltaXQinwEKEVBpRmluZEV4ZWNTdWNjZXNzEg4KBm91dHB1dBgBIAEoCRIvCgp0cnVuY2F0aW9uGAIgASgLMhYuYWdlbnQudjEuUGlUcnVuY2F0aW9uSACIAQESIQoUcmVzdWx0X2xpbWl0X3JlYWNoZWQYAyABKA1IAYgBAUINCgtfdHJ1bmNhdGlvbkIXChVfcmVzdWx0X2xpbWl0X3JlYWNoZWQiIAoPUGlGaW5kRXhlY0Vycm9yEg0KBWVycm9yGAEgASgJIngKEFBpRmluZEV4ZWNSZXN1bHQSLgoHc3VjY2VzcxgBIAEoCzIbLmFnZW50LnYxLlBpRmluZEV4ZWNTdWNjZXNzSAASKgoFZXJyb3IYAiABKAsyGS5hZ2VudC52MS5QaUZpbmRFeGVjRXJyb3JIAEIICgZyZXN1bHQiSAoMUGlMc0V4ZWNBcmdzEhEKBHBhdGgYASABKAlIAIgBARISCgVsaW1pdBgCIAEoBUgBiAEBQgcKBV9wYXRoQggKBl9saW1pdCKbAQoPUGlMc0V4ZWNTdWNjZXNzEg4KBm91dHB1dBgBIAEoCRIvCgp0cnVuY2F0aW9uGAIgASgLMhYuYWdlbnQudjEuUGlUcnVuY2F0aW9uSACIAQESIAoTZW50cnlfbGltaXRfcmVhY2hlZBgDIAEoDUgBiAEBQg0KC190cnVuY2F0aW9uQhYKFF9lbnRyeV9saW1pdF9yZWFjaGVkIh4KDVBpTHNFeGVjRXJyb3ISDQoFZXJyb3IYASABKAkicgoOUGlMc0V4ZWNSZXN1bHQSLAoHc3VjY2VzcxgBIAEoCzIZLmFnZW50LnYxLlBpTHNFeGVjU3VjY2Vzc0gAEigKBWVycm9yGAIgASgLMhcuYWdlbnQudjEuUGlMc0V4ZWNFcnJvckgAQggKBnJlc3VsdCI8ChFNY3BTZXJ2ZXJOb3RGb3VuZBIMCgRuYW1lGAEgASgJEhkKEWF2YWlsYWJsZV9zZXJ2ZXJzGAIgAygJIg0KC01jcEFwcHJvdmVkIkEKEE1jcFN0YXRlRXhlY0FyZ3MSGgoSc2VydmVyX2lkZW50aWZpZXJzGAEgAygJEhEKCWtpY2tfb25seRgCIAEoCCKHAgoOTWNwU3RhdGVTZXJ2ZXISEwoLc2VydmVyX25hbWUYASABKAkSGQoRc2VydmVyX2lkZW50aWZpZXIYAiABKAkSEwoGcGx1Z2luGAMgASgJSACIAQESGAoLbWFya2V0cGxhY2UYBCABKAlIAYgBARIqCgV0b29scxgFIAMoCzIbLmFnZW50LnYxLk1jcFRvb2xEZWZpbml0aW9uEi8KDGluc3RydWN0aW9ucxgGIAMoCzIZLmFnZW50LnYxLk1jcEluc3RydWN0aW9ucxITCgZzdGF0dXMYByABKAlIAogBAUIJCgdfcGx1Z2luQg4KDF9tYXJrZXRwbGFjZUIJCgdfc3RhdHVzIjwKD01jcFN0YXRlU3VjY2VzcxIpCgdzZXJ2ZXJzGAEgAygLMhguYWdlbnQudjEuTWNwU3RhdGVTZXJ2ZXIiHgoNTWNwU3RhdGVFcnJvchINCgVlcnJvchgBIAEoCSIiChBNY3BTdGF0ZVJlamVjdGVkEg4KBnJlYXNvbhgBIAEoCSKmAQoSTWNwU3RhdGVFeGVjUmVzdWx0EiwKB3N1Y2Nlc3MYASABKAsyGS5hZ2VudC52MS5NY3BTdGF0ZVN1Y2Nlc3NIABIoCgVlcnJvchgCIAEoCzIXLmFnZW50LnYxLk1jcFN0YXRlRXJyb3JIABIuCghyZWplY3RlZBgDIAEoCzIaLmFnZW50LnYxLk1jcFN0YXRlUmVqZWN0ZWRIAEIICgZyZXN1bHQirQEKKUNvbW1hbmRDbGFzc2lmaWVyUmVzdWx0X0NsYXNzaWZpZWRDb21tYW5kEgwKBG5hbWUYASABKAkSEQoJYXJndW1lbnRzGAIgAygJEiYKGXN1Z2dlc3RlZF9hbGxvd2xpc3RfZW50cnkYAyABKAlIAIgBARIZChFzdWJjb21tYW5kX3Rva2VucxgEIAMoCUIcChpfc3VnZ2VzdGVkX2FsbG93bGlzdF9lbnRyeSLXAQoXQ29tbWFuZENsYXNzaWZpZXJSZXN1bHQSRQoIY29tbWFuZHMYASADKAsyMy5hZ2VudC52MS5Db21tYW5kQ2xhc3NpZmllclJlc3VsdF9DbGFzc2lmaWVkQ29tbWFuZBJWChZzdWdnZXN0ZWRfc2FuZGJveF9tb2RlGAIgASgOMjYuYWdlbnQudjEuQ29tbWFuZENsYXNzaWZpZXJSZXN1bHRfU3VnZ2VzdGVkU2FuZGJveE1vZGUSHQoVY2xhc3NpZmljYXRpb25fZmFpbGVkGAMgASgIIpMBCiJTaGVsbENvbW1hbmRQYXJzaW5nUmVzdWx0X1JlZGlyZWN0EhAKCG9wZXJhdG9yGAEgASgJEhcKD2Rlc3RpbmF0aW9uX2ZkcxgCIAMoDRIYChB0YXJnZXRfbm9kZV90eXBlGAMgASgJEhgKC3RhcmdldF90ZXh0GAQgASgJSACIAQFCDgoMX3RhcmdldF90ZXh0IooCChpTaGVsbEFsbG93bGlzdFByZWNoZWNrQXJncxIPCgdjb21tYW5kGAEgASgJEhkKEXdvcmtpbmdfZGlyZWN0b3J5GAIgASgJEjsKDnBhcnNpbmdfcmVzdWx0GAMgASgLMiMuYWdlbnQudjEuU2hlbGxDb21tYW5kUGFyc2luZ1Jlc3VsdBJBChFjbGFzc2lmaWVyX3Jlc3VsdBgEIAEoCzIhLmFnZW50LnYxLkNvbW1hbmRDbGFzc2lmaWVyUmVzdWx0SACIAQESGQoMdG9vbF9jYWxsX2lkGAUgASgJSAGIAQFCFAoSX2NsYXNzaWZpZXJfcmVzdWx0Qg8KDV90b29sX2NhbGxfaWQiMwocU2hlbGxBbGxvd2xpc3RQcmVjaGVja1Jlc3VsdBITCgthbGxvd2xpc3RlZBgBIAEoCCJ2ChhNY3BBbGxvd2xpc3RQcmVjaGVja0FyZ3MSGwoTcHJvdmlkZXJfaWRlbnRpZmllchgBIAEoCRIRCgl0b29sX25hbWUYAiABKAkSGQoMdG9vbF9jYWxsX2lkGAMgASgJSACIAQFCDwoNX3Rvb2xfY2FsbF9pZCIxChpNY3BBbGxvd2xpc3RQcmVjaGVja1Jlc3VsdBITCgthbGxvd2xpc3RlZBgBIAEoCCJYCh1XZWJGZXRjaEFsbG93bGlzdFByZWNoZWNrQXJncxILCgN1cmwYASABKAkSGQoMdG9vbF9jYWxsX2lkGAIgASgJSACIAQFCDwoNX3Rvb2xfY2FsbF9pZCI2Ch9XZWJGZXRjaEFsbG93bGlzdFByZWNoZWNrUmVzdWx0EhMKC2FsbG93bGlzdGVkGAEgASgIIjcKEVNtYXJ0TW9kZUFwcHJvdmFsEhIKCnJlcXVlc3RfaWQYASABKAkSDgoGcmVhc29uGAIgASgJIpwBCh1TaGVsbE91dHB1dE5vdGlmaWNhdGlvbkNvbmZpZxIPCgdwYXR0ZXJuGAEgASgJEg4KBnJlYXNvbhgCIAEoCRIVCghkZWJvdW5jZRgDIAEoAUgAiAEBEh8KEm5vdGlmaWNhdGlvbl9saW1pdBgEIAEoBUgBiAEBQgsKCV9kZWJvdW5jZUIVChNfbm90aWZpY2F0aW9uX2xpbWl0InkKHFNoZWxsSG9va0FwcHJvdmFsUmVxdWlyZW1lbnQSOQoEa2luZBgBIAEoDjIrLmFnZW50LnYxLlNoZWxsSG9va0FwcHJvdmFsUmVxdWlyZW1lbnRfS2luZBITCgZyZWFzb24YAiABKAlIAIgBAUIJCgdfcmVhc29uIjAKGEZvcmNlQmFja2dyb3VuZFNoZWxsQXJncxIUCgx0b29sX2NhbGxfaWQYASABKAkilQEKGkZvcmNlQmFja2dyb3VuZFNoZWxsUmVzdWx0EjQKBnN0YXR1cxgBIAEoDjIkLmFnZW50LnYxLkZvcmNlQmFja2dyb3VuZFNoZWxsU3RhdHVzEjAKDHNoZWxsX3Jlc3VsdBgCIAEoCzIVLmFnZW50LnYxLlNoZWxsUmVzdWx0SACIAQFCDwoNX3NoZWxsX3Jlc3VsdCJBChVIb29rQWRkaXRpb25hbENvbnRleHQSFwoPaG9va19ldmVudF9uYW1lGAEgASgJEg8KB2NvbnRlbnQYAiABKAkiWwoWU2hlbGxTdHJlYW1Ib29rQ29udGV4dBJBChhob29rX2FkZGl0aW9uYWxfY29udGV4dHMYASADKAsyHy5hZ2VudC52MS5Ib29rQWRkaXRpb25hbENvbnRleHQiSwoMU3ViYWdlbnRBcmdzEhQKDHRvb2xfY2FsbF9pZBgBIAEoCRIVCg1zdWJhZ2VudF90eXBlGAIgASgJEg4KBnByb21wdBgEIAEoCSLbAQoPU3ViYWdlbnRTdWNjZXNzEhAKCGFnZW50X2lkGAEgASgJEhoKDWZpbmFsX21lc3NhZ2UYAiABKAlIAIgBARIXCg90b29sX2NhbGxfY291bnQYAyABKAUSPQoRYmFja2dyb3VuZF9yZWFzb24YBCABKA4yIi5hZ2VudC52MS5TdWJhZ2VudEJhY2tncm91bmRSZWFzb24SHAoPdHJhbnNjcmlwdF9wYXRoGAUgASgJSAGIAQFCEAoOX2ZpbmFsX21lc3NhZ2VCEgoQX3RyYW5zY3JpcHRfcGF0aCJCCg1TdWJhZ2VudEVycm9yEhUKCGFnZW50X2lkGAEgASgJSACIAQESDQoFZXJyb3IYAiABKAlCCwoJX2FnZW50X2lkInIKDlN1YmFnZW50UmVzdWx0EiwKB3N1Y2Nlc3MYASABKAsyGS5hZ2VudC52MS5TdWJhZ2VudFN1Y2Nlc3NIABIoCgVlcnJvchgCIAEoCzIXLmFnZW50LnYxLlN1YmFnZW50RXJyb3JIAEIICgZyZXN1bHQiOQoRU3ViYWdlbnRBd2FpdEFyZ3MSEAoIYWdlbnRfaWQYASABKAkSEgoKdGltZW91dF9tcxgCIAEoDSKiAQoVU3ViYWdlbnRBd2FpdENvbXBsZXRlEhAKCGFnZW50X2lkGAEgASgJEhwKD3RyYW5zY3JpcHRfcGF0aBgCIAEoCUgAiAEBEhcKD3Rvb2xfY2FsbF9jb3VudBgDIAEoBRIaCg1maW5hbF9tZXNzYWdlGAQgASgJSAGIAQFCEgoQX3RyYW5zY3JpcHRfcGF0aEIQCg5fZmluYWxfbWVzc2FnZSJfChlTdWJhZ2VudEF3YWl0U3RpbGxSdW5uaW5nEhAKCGFnZW50X2lkGAEgASgJEhwKD3RyYW5zY3JpcHRfcGF0aBgCIAEoCUgAiAEBQhIKEF90cmFuc2NyaXB0X3BhdGgiKQoVU3ViYWdlbnRBd2FpdE5vdEZvdW5kEhAKCGFnZW50X2lkGAEgASgJIkcKElN1YmFnZW50QXdhaXRFcnJvchIVCghhZ2VudF9pZBgBIAEoCUgAiAEBEg0KBWVycm9yGAIgASgJQgsKCV9hZ2VudF9pZCL3AQoTU3ViYWdlbnRBd2FpdFJlc3VsdBIzCghjb21wbGV0ZRgBIAEoCzIfLmFnZW50LnYxLlN1YmFnZW50QXdhaXRDb21wbGV0ZUgAEjwKDXN0aWxsX3J1bm5pbmcYAiABKAsyIy5hZ2VudC52MS5TdWJhZ2VudEF3YWl0U3RpbGxSdW5uaW5nSAASNAoJbm90X2ZvdW5kGAMgASgLMh8uYWdlbnQudjEuU3ViYWdlbnRBd2FpdE5vdEZvdW5kSAASLQoFZXJyb3IYBCABKAsyHC5hZ2VudC52MS5TdWJhZ2VudEF3YWl0RXJyb3JIAEIICgZyZXN1bHQiMwobRm9yY2VCYWNrZ3JvdW5kU3ViYWdlbnRBcmdzEhQKDHRvb2xfY2FsbF9pZBgBIAEoCSJYCh1Gb3JjZUJhY2tncm91bmRTdWJhZ2VudFJlc3VsdBI3CgZzdGF0dXMYASABKA4yJy5hZ2VudC52MS5Gb3JjZUJhY2tncm91bmRTdWJhZ2VudFN0YXR1cyIYChZQcmVDb21wYWN0UmVxdWVzdFF1ZXJ5IhsKGVN1YmFnZW50U3RhcnRSZXF1ZXN0UXVlcnkiGgoYU3ViYWdlbnRTdG9wUmVxdWVzdFF1ZXJ5IhgKFlByZVRvb2xVc2VSZXF1ZXN0UXVlcnkiGQoXUG9zdFRvb2xVc2VSZXF1ZXN0UXVlcnkiIAoeUG9zdFRvb2xVc2VGYWlsdXJlUmVxdWVzdFF1ZXJ5IiAKHkJlZm9yZVN1Ym1pdFByb21wdFJlcXVlc3RRdWVyeSIgCh5BZnRlckFnZW50UmVzcG9uc2VSZXF1ZXN0UXVlcnkiHwodQWZ0ZXJBZ2VudFRob3VnaHRSZXF1ZXN0UXVlcnkiEgoQU3RvcFJlcXVlc3RRdWVyeSJHChlQcmVDb21wYWN0UmVxdWVzdFJlc3BvbnNlEhkKDHVzZXJfbWVzc2FnZRgBIAEoCUgAiAEBQg8KDV91c2VyX21lc3NhZ2UiqgEKHFN1YmFnZW50U3RhcnRSZXF1ZXN0UmVzcG9uc2USFwoKcGVybWlzc2lvbhgBIAEoCUgAiAEBEhkKDHVzZXJfbWVzc2FnZRgCIAEoCUgBiAEBEh8KEmFkZGl0aW9uYWxfY29udGV4dBgDIAEoCUgCiAEBQg0KC19wZXJtaXNzaW9uQg8KDV91c2VyX21lc3NhZ2VCFQoTX2FkZGl0aW9uYWxfY29udGV4dCKJAQobU3ViYWdlbnRTdG9wUmVxdWVzdFJlc3BvbnNlEh0KEGZvbGxvd3VwX21lc3NhZ2UYASABKAlIAIgBARIfChJhZGRpdGlvbmFsX2NvbnRleHQYAiABKAlIAYgBAUITChFfZm9sbG93dXBfbWVzc2FnZUIVChNfYWRkaXRpb25hbF9jb250ZXh0IoMCChlQcmVUb29sVXNlUmVxdWVzdFJlc3BvbnNlEhcKCnBlcm1pc3Npb24YASABKAlIAIgBARIZCgx1c2VyX21lc3NhZ2UYAiABKAlIAYgBARIaCg1hZ2VudF9tZXNzYWdlGAMgASgJSAKIAQESGgoNdXBkYXRlZF9pbnB1dBgEIAEoCUgDiAEBEh8KEmFkZGl0aW9uYWxfY29udGV4dBgFIAEoCUgEiAEBQg0KC19wZXJtaXNzaW9uQg8KDV91c2VyX21lc3NhZ2VCEAoOX2FnZW50X21lc3NhZ2VCEAoOX3VwZGF0ZWRfaW5wdXRCFQoTX2FkZGl0aW9uYWxfY29udGV4dCJUChpQb3N0VG9vbFVzZVJlcXVlc3RSZXNwb25zZRIfChJhZGRpdGlvbmFsX2NvbnRleHQYASABKAlIAIgBAUIVChNfYWRkaXRpb25hbF9jb250ZXh0IlsKIVBvc3RUb29sVXNlRmFpbHVyZVJlcXVlc3RSZXNwb25zZRIfChJhZGRpdGlvbmFsX2NvbnRleHQYASABKAlIAIgBAUIVChNfYWRkaXRpb25hbF9jb250ZXh0IqsBCiFCZWZvcmVTdWJtaXRQcm9tcHRSZXF1ZXN0UmVzcG9uc2USFQoIY29udGludWUYASABKAhIAIgBARIZCgx1c2VyX21lc3NhZ2UYAiABKAlIAYgBARIfChJhZGRpdGlvbmFsX2NvbnRleHQYAyABKAlIAogBAUILCglfY29udGludWVCDwoNX3VzZXJfbWVzc2FnZUIVChNfYWRkaXRpb25hbF9jb250ZXh0IiMKIUFmdGVyQWdlbnRSZXNwb25zZVJlcXVlc3RSZXNwb25zZSIiCiBBZnRlckFnZW50VGhvdWdodFJlcXVlc3RSZXNwb25zZSJJChNTdG9wUmVxdWVzdFJlc3BvbnNlEh0KEGZvbGxvd3VwX21lc3NhZ2UYASABKAlIAIgBAUITChFfZm9sbG93dXBfbWVzc2FnZSKdBQoSRXhlY3V0ZUhvb2tSZXF1ZXN0EjcKC3ByZV9jb21wYWN0GAEgASgLMiAuYWdlbnQudjEuUHJlQ29tcGFjdFJlcXVlc3RRdWVyeUgAEj0KDnN1YmFnZW50X3N0YXJ0GAIgASgLMiMuYWdlbnQudjEuU3ViYWdlbnRTdGFydFJlcXVlc3RRdWVyeUgAEjsKDXN1YmFnZW50X3N0b3AYAyABKAsyIi5hZ2VudC52MS5TdWJhZ2VudFN0b3BSZXF1ZXN0UXVlcnlIABI4CgxwcmVfdG9vbF91c2UYBCABKAsyIC5hZ2VudC52MS5QcmVUb29sVXNlUmVxdWVzdFF1ZXJ5SAASOgoNcG9zdF90b29sX3VzZRgFIAEoCzIhLmFnZW50LnYxLlBvc3RUb29sVXNlUmVxdWVzdFF1ZXJ5SAASSQoVcG9zdF90b29sX3VzZV9mYWlsdXJlGAYgASgLMiguYWdlbnQudjEuUG9zdFRvb2xVc2VGYWlsdXJlUmVxdWVzdFF1ZXJ5SAASSAoUYmVmb3JlX3N1Ym1pdF9wcm9tcHQYByABKAsyKC5hZ2VudC52MS5CZWZvcmVTdWJtaXRQcm9tcHRSZXF1ZXN0UXVlcnlIABJIChRhZnRlcl9hZ2VudF9yZXNwb25zZRgIIAEoCzIoLmFnZW50LnYxLkFmdGVyQWdlbnRSZXNwb25zZVJlcXVlc3RRdWVyeUgAEkYKE2FmdGVyX2FnZW50X3Rob3VnaHQYCSABKAsyJy5hZ2VudC52MS5BZnRlckFnZW50VGhvdWdodFJlcXVlc3RRdWVyeUgAEioKBHN0b3AYCyABKAsyGi5hZ2VudC52MS5TdG9wUmVxdWVzdFF1ZXJ5SABCCQoHcmVxdWVzdCK9BQoTRXhlY3V0ZUhvb2tSZXNwb25zZRI6CgtwcmVfY29tcGFjdBgBIAEoCzIjLmFnZW50LnYxLlByZUNvbXBhY3RSZXF1ZXN0UmVzcG9uc2VIABJACg5zdWJhZ2VudF9zdGFydBgCIAEoCzImLmFnZW50LnYxLlN1YmFnZW50U3RhcnRSZXF1ZXN0UmVzcG9uc2VIABI+Cg1zdWJhZ2VudF9zdG9wGAMgASgLMiUuYWdlbnQudjEuU3ViYWdlbnRTdG9wUmVxdWVzdFJlc3BvbnNlSAASOwoMcHJlX3Rvb2xfdXNlGAQgASgLMiMuYWdlbnQudjEuUHJlVG9vbFVzZVJlcXVlc3RSZXNwb25zZUgAEj0KDXBvc3RfdG9vbF91c2UYBSABKAsyJC5hZ2VudC52MS5Qb3N0VG9vbFVzZVJlcXVlc3RSZXNwb25zZUgAEkwKFXBvc3RfdG9vbF91c2VfZmFpbHVyZRgGIAEoCzIrLmFnZW50LnYxLlBvc3RUb29sVXNlRmFpbHVyZVJlcXVlc3RSZXNwb25zZUgAEksKFGJlZm9yZV9zdWJtaXRfcHJvbXB0GAcgASgLMisuYWdlbnQudjEuQmVmb3JlU3VibWl0UHJvbXB0UmVxdWVzdFJlc3BvbnNlSAASSwoUYWZ0ZXJfYWdlbnRfcmVzcG9uc2UYCCABKAsyKy5hZ2VudC52MS5BZnRlckFnZW50UmVzcG9uc2VSZXF1ZXN0UmVzcG9uc2VIABJJChNhZnRlcl9hZ2VudF90aG91Z2h0GAkgASgLMiouYWdlbnQudjEuQWZ0ZXJBZ2VudFRob3VnaHRSZXF1ZXN0UmVzcG9uc2VIABItCgRzdG9wGAsgASgLMh0uYWdlbnQudjEuU3RvcFJlcXVlc3RSZXNwb25zZUgAQgoKCHJlc3BvbnNlIkAKD0V4ZWN1dGVIb29rQXJncxItCgdyZXF1ZXN0GAEgASgLMhwuYWdlbnQudjEuRXhlY3V0ZUhvb2tSZXF1ZXN0IkQKEUV4ZWN1dGVIb29rUmVzdWx0Ei8KCHJlc3BvbnNlGAEgASgLMh0uYWdlbnQudjEuRXhlY3V0ZUhvb2tSZXNwb25zZSIlChNTbWFydE1vZGVSaXNrVGFyZ2V0Eg4KBmFjdGlvbhgBIAEoCSJHCiZTbWFydE1vZGVDbGFzc2lmaWVyQ29udmVyc2F0aW9uTWVzc2FnZRIMCgRyb2xlGAEgASgJEg8KB2NvbnRlbnQYAiABKAki7gEKF1NtYXJ0TW9kZUNsYXNzaWZpZXJBcmdzEhQKDHRvb2xfY2FsbF9pZBgBIAEoCRIjChZwYXJlbnRfY29udmVyc2F0aW9uX2lkGAIgASgJSACIAQESLQoGdGFyZ2V0GAMgASgLMh0uYWdlbnQudjEuU21hcnRNb2RlUmlza1RhcmdldBJOChRjb252ZXJzYXRpb25fY29udGV4dBgEIAMoCzIwLmFnZW50LnYxLlNtYXJ0TW9kZUNsYXNzaWZpZXJDb252ZXJzYXRpb25NZXNzYWdlQhkKF19wYXJlbnRfY29udmVyc2F0aW9uX2lkIoEBChpTbWFydE1vZGVDbGFzc2lmaWVyU3VjY2VzcxI3CghkZWNpc2lvbhgBIAEoDjIlLmFnZW50LnYxLlNtYXJ0TW9kZUNsYXNzaWZpZXJEZWNpc2lvbhIZCgxibG9ja19yZWFzb24YAiABKAlIAIgBAUIPCg1fYmxvY2tfcmVhc29uIikKGFNtYXJ0TW9kZUNsYXNzaWZpZXJFcnJvchINCgVlcnJvchgBIAEoCSKTAQoZU21hcnRNb2RlQ2xhc3NpZmllclJlc3VsdBI3CgdzdWNjZXNzGAEgASgLMiQuYWdlbnQudjEuU21hcnRNb2RlQ2xhc3NpZmllclN1Y2Nlc3NIABIzCgVlcnJvchgCIAEoCzIiLmFnZW50LnYxLlNtYXJ0TW9kZUNsYXNzaWZpZXJFcnJvckgAQggKBnJlc3VsdCI7ChVDYW52YXNEaWFnbm9zdGljc0FyZ3MSDAoEcGF0aBgBIAEoCRIUCgx0b29sX2NhbGxfaWQYAiABKAkiUwoYQ2FudmFzRGlhZ25vc3RpY3NTdWNjZXNzEgwKBHBhdGgYASABKAkSKQoLZGlhZ25vc3RpY3MYAiADKAsyFC5hZ2VudC52MS5EaWFnbm9zdGljIjUKFkNhbnZhc0RpYWdub3N0aWNzRXJyb3ISDAoEcGF0aBgBIAEoCRINCgVlcnJvchgCIAEoCSKNAQoXQ2FudmFzRGlhZ25vc3RpY3NSZXN1bHQSNQoHc3VjY2VzcxgBIAEoCzIiLmFnZW50LnYxLkNhbnZhc0RpYWdub3N0aWNzU3VjY2Vzc0gAEjEKBWVycm9yGAIgASgLMiAuYWdlbnQudjEuQ2FudmFzRGlhZ25vc3RpY3NFcnJvckgAQggKBnJlc3VsdCKsAQoVQ29udmVyc2F0aW9uU2VhcmNoSGl0EhcKD2NvbnZlcnNhdGlvbl9pZBgBIAEoCRINCgV0aXRsZRgCIAEoCRIyCgZzb3VyY2UYAyABKA4yIi5hZ2VudC52MS5Db252ZXJzYXRpb25TZWFyY2hTb3VyY2USFQoNdXBkYXRlZF9hdF9tcxgEIAEoAxIUCgdzbmlwcGV0GAUgASgJSACIAQFCCgoIX3NuaXBwZXQiggEKGUNvbnZlcnNhdGlvblNlYXJjaFN1Y2Nlc3MSLQoEaGl0cxgBIAMoCzIfLmFnZW50LnYxLkNvbnZlcnNhdGlvblNlYXJjaEhpdBIRCgl0cnVuY2F0ZWQYAiABKAgSDwoHcGFydGlhbBgDIAEoCBISCgpyZWJ1aWxkaW5nGAQgASgIIigKF0NvbnZlcnNhdGlvblNlYXJjaEVycm9yEg0KBWVycm9yGAEgASgJIlsKFkNvbnZlcnNhdGlvblNlYXJjaEFyZ3MSDQoFcXVlcnkYASABKAkSFAoMdG9vbF9jYWxsX2lkGAIgASgJEhIKBWxpbWl0GAMgASgFSACIAQFCCAoGX2xpbWl0IpABChhDb252ZXJzYXRpb25TZWFyY2hSZXN1bHQSNgoHc3VjY2VzcxgBIAEoCzIjLmFnZW50LnYxLkNvbnZlcnNhdGlvblNlYXJjaFN1Y2Nlc3NIABIyCgVlcnJvchgCIAEoCzIhLmFnZW50LnYxLkNvbnZlcnNhdGlvblNlYXJjaEVycm9ySABCCAoGcmVzdWx0IlUKGEFnZW50U3RvcmVDb25mbGljdEN1cnNvchIVCg1qb3VybmFsX2Vwb2NoGAEgASgJEgsKA3NlcRgCIAEoBBIVCg1sYXN0X2V2ZW50X2lkGAMgASgJIqUDChdBZ2VudFN0b3JlQ29uZmxpY3RFdmVudBIJCgF2GAEgASgNEhAKCGV2ZW50X2lkGAIgASgJEhUKDWpvdXJuYWxfZXBvY2gYAyABKAkSCwoDc2VxGAQgASgEEg0KBXRzX21zGAUgASgEEgwKBGtpbmQYBiABKAkSFQoIc3RvcmVfaWQYByABKAlIAIgBARIeChFvcmlnaW5hbF9yZWxfcGF0aBgIIAEoCUgBiAEBEh4KEWNvbmZsaWN0X3JlbF9wYXRoGAkgASgJSAKIAQESHgoRb3JpZ2luYWxfYWJzX3BhdGgYCiABKAlIA4gBARIeChFjb25mbGljdF9hYnNfcGF0aBgLIAEoCUgEiAEBEhwKD3ByZXNlcnZlZF9ieXRlcxgMIAEoBEgFiAEBQgsKCV9zdG9yZV9pZEIUChJfb3JpZ2luYWxfcmVsX3BhdGhCFAoSX2NvbmZsaWN0X3JlbF9wYXRoQhQKEl9vcmlnaW5hbF9hYnNfcGF0aEIUChJfY29uZmxpY3RfYWJzX3BhdGhCEgoQX3ByZXNlcnZlZF9ieXRlcyKUAQoZQWdlbnRTdG9yZUNvbmZsaWN0U3VjY2VzcxIxCgZldmVudHMYASADKAsyIS5hZ2VudC52MS5BZ2VudFN0b3JlQ29uZmxpY3RFdmVudBI3CgtuZXh0X2N1cnNvchgCIAEoCzIiLmFnZW50LnYxLkFnZW50U3RvcmVDb25mbGljdEN1cnNvchILCgNnYXAYAyABKAgiKAoXQWdlbnRTdG9yZUNvbmZsaWN0RXJyb3ISDQoFZXJyb3IYASABKAkifgoWQWdlbnRTdG9yZUNvbmZsaWN0QXJncxI3CgZjdXJzb3IYASABKAsyIi5hZ2VudC52MS5BZ2VudFN0b3JlQ29uZmxpY3RDdXJzb3JIAIgBARIUCgdhZHZhbmNlGAIgASgISAGIAQFCCQoHX2N1cnNvckIKCghfYWR2YW5jZSKQAQoYQWdlbnRTdG9yZUNvbmZsaWN0UmVzdWx0EjYKB3N1Y2Nlc3MYASABKAsyIy5hZ2VudC52MS5BZ2VudFN0b3JlQ29uZmxpY3RTdWNjZXNzSAASMgoFZXJyb3IYAiABKAsyIS5hZ2VudC52MS5BZ2VudFN0b3JlQ29uZmxpY3RFcnJvckgAQggKBnJlc3VsdCJ8Cg5GaWxlRGlmZl9DaHVuaxIPCgdjb250ZW50GAEgASgJEg0KBWxpbmVzGAIgAygJEhEKCW9sZF9zdGFydBgDIAEoBRIRCglvbGRfbGluZXMYBCABKAUSEQoJbmV3X3N0YXJ0GAUgASgFEhEKCW5ld19saW5lcxgGIAEoBSKQAgoIRmlsZURpZmYSDQoFYWRkZWQYBCABKAUSDwoHcmVtb3ZlZBgFIAEoBRIMCgRmcm9tGAEgASgJEgoKAnRvGAIgASgJEigKBmNodW5rcxgDIAMoCzIYLmFnZW50LnYxLkZpbGVEaWZmX0NodW5rEiEKFGJlZm9yZV9maWxlX2NvbnRlbnRzGAYgASgJSACIAQESIAoTYWZ0ZXJfZmlsZV9jb250ZW50cxgHIAEoCUgBiAEBEhkKDGlzX2dlbmVyYXRlZBgIIAEoCEgCiAEBQhcKFV9iZWZvcmVfZmlsZV9jb250ZW50c0IWChRfYWZ0ZXJfZmlsZV9jb250ZW50c0IPCg1faXNfZ2VuZXJhdGVkIlsKB0dpdERpZmYSIQoFZGlmZnMYASADKAsyEi5hZ2VudC52MS5GaWxlRGlmZhItCglkaWZmX3R5cGUYAiABKA4yGi5hZ2VudC52MS5HaXREaWZmX0RpZmZUeXBlImgKHUdldERpZmZSZXNwb25zZV9TdWJtb2R1bGVEaWZmEhUKDXJlbGF0aXZlX3BhdGgYASABKAkSHwoEZGlmZhgCIAEoCzIRLmFnZW50LnYxLkdpdERpZmYSDwoHZXJyb3JlZBgDIAEoCCLyAwoOR2V0RGlmZlJlcXVlc3QSCwoDY3dkGAEgASgJEgsKA3JlZhgCIAEoCRIQCghiYXNlX3JlZhgDIAEoCRISCgptZXJnZV9iYXNlGAQgASgIEhQKDHRhcmdldF9wYXRocxgFIAMoCRIiChV1bmlmaWVkX2NvbnRleHRfbGluZXMYBiABKAVIAIgBARIbChNtYXhfdW50cmFja2VkX2ZpbGVzGAcgASgFEh8KF3N1Ym1vZHVsZV9yZWN1cnNlX2RlcHRoGAkgASgFEh0KFWluY2x1ZGVfc3BhY2VfY2hhbmdlcxgKIAEoCBIWCg5jb21taXR0ZWRfb25seRgLIAEoCBIYChBjb21wdXRlX3BhdGNoX2lkGAwgASgIEhwKD3JldHVybl9oZWFkX3NoYRgNIAEoCEgBiAEBEh8KEm1heF9yZXNwb25zZV9ieXRlcxgOIAEoBUgCiAEBEkEKDW91dHB1dF9mb3JtYXQYCCABKA4yJS5hZ2VudC52MS5HZXREaWZmUmVxdWVzdF9PdXRwdXRGb3JtYXRIA4gBAUIYChZfdW5pZmllZF9jb250ZXh0X2xpbmVzQhIKEF9yZXR1cm5faGVhZF9zaGFCFQoTX21heF9yZXNwb25zZV9ieXRlc0IQCg5fb3V0cHV0X2Zvcm1hdCL+AQoPR2V0RGlmZlJlc3BvbnNlEh8KBGRpZmYYASABKAsyES5hZ2VudC52MS5HaXREaWZmEkAKD3N1Ym1vZHVsZV9kaWZmcxgCIAMoCzInLmFnZW50LnYxLkdldERpZmZSZXNwb25zZV9TdWJtb2R1bGVEaWZmEhUKCHBhdGNoX2lkGAMgASgJSACIAQESFQoIaGVhZF9zaGEYBCABKAlIAYgBARIkChdoYXNfdW5jb21taXR0ZWRfY2hhbmdlcxgFIAEoCEgCiAEBQgsKCV9wYXRjaF9pZEILCglfaGVhZF9zaGFCGgoYX2hhc191bmNvbW1pdHRlZF9jaGFuZ2VzImQKDlBpUmVhZFRvb2xDYWxsEiYKBGFyZ3MYASABKAsyGC5hZ2VudC52MS5QaVJlYWRFeGVjQXJncxIqCgZyZXN1bHQYAiABKAsyGi5hZ2VudC52MS5QaVJlYWRFeGVjUmVzdWx0ImQKDlBpQmFzaFRvb2xDYWxsEiYKBGFyZ3MYASABKAsyGC5hZ2VudC52MS5QaUJhc2hFeGVjQXJncxIqCgZyZXN1bHQYAiABKAsyGi5hZ2VudC52MS5QaUJhc2hFeGVjUmVzdWx0ImQKDlBpRWRpdFRvb2xDYWxsEiYKBGFyZ3MYASABKAsyGC5hZ2VudC52MS5QaUVkaXRFeGVjQXJncxIqCgZyZXN1bHQYAiABKAsyGi5hZ2VudC52MS5QaUVkaXRFeGVjUmVzdWx0ImcKD1BpV3JpdGVUb29sQ2FsbBInCgRhcmdzGAEgASgLMhkuYWdlbnQudjEuUGlXcml0ZUV4ZWNBcmdzEisKBnJlc3VsdBgCIAEoCzIbLmFnZW50LnYxLlBpV3JpdGVFeGVjUmVzdWx0ImQKDlBpR3JlcFRvb2xDYWxsEiYKBGFyZ3MYASABKAsyGC5hZ2VudC52MS5QaUdyZXBFeGVjQXJncxIqCgZyZXN1bHQYAiABKAsyGi5hZ2VudC52MS5QaUdyZXBFeGVjUmVzdWx0ImQKDlBpRmluZFRvb2xDYWxsEiYKBGFyZ3MYASABKAsyGC5hZ2VudC52MS5QaUZpbmRFeGVjQXJncxIqCgZyZXN1bHQYAiABKAsyGi5hZ2VudC52MS5QaUZpbmRFeGVjUmVzdWx0Il4KDFBpTHNUb29sQ2FsbBIkCgRhcmdzGAEgASgLMhYuYWdlbnQudjEuUGlMc0V4ZWNBcmdzEigKBnJlc3VsdBgCIAEoCzIYLmFnZW50LnYxLlBpTHNFeGVjUmVzdWx0IoEBChtTZWFyY2hDb252ZXJzYXRpb25zVG9vbENhbGwSLgoEYXJncxgBIAEoCzIgLmFnZW50LnYxLkNvbnZlcnNhdGlvblNlYXJjaEFyZ3MSMgoGcmVzdWx0GAIgASgLMiIuYWdlbnQudjEuQ29udmVyc2F0aW9uU2VhcmNoUmVzdWx0IjkKGkNvbm5lY3RTY21HaXRodWJSZXBvc2l0b3J5Eg0KBW93bmVyGAEgASgJEgwKBHJlcG8YAiABKAkifgoQQ29ubmVjdFNjbUdpdGh1YhI4CgpyZXBvc2l0b3J5GAEgASgLMiQuYWdlbnQudjEuQ29ubmVjdFNjbUdpdGh1YlJlcG9zaXRvcnkSHAoPZ2hlX2FwcGxpY2F0aW9uGAIgASgJSACIAQFCEgoQX2doZV9hcHBsaWNhdGlvbiJeCg5Db25uZWN0U2NtQXJncxIUCgx0b29sX2NhbGxfaWQYASABKAkSLAoGZ2l0aHViGAIgASgLMhouYWdlbnQudjEuQ29ubmVjdFNjbUdpdGh1YkgAQggKBnRhcmdldCITChFDb25uZWN0U2NtU3VjY2VzcyIgCg9Db25uZWN0U2NtRXJyb3ISDQoFZXJyb3IYASABKAkiJAoSQ29ubmVjdFNjbVJlamVjdGVkEg4KBnJlYXNvbhgBIAEoCSKqAQoQQ29ubmVjdFNjbVJlc3VsdBIuCgdzdWNjZXNzGAEgASgLMhsuYWdlbnQudjEuQ29ubmVjdFNjbVN1Y2Nlc3NIABIqCgVlcnJvchgCIAEoCzIZLmFnZW50LnYxLkNvbm5lY3RTY21FcnJvckgAEjAKCHJlamVjdGVkGAMgASgLMhwuYWdlbnQudjEuQ29ubmVjdFNjbVJlamVjdGVkSABCCAoGcmVzdWx0ImgKEkNvbm5lY3RTY21Ub29sQ2FsbBImCgRhcmdzGAEgASgLMhguYWdlbnQudjEuQ29ubmVjdFNjbUFyZ3MSKgoGcmVzdWx0GAIgASgLMhouYWdlbnQudjEuQ29ubmVjdFNjbVJlc3VsdCqIAQodQXBwbGllZEFnZW50Q2hhbmdlX0NoYW5nZVR5cGUSGwoXQ0hBTkdFX1RZUEVfVU5TUEVDSUZJRUQQABIXChNDSEFOR0VfVFlQRV9DUkVBVEVEEAESGAoUQ0hBTkdFX1RZUEVfTU9ESUZJRUQQAhIXChNDSEFOR0VfVFlQRV9ERUxFVEVEEAMqpAEKC01vdXNlQnV0dG9uEhwKGE1PVVNFX0JVVFRPTl9VTlNQRUNJRklFRBAAEhUKEU1PVVNFX0JVVFRPTl9MRUZUEAESFgoSTU9VU0VfQlVUVE9OX1JJR0hUEAISFwoTTU9VU0VfQlVUVE9OX01JRERMRRADEhUKEU1PVVNFX0JVVFRPTl9CQUNLEAQSGAoUTU9VU0VfQlVUVE9OX0ZPUldBUkQQBSqeAQoPU2Nyb2xsRGlyZWN0aW9uEiAKHFNDUk9MTF9ESVJFQ1RJT05fVU5TUEVDSUZJRUQQABIXChNTQ1JPTExfRElSRUNUSU9OX1VQEAESGQoVU0NST0xMX0RJUkVDVElPTl9ET1dOEAISGQoVU0NST0xMX0RJUkVDVElPTl9MRUZUEAMSGgoWU0NST0xMX0RJUkVDVElPTl9SSUdIVBAEKnAKEEN1cnNvclJ1bGVTb3VyY2USIgoeQ1VSU09SX1JVTEVfU09VUkNFX1VOU1BFQ0lGSUVEEAASGwoXQ1VSU09SX1JVTEVfU09VUkNFX1RFQU0QARIbChdDVVJTT1JfUlVMRV9TT1VSQ0VfVVNFUhACKrwBChJEaWFnbm9zdGljU2V2ZXJpdHkSIwofRElBR05PU1RJQ19TRVZFUklUWV9VTlNQRUNJRklFRBAAEh0KGURJQUdOT1NUSUNfU0VWRVJJVFlfRVJST1IQARIfChtESUFHTk9TVElDX1NFVkVSSVRZX1dBUk5JTkcQAhIjCh9ESUFHTk9TVElDX1NFVkVSSVRZX0lORk9STUFUSU9OEAMSHAoYRElBR05PU1RJQ19TRVZFUklUWV9ISU5UEAQqnAEKDVJlY29yZGluZ01vZGUSHgoaUkVDT1JESU5HX01PREVfVU5TUEVDSUZJRUQQABIiCh5SRUNPUkRJTkdfTU9ERV9TVEFSVF9SRUNPUkRJTkcQARIhCh1SRUNPUkRJTkdfTU9ERV9TQVZFX1JFQ09SRElORxACEiQKIFJFQ09SRElOR19NT0RFX0RJU0NBUkRfUkVDT1JESU5HEAMqkwEKH1JlcXVlc3RlZEZpbGVQYXRoUmVqZWN0ZWRSZWFzb24SMwovUkVRVUVTVEVEX0ZJTEVfUEFUSF9SRUpFQ1RFRF9SRUFTT05fVU5TUEVDSUZJRUQQABI7CjdSRVFVRVNURURfRklMRV9QQVRIX1JFSkVDVEVEX1JFQVNPTl9TTEFTSEVTX05PVF9BTExPV0VEEAEqrQEKC1BhY2thZ2VUeXBlEhwKGFBBQ0tBR0VfVFlQRV9VTlNQRUNJRklFRBAAEh8KG1BBQ0tBR0VfVFlQRV9DVVJTT1JfUFJPSkVDVBABEiAKHFBBQ0tBR0VfVFlQRV9DVVJTT1JfUEVSU09OQUwQAhIdChlQQUNLQUdFX1RZUEVfQ0xBVURFX1NLSUxMEAMSHgoaUEFDS0FHRV9UWVBFX0NMQVVERV9QTFVHSU4QBCp9ChJTYW5kYm94UG9saWN5X1R5cGUSFAoQVFlQRV9VTlNQRUNJRklFRBAAEhYKElRZUEVfSU5TRUNVUkVfTk9ORRABEhwKGFRZUEVfV09SS1NQQUNFX1JFQURXUklURRACEhsKF1RZUEVfV09SS1NQQUNFX1JFQURPTkxZEAMqcQoPVGltZW91dEJlaGF2aW9yEiAKHFRJTUVPVVRfQkVIQVZJT1JfVU5TUEVDSUZJRUQQABIbChdUSU1FT1VUX0JFSEFWSU9SX0NBTkNFTBABEh8KG1RJTUVPVVRfQkVIQVZJT1JfQkFDS0dST1VORBACKnkKEFNoZWxsQWJvcnRSZWFzb24SIgoeU0hFTExfQUJPUlRfUkVBU09OX1VOU1BFQ0lGSUVEEAASIQodU0hFTExfQUJPUlRfUkVBU09OX1VTRVJfQUJPUlQQARIeChpTSEVMTF9BQk9SVF9SRUFTT05fVElNRU9VVBACKqoBChxDdXN0b21TdWJhZ2VudFBlcm1pc3Npb25Nb2RlEi8KK0NVU1RPTV9TVUJBR0VOVF9QRVJNSVNTSU9OX01PREVfVU5TUEVDSUZJRUQQABIrCidDVVNUT01fU1VCQUdFTlRfUEVSTUlTU0lPTl9NT0RFX0RFRkFVTFQQARIsCihDVVNUT01fU1VCQUdFTlRfUEVSTUlTU0lPTl9NT0RFX1JFQURPTkxZEAIqlQEKClRvZG9TdGF0dXMSGwoXVE9ET19TVEFUVVNfVU5TUEVDSUZJRUQQABIXChNUT0RPX1NUQVRVU19QRU5ESU5HEAESGwoXVE9ET19TVEFUVVNfSU5fUFJPR1JFU1MQAhIZChVUT0RPX1NUQVRVU19DT01QTEVURUQQAxIZChVUT0RPX1NUQVRVU19DQU5DRUxMRUQQBCpmCghDbGllbnRPUxIZChVDTElFTlRfT1NfVU5TUEVDSUZJRUQQABIVChFDTElFTlRfT1NfV0lORE9XUxABEhMKD0NMSUVOVF9PU19NQUNPUxACEhMKD0NMSUVOVF9PU19MSU5VWBADKuwBChxBcnRpZmFjdFVwbG9hZERpc3BhdGNoU3RhdHVzEi8KK0FSVElGQUNUX1VQTE9BRF9ESVNQQVRDSF9TVEFUVVNfVU5TUEVDSUZJRUQQABIsCihBUlRJRkFDVF9VUExPQURfRElTUEFUQ0hfU1RBVFVTX0FDQ0VQVEVEEAESLAooQVJUSUZBQ1RfVVBMT0FEX0RJU1BBVENIX1NUQVRVU19SRUpFQ1RFRBACEj8KO0FSVElGQUNUX1VQTE9BRF9ESVNQQVRDSF9TVEFUVVNfU0tJUFBFRF9BTFJFQURZX0lOX1BST0dSRVNTEAMqVwoKRnJhbWVfS2luZBIUChBLSU5EX1VOU1BFQ0lGSUVEEAASEAoMS0lORF9SRVFVRVNUEAESEQoNS0lORF9SRVNQT05TRRACEg4KCktJTkRfRVJST1IQAyqwAgoXQnVnYm90RGVlcGxpbmtFdmVudEtpbmQSKgomQlVHQk9UX0RFRVBMSU5LX0VWRU5UX0tJTkRfVU5TUEVDSUZJRUQQABImCiJCVUdCT1RfREVFUExJTktfRVZFTlRfS0lORF9DTElDS0VEEAESMwovQlVHQk9UX0RFRVBMSU5LX0VWRU5UX0tJTkRfSEFORExFRF9ESUFMT0dfU0hPV04QAhIzCi9CVUdCT1RfREVFUExJTktfRVZFTlRfS0lORF9IQU5ETEVEX0NIQVRfQ1JFQVRFRBADEiQKIEJVR0JPVF9ERUVQTElOS19FVkVOVF9LSU5EX0VSUk9SEAQSMQotQlVHQk9UX0RFRVBMSU5LX0VWRU5UX0tJTkRfSEFORExFRF9GSVhfSU5fV0VCEAUqygEKLENvbW1hbmRDbGFzc2lmaWVyUmVzdWx0X1N1Z2dlc3RlZFNhbmRib3hNb2RlEiYKIlNVR0dFU1RFRF9TQU5EQk9YX01PREVfVU5TUEVDSUZJRUQQABIiCh5TVUdHRVNURURfU0FOREJPWF9NT0RFX1NBTkRCT1gQARIlCiFTVUdHRVNURURfU0FOREJPWF9NT0RFX05PX1NBTkRCT1gQAhInCiNTVUdHRVNURURfU0FOREJPWF9NT0RFX1VOREVURVJNSU5FRBADKpABCiFTaGVsbEhvb2tBcHByb3ZhbFJlcXVpcmVtZW50X0tpbmQSNAowU0hFTExfSE9PS19BUFBST1ZBTF9SRVFVSVJFTUVOVF9LSU5EX1VOU1BFQ0lGSUVEEAASNQoxU0hFTExfSE9PS19BUFBST1ZBTF9SRVFVSVJFTUVOVF9LSU5EX0ZPUkNFX1BST01QVBABKo8BChVTaGVsbEJhY2tncm91bmRSZWFzb24SJwojU0hFTExfQkFDS0dST1VORF9SRUFTT05fVU5TUEVDSUZJRUQQABIjCh9TSEVMTF9CQUNLR1JPVU5EX1JFQVNPTl9USU1FT1VUEAESKAokU0hFTExfQkFDS0dST1VORF9SRUFTT05fVVNFUl9SRVFVRVNUEAIqpAEKGkZvcmNlQmFja2dyb3VuZFNoZWxsU3RhdHVzEi0KKUZPUkNFX0JBQ0tHUk9VTkRfU0hFTExfU1RBVFVTX1VOU1BFQ0lGSUVEEAASKgomRk9SQ0VfQkFDS0dST1VORF9TSEVMTF9TVEFUVVNfQUNDRVBURUQQARIrCidGT1JDRV9CQUNLR1JPVU5EX1NIRUxMX1NUQVRVU19OT1RfRk9VTkQQAirSAQoYU3ViYWdlbnRCYWNrZ3JvdW5kUmVhc29uEioKJlNVQkFHRU5UX0JBQ0tHUk9VTkRfUkVBU09OX1VOU1BFQ0lGSUVEEAASLAooU1VCQUdFTlRfQkFDS0dST1VORF9SRUFTT05fQUdFTlRfUkVRVUVTVBABEisKJ1NVQkFHRU5UX0JBQ0tHUk9VTkRfUkVBU09OX1VTRVJfUkVRVUVTVBACEi8KK1NVQkFHRU5UX0JBQ0tHUk9VTkRfUkVBU09OX1FVRVVFRF9GT0xMT1dfVVAQAyqwAQodRm9yY2VCYWNrZ3JvdW5kU3ViYWdlbnRTdGF0dXMSMAosRk9SQ0VfQkFDS0dST1VORF9TVUJBR0VOVF9TVEFUVVNfVU5TUEVDSUZJRUQQABItCilGT1JDRV9CQUNLR1JPVU5EX1NVQkFHRU5UX1NUQVRVU19BQ0NFUFRFRBABEi4KKkZPUkNFX0JBQ0tHUk9VTkRfU1VCQUdFTlRfU1RBVFVTX05PVF9GT1VORBACKqEBChtTbWFydE1vZGVDbGFzc2lmaWVyRGVjaXNpb24SLgoqU01BUlRfTU9ERV9DTEFTU0lGSUVSX0RFQ0lTSU9OX1VOU1BFQ0lGSUVEEAASKAokU01BUlRfTU9ERV9DTEFTU0lGSUVSX0RFQ0lTSU9OX0FMTE9XEAESKAokU01BUlRfTU9ERV9DTEFTU0lGSUVSX0RFQ0lTSU9OX0JMT0NLEAIqmAEKGENvbnZlcnNhdGlvblNlYXJjaFNvdXJjZRIqCiZDT05WRVJTQVRJT05fU0VBUkNIX1NPVVJDRV9VTlNQRUNJRklFRBAAEiQKIENPTlZFUlNBVElPTl9TRUFSQ0hfU09VUkNFX0xPQ0FMEAESKgomQ09OVkVSU0FUSU9OX1NFQVJDSF9TT1VSQ0VfQ0xPVURfQ0FDSEUQAirTAQobR2V0RGlmZlJlcXVlc3RfT3V0cHV0Rm9ybWF0Eh0KGU9VVFBVVF9GT1JNQVRfVU5TUEVDSUZJRUQQABIdChlPVVRQVVRfRk9STUFUX05BTUVfU1RBVFVTEAESKQolT1VUUFVUX0ZPUk1BVF9OQU1FX1NUQVRVU19BTkRfTlVNU1RBVBACEhwKGE9VVFBVVF9GT1JNQVRfRklMRV9ESUZGUxADEi0KKU9VVFBVVF9GT1JNQVRfRElGRlNfV0lUSF9CRUZPUkVfQU5EX0FGVEVSEAQqcQoQR2l0RGlmZl9EaWZmVHlwZRIZChVESUZGX1RZUEVfVU5TUEVDSUZJRUQQABIaChZESUZGX1RZUEVfRElGRl9UT19IRUFEEAESJgoiRElGRl9UWVBFX0RJRkZfRlJPTV9CUkFOQ0hfVE9fTUFJThACMocECgxBZ2VudFNlcnZpY2USQQoDUnVuEhwuYWdlbnQudjEuQWdlbnRDbGllbnRNZXNzYWdlGhwuYWdlbnQudjEuQWdlbnRTZXJ2ZXJNZXNzYWdlEj8KBlJ1blNTRRIXLmFnZW50LnYxLkJpZGlSZXF1ZXN0SWQaHC5hZ2VudC52MS5BZ2VudFNlcnZlck1lc3NhZ2USRAoJTmFtZUFnZW50EhouYWdlbnQudjEuTmFtZUFnZW50UmVxdWVzdBobLmFnZW50LnYxLk5hbWVBZ2VudFJlc3BvbnNlElYKD0dldFVzYWJsZU1vZGVscxIgLmFnZW50LnYxLkdldFVzYWJsZU1vZGVsc1JlcXVlc3QaIS5hZ2VudC52MS5HZXRVc2FibGVNb2RlbHNSZXNwb25zZRJoChVHZXREZWZhdWx0TW9kZWxGb3JDbGkSJi5hZ2VudC52MS5HZXREZWZhdWx0TW9kZWxGb3JDbGlSZXF1ZXN0GicuYWdlbnQudjEuR2V0RGVmYXVsdE1vZGVsRm9yQ2xpUmVzcG9uc2USawoWR2V0QWxsb3dlZE1vZGVsSW50ZW50cxInLmFnZW50LnYxLkdldEFsbG93ZWRNb2RlbEludGVudHNSZXF1ZXN0GiguYWdlbnQudjEuR2V0QWxsb3dlZE1vZGVsSW50ZW50c1Jlc3BvbnNlMrUICg5Db250cm9sU2VydmljZRJNCgxSZWFkVGV4dEZpbGUSHS5hZ2VudC52MS5SZWFkVGV4dEZpbGVSZXF1ZXN0Gh4uYWdlbnQudjEuUmVhZFRleHRGaWxlUmVzcG9uc2USUAoNV3JpdGVUZXh0RmlsZRIeLmFnZW50LnYxLldyaXRlVGV4dEZpbGVSZXF1ZXN0Gh8uYWdlbnQudjEuV3JpdGVUZXh0RmlsZVJlc3BvbnNlElMKDlJlYWRCaW5hcnlGaWxlEh8uYWdlbnQudjEuUmVhZEJpbmFyeUZpbGVSZXF1ZXN0GiAuYWdlbnQudjEuUmVhZEJpbmFyeUZpbGVSZXNwb25zZRJWCg9Xcml0ZUJpbmFyeUZpbGUSIC5hZ2VudC52MS5Xcml0ZUJpbmFyeUZpbGVSZXF1ZXN0GiEuYWdlbnQudjEuV3JpdGVCaW5hcnlGaWxlUmVzcG9uc2USbgoXR2V0V29ya3NwYWNlQ2hhbmdlc0hhc2gSKC5hZ2VudC52MS5HZXRXb3Jrc3BhY2VDaGFuZ2VzSGFzaFJlcXVlc3QaKS5hZ2VudC52MS5HZXRXb3Jrc3BhY2VDaGFuZ2VzSGFzaFJlc3BvbnNlEnEKGFJlZnJlc2hHaXRodWJBY2Nlc3NUb2tlbhIpLmFnZW50LnYxLlJlZnJlc2hHaXRodWJBY2Nlc3NUb2tlblJlcXVlc3QaKi5hZ2VudC52MS5SZWZyZXNoR2l0aHViQWNjZXNzVG9rZW5SZXNwb25zZRJrChZXYXJtUmVtb3RlQWNjZXNzU2VydmVyEicuYWdlbnQudjEuV2FybVJlbW90ZUFjY2Vzc1NlcnZlclJlcXVlc3QaKC5hZ2VudC52MS5XYXJtUmVtb3RlQWNjZXNzU2VydmVyUmVzcG9uc2USUAoNTGlzdEFydGlmYWN0cxIeLmFnZW50LnYxLkxpc3RBcnRpZmFjdHNSZXF1ZXN0Gh8uYWdlbnQudjEuTGlzdEFydGlmYWN0c1Jlc3BvbnNlElYKD1VwbG9hZEFydGlmYWN0cxIgLmFnZW50LnYxLlVwbG9hZEFydGlmYWN0c1JlcXVlc3QaIS5hZ2VudC52MS5VcGxvYWRBcnRpZmFjdHNSZXNwb25zZRJiChNHZXRNY3BSZWZyZXNoVG9rZW5zEiQuYWdlbnQudjEuR2V0TWNwUmVmcmVzaFRva2Vuc1JlcXVlc3QaJS5hZ2VudC52MS5HZXRNY3BSZWZyZXNoVG9rZW5zUmVzcG9uc2USdwoaVXBkYXRlRW52aXJvbm1lbnRWYXJpYWJsZXMSKy5hZ2VudC52MS5VcGRhdGVFbnZpcm9ubWVudFZhcmlhYmxlc1JlcXVlc3QaLC5hZ2VudC52MS5VcGRhdGVFbnZpcm9ubWVudFZhcmlhYmxlc1Jlc3BvbnNlMg0KC0V4ZWNTZXJ2aWNlMlEKIlByaXZhdGVXb3JrZXJCcmlkZ2VFeHRlcm5hbFNlcnZpY2USKwoHQ29ubmVjdBIPLmFnZW50LnYxLkZyYW1lGg8uYWdlbnQudjEuRnJhbWUyeAoQTGlmZWN5Y2xlU2VydmljZRIxCg1SZXNldEluc3RhbmNlEg8uYWdlbnQudjEuRW1wdHkaDy5hZ2VudC52MS5FbXB0eRIxCg1SZW5ld0luc3RhbmNlEg8uYWdlbnQudjEuRW1wdHkaDy5hZ2VudC52MS5FbXB0eWIGcHJvdG8z"); /** * @generated from message agent.v1.GlobToolResult */ export type GlobToolResult = Message<"agent.v1.GlobToolResult"> & { - /** - * @generated from oneof agent.v1.GlobToolResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.GlobToolSuccess success = 1; - */ - value: GlobToolSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.GlobToolError error = 2; - */ - value: GlobToolError; - case: "error"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.GlobToolResult.result + */ + result: { + /** + * @generated from field: agent.v1.GlobToolSuccess success = 1; + */ + value: GlobToolSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.GlobToolError error = 2; + */ + value: GlobToolError; + case: "error"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.GlobToolResult. * Use `create(GlobToolResultSchema)` to create a new message. */ -export const GlobToolResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 0); +export const GlobToolResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 0); /** * @generated from message agent.v1.GlobToolError */ export type GlobToolError = Message<"agent.v1.GlobToolError"> & { - /** - * @generated from field: string error = 1; - */ - error: string; + /** + * @generated from field: string error = 1; + */ + error: string; }; /** * Describes the message agent.v1.GlobToolError. * Use `create(GlobToolErrorSchema)` to create a new message. */ -export const GlobToolErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 1); +export const GlobToolErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 1); /** * Only file results are needed for this tool @@ -68,277 +64,290 @@ export const GlobToolErrorSchema: GenMessage = /*@__PURE__*/ mess * @generated from message agent.v1.GlobToolSuccess */ export type GlobToolSuccess = Message<"agent.v1.GlobToolSuccess"> & { - /** - * @generated from field: string pattern = 1; - */ - pattern: string; + /** + * @generated from field: string pattern = 1; + */ + pattern: string; - /** - * @generated from field: string path = 2; - */ - path: string; + /** + * @generated from field: string path = 2; + */ + path: string; - /** - * @generated from field: repeated string files = 3; - */ - files: string[]; + /** + * @generated from field: repeated string files = 3; + */ + files: string[]; - /** - * @generated from field: int32 total_files = 4; - */ - totalFiles: number; + /** + * @generated from field: int32 total_files = 4; + */ + totalFiles: number; - /** - * @generated from field: bool client_truncated = 5; - */ - clientTruncated: boolean; + /** + * @generated from field: bool client_truncated = 5; + */ + clientTruncated: boolean; - /** - * @generated from field: bool ripgrep_truncated = 6; - */ - ripgrepTruncated: boolean; + /** + * @generated from field: bool ripgrep_truncated = 6; + */ + ripgrepTruncated: boolean; }; /** * Describes the message agent.v1.GlobToolSuccess. * Use `create(GlobToolSuccessSchema)` to create a new message. */ -export const GlobToolSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 2); +export const GlobToolSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 2); /** * @generated from message agent.v1.GlobToolCall */ export type GlobToolCall = Message<"agent.v1.GlobToolCall"> & { - /** - * @generated from field: bytes args = 1; - */ - args: Uint8Array; + /** + * @generated from field: bytes args = 1; + */ + args: Uint8Array; - /** - * @generated from field: agent.v1.GlobToolResult result = 2; - */ - result?: GlobToolResult; + /** + * @generated from field: agent.v1.GlobToolResult result = 2; + */ + result?: GlobToolResult | undefined; }; /** * Describes the message agent.v1.GlobToolCall. * Use `create(GlobToolCallSchema)` to create a new message. */ -export const GlobToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 3); +export const GlobToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 3); /** * @generated from message agent.v1.ReadLintsToolCall */ export type ReadLintsToolCall = Message<"agent.v1.ReadLintsToolCall"> & { - /** - * @generated from field: agent.v1.ReadLintsToolArgs args = 1; - */ - args?: ReadLintsToolArgs; + /** + * @generated from field: agent.v1.ReadLintsToolArgs args = 1; + */ + args?: ReadLintsToolArgs | undefined; - /** - * @generated from field: agent.v1.ReadLintsToolResult result = 2; - */ - result?: ReadLintsToolResult; + /** + * @generated from field: agent.v1.ReadLintsToolResult result = 2; + */ + result?: ReadLintsToolResult | undefined; }; /** * Describes the message agent.v1.ReadLintsToolCall. * Use `create(ReadLintsToolCallSchema)` to create a new message. */ -export const ReadLintsToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 4); +export const ReadLintsToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 4); /** * @generated from message agent.v1.ReadLintsToolArgs */ export type ReadLintsToolArgs = Message<"agent.v1.ReadLintsToolArgs"> & { - /** - * @generated from field: repeated string paths = 1; - */ - paths: string[]; + /** + * @generated from field: repeated string paths = 1; + */ + paths: string[]; }; /** * Describes the message agent.v1.ReadLintsToolArgs. * Use `create(ReadLintsToolArgsSchema)` to create a new message. */ -export const ReadLintsToolArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 5); +export const ReadLintsToolArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 5); /** * @generated from message agent.v1.ReadLintsToolResult */ export type ReadLintsToolResult = Message<"agent.v1.ReadLintsToolResult"> & { - /** - * @generated from oneof agent.v1.ReadLintsToolResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.ReadLintsToolSuccess success = 1; - */ - value: ReadLintsToolSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.ReadLintsToolError error = 2; - */ - value: ReadLintsToolError; - case: "error"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ReadLintsToolResult.result + */ + result: { + /** + * @generated from field: agent.v1.ReadLintsToolSuccess success = 1; + */ + value: ReadLintsToolSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.ReadLintsToolError error = 2; + */ + value: ReadLintsToolError; + case: "error"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ReadLintsToolResult. * Use `create(ReadLintsToolResultSchema)` to create a new message. */ -export const ReadLintsToolResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 6); +export const ReadLintsToolResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 6); /** * @generated from message agent.v1.ReadLintsToolSuccess */ export type ReadLintsToolSuccess = Message<"agent.v1.ReadLintsToolSuccess"> & { - /** - * @generated from field: repeated agent.v1.FileDiagnostics file_diagnostics = 1; - */ - fileDiagnostics: FileDiagnostics[]; + /** + * @generated from field: repeated agent.v1.FileDiagnostics file_diagnostics = 1; + */ + fileDiagnostics: FileDiagnostics[]; - /** - * @generated from field: int32 total_files = 2; - */ - totalFiles: number; + /** + * @generated from field: int32 total_files = 2; + */ + totalFiles: number; - /** - * @generated from field: int32 total_diagnostics = 3; - */ - totalDiagnostics: number; + /** + * @generated from field: int32 total_diagnostics = 3; + */ + totalDiagnostics: number; }; /** * Describes the message agent.v1.ReadLintsToolSuccess. * Use `create(ReadLintsToolSuccessSchema)` to create a new message. */ -export const ReadLintsToolSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 7); +export const ReadLintsToolSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 7); /** * @generated from message agent.v1.FileDiagnostics */ export type FileDiagnostics = Message<"agent.v1.FileDiagnostics"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: repeated agent.v1.DiagnosticItem diagnostics = 2; - */ - diagnostics: DiagnosticItem[]; + /** + * @generated from field: repeated agent.v1.DiagnosticItem diagnostics = 2; + */ + diagnostics: DiagnosticItem[]; - /** - * @generated from field: int32 diagnostics_count = 3; - */ - diagnosticsCount: number; + /** + * @generated from field: int32 diagnostics_count = 3; + */ + diagnosticsCount: number; }; /** * Describes the message agent.v1.FileDiagnostics. * Use `create(FileDiagnosticsSchema)` to create a new message. */ -export const FileDiagnosticsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 8); +export const FileDiagnosticsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 8); /** * @generated from message agent.v1.DiagnosticItem */ export type DiagnosticItem = Message<"agent.v1.DiagnosticItem"> & { - /** - * @generated from field: agent.v1.DiagnosticSeverity severity = 1; - */ - severity: DiagnosticSeverity; + /** + * @generated from field: agent.v1.DiagnosticSeverity severity = 1; + */ + severity: DiagnosticSeverity; - /** - * @generated from field: agent.v1.DiagnosticRange range = 2; - */ - range?: DiagnosticRange; + /** + * @generated from field: agent.v1.DiagnosticRange range = 2; + */ + range?: DiagnosticRange | undefined; - /** - * @generated from field: string message = 3; - */ - message: string; + /** + * @generated from field: string message = 3; + */ + message: string; - /** - * @generated from field: string source = 4; - */ - source: string; + /** + * @generated from field: string source = 4; + */ + source: string; - /** - * @generated from field: string code = 5; - */ - code: string; + /** + * @generated from field: string code = 5; + */ + code: string; - /** - * @generated from field: bool is_stale = 6; - */ - isStale: boolean; + /** + * @generated from field: bool is_stale = 6; + */ + isStale: boolean; }; /** * Describes the message agent.v1.DiagnosticItem. * Use `create(DiagnosticItemSchema)` to create a new message. */ -export const DiagnosticItemSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 9); +export const DiagnosticItemSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 9); /** * @generated from message agent.v1.DiagnosticRange */ export type DiagnosticRange = Message<"agent.v1.DiagnosticRange"> & { - /** - * @generated from field: agent.v1.Position start = 1; - */ - start?: Position; + /** + * @generated from field: agent.v1.Position start = 1; + */ + start?: Position | undefined; - /** - * @generated from field: agent.v1.Position end = 2; - */ - end?: Position; + /** + * @generated from field: agent.v1.Position end = 2; + */ + end?: Position | undefined; }; /** * Describes the message agent.v1.DiagnosticRange. * Use `create(DiagnosticRangeSchema)` to create a new message. */ -export const DiagnosticRangeSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 10); +export const DiagnosticRangeSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 10); /** * @generated from message agent.v1.ReadLintsToolError */ export type ReadLintsToolError = Message<"agent.v1.ReadLintsToolError"> & { - /** - * @generated from field: string error_message = 1; - */ - errorMessage: string; + /** + * @generated from field: string error_message = 1; + */ + errorMessage: string; }; /** * Describes the message agent.v1.ReadLintsToolError. * Use `create(ReadLintsToolErrorSchema)` to create a new message. */ -export const ReadLintsToolErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 11); +export const ReadLintsToolErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 11); /** * @generated from message agent.v1.McpToolError */ export type McpToolError = Message<"agent.v1.McpToolError"> & { - /** - * @generated from field: string error = 1; - */ - error: string; + /** + * @generated from field: string error = 1; + */ + error: string; + + /** + * @generated from field: string read_tool_def_reminder = 2; + */ + readToolDefReminder: string; }; /** * Describes the message agent.v1.McpToolError. * Use `create(McpToolErrorSchema)` to create a new message. */ -export const McpToolErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 12); +export const McpToolErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 12); /** * Result for MCP tool calls (separate from exec results) @@ -346,637 +355,629 @@ export const McpToolErrorSchema: GenMessage = /*@__PURE__*/ messag * @generated from message agent.v1.McpToolResult */ export type McpToolResult = Message<"agent.v1.McpToolResult"> & { - /** - * @generated from oneof agent.v1.McpToolResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.McpSuccess success = 1; - */ - value: McpSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.McpToolError error = 2; - */ - value: McpToolError; - case: "error"; - } - | { - /** - * @generated from field: agent.v1.McpRejected rejected = 3; - */ - value: McpRejected; - case: "rejected"; - } - | { - /** - * @generated from field: agent.v1.McpPermissionDenied permission_denied = 4; - */ - value: McpPermissionDenied; - case: "permissionDenied"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.McpToolResult.result + */ + result: { + /** + * @generated from field: agent.v1.McpSuccess success = 1; + */ + value: McpSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.McpToolError error = 2; + */ + value: McpToolError; + case: "error"; + } | { + /** + * @generated from field: agent.v1.McpRejected rejected = 3; + */ + value: McpRejected; + case: "rejected"; + } | { + /** + * @generated from field: agent.v1.McpPermissionDenied permission_denied = 4; + */ + value: McpPermissionDenied; + case: "permissionDenied"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.McpToolResult. * Use `create(McpToolResultSchema)` to create a new message. */ -export const McpToolResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 13); +export const McpToolResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 13); /** * @generated from message agent.v1.McpToolCall */ export type McpToolCall = Message<"agent.v1.McpToolCall"> & { - /** - * @generated from field: agent.v1.McpArgs args = 1; - */ - args?: McpArgs; + /** + * @generated from field: agent.v1.McpArgs args = 1; + */ + args?: McpArgs | undefined; - /** - * @generated from field: agent.v1.McpToolResult result = 2; - */ - result?: McpToolResult; + /** + * @generated from field: agent.v1.McpToolResult result = 2; + */ + result?: McpToolResult | undefined; + + /** + * @generated from field: optional string description = 3; + */ + description?: string | undefined; }; /** * Describes the message agent.v1.McpToolCall. * Use `create(McpToolCallSchema)` to create a new message. */ -export const McpToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 14); +export const McpToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 14); /** * @generated from message agent.v1.SemSearchToolCall */ export type SemSearchToolCall = Message<"agent.v1.SemSearchToolCall"> & { - /** - * @generated from field: agent.v1.SemSearchToolArgs args = 1; - */ - args?: SemSearchToolArgs; + /** + * @generated from field: agent.v1.SemSearchToolArgs args = 1; + */ + args?: SemSearchToolArgs | undefined; - /** - * @generated from field: agent.v1.SemSearchToolResult result = 2; - */ - result?: SemSearchToolResult; + /** + * @generated from field: agent.v1.SemSearchToolResult result = 2; + */ + result?: SemSearchToolResult | undefined; }; /** * Describes the message agent.v1.SemSearchToolCall. * Use `create(SemSearchToolCallSchema)` to create a new message. */ -export const SemSearchToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 15); +export const SemSearchToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 15); /** * @generated from message agent.v1.SemSearchToolArgs */ export type SemSearchToolArgs = Message<"agent.v1.SemSearchToolArgs"> & { - /** - * @generated from field: string query = 1; - */ - query: string; + /** + * @generated from field: string query = 1; + */ + query: string; - /** - * @generated from field: repeated string target_directories = 2; - */ - targetDirectories: string[]; + /** + * @generated from field: repeated string target_directories = 2; + */ + targetDirectories: string[]; - /** - * @generated from field: string explanation = 3; - */ - explanation: string; + /** + * @generated from field: string explanation = 3; + */ + explanation: string; }; /** * Describes the message agent.v1.SemSearchToolArgs. * Use `create(SemSearchToolArgsSchema)` to create a new message. */ -export const SemSearchToolArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 16); +export const SemSearchToolArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 16); /** * @generated from message agent.v1.SemSearchToolResult */ export type SemSearchToolResult = Message<"agent.v1.SemSearchToolResult"> & { - /** - * @generated from oneof agent.v1.SemSearchToolResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.SemSearchToolSuccess success = 1; - */ - value: SemSearchToolSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.SemSearchToolError error = 2; - */ - value: SemSearchToolError; - case: "error"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.SemSearchToolResult.result + */ + result: { + /** + * @generated from field: agent.v1.SemSearchToolSuccess success = 1; + */ + value: SemSearchToolSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.SemSearchToolError error = 2; + */ + value: SemSearchToolError; + case: "error"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.SemSearchToolResult. * Use `create(SemSearchToolResultSchema)` to create a new message. */ -export const SemSearchToolResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 17); +export const SemSearchToolResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 17); /** * @generated from message agent.v1.SemSearchToolSuccess */ export type SemSearchToolSuccess = Message<"agent.v1.SemSearchToolSuccess"> & { - /** - * @generated from field: string results = 1; - */ - results: string; + /** + * @generated from field: string results = 1; + */ + results: string; - /** - * @generated from field: repeated bytes code_results = 2; - */ - codeResults: Uint8Array[]; + /** + * @generated from field: repeated bytes code_results = 2; + */ + codeResults: Uint8Array[]; }; /** * Describes the message agent.v1.SemSearchToolSuccess. * Use `create(SemSearchToolSuccessSchema)` to create a new message. */ -export const SemSearchToolSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 18); +export const SemSearchToolSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 18); /** * @generated from message agent.v1.SemSearchToolError */ export type SemSearchToolError = Message<"agent.v1.SemSearchToolError"> & { - /** - * @generated from field: string error_message = 1; - */ - errorMessage: string; + /** + * @generated from field: string error_message = 1; + */ + errorMessage: string; }; /** * Describes the message agent.v1.SemSearchToolError. * Use `create(SemSearchToolErrorSchema)` to create a new message. */ -export const SemSearchToolErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 19); +export const SemSearchToolErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 19); /** * @generated from message agent.v1.ListMcpResourcesToolCall */ export type ListMcpResourcesToolCall = Message<"agent.v1.ListMcpResourcesToolCall"> & { - /** - * @generated from field: agent.v1.ListMcpResourcesExecArgs args = 1; - */ - args?: ListMcpResourcesExecArgs; + /** + * @generated from field: agent.v1.ListMcpResourcesExecArgs args = 1; + */ + args?: ListMcpResourcesExecArgs | undefined; - /** - * @generated from field: agent.v1.ListMcpResourcesExecResult result = 2; - */ - result?: ListMcpResourcesExecResult; + /** + * @generated from field: agent.v1.ListMcpResourcesExecResult result = 2; + */ + result?: ListMcpResourcesExecResult | undefined; }; /** * Describes the message agent.v1.ListMcpResourcesToolCall. * Use `create(ListMcpResourcesToolCallSchema)` to create a new message. */ -export const ListMcpResourcesToolCallSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 20); +export const ListMcpResourcesToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 20); /** * @generated from message agent.v1.ReadMcpResourceToolCall */ export type ReadMcpResourceToolCall = Message<"agent.v1.ReadMcpResourceToolCall"> & { - /** - * @generated from field: agent.v1.ReadMcpResourceExecArgs args = 1; - */ - args?: ReadMcpResourceExecArgs; + /** + * @generated from field: agent.v1.ReadMcpResourceExecArgs args = 1; + */ + args?: ReadMcpResourceExecArgs | undefined; - /** - * @generated from field: agent.v1.ReadMcpResourceExecResult result = 2; - */ - result?: ReadMcpResourceExecResult; + /** + * @generated from field: agent.v1.ReadMcpResourceExecResult result = 2; + */ + result?: ReadMcpResourceExecResult | undefined; }; /** * Describes the message agent.v1.ReadMcpResourceToolCall. * Use `create(ReadMcpResourceToolCallSchema)` to create a new message. */ -export const ReadMcpResourceToolCallSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 21); +export const ReadMcpResourceToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 21); /** * @generated from message agent.v1.FetchToolCall */ export type FetchToolCall = Message<"agent.v1.FetchToolCall"> & { - /** - * @generated from field: agent.v1.FetchArgs args = 1; - */ - args?: FetchArgs; + /** + * @generated from field: agent.v1.FetchArgs args = 1; + */ + args?: FetchArgs | undefined; - /** - * @generated from field: agent.v1.FetchResult result = 2; - */ - result?: FetchResult; + /** + * @generated from field: agent.v1.FetchResult result = 2; + */ + result?: FetchResult | undefined; }; /** * Describes the message agent.v1.FetchToolCall. * Use `create(FetchToolCallSchema)` to create a new message. */ -export const FetchToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 22); +export const FetchToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 22); /** * @generated from message agent.v1.RecordScreenToolCall */ export type RecordScreenToolCall = Message<"agent.v1.RecordScreenToolCall"> & { - /** - * @generated from field: agent.v1.RecordScreenArgs args = 1; - */ - args?: RecordScreenArgs; + /** + * @generated from field: agent.v1.RecordScreenArgs args = 1; + */ + args?: RecordScreenArgs | undefined; - /** - * @generated from field: agent.v1.RecordScreenResult result = 2; - */ - result?: RecordScreenResult; + /** + * @generated from field: agent.v1.RecordScreenResult result = 2; + */ + result?: RecordScreenResult | undefined; }; /** * Describes the message agent.v1.RecordScreenToolCall. * Use `create(RecordScreenToolCallSchema)` to create a new message. */ -export const RecordScreenToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 23); +export const RecordScreenToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 23); /** * @generated from message agent.v1.WriteShellStdinToolCall */ export type WriteShellStdinToolCall = Message<"agent.v1.WriteShellStdinToolCall"> & { - /** - * @generated from field: agent.v1.WriteShellStdinArgs args = 1; - */ - args?: WriteShellStdinArgs; + /** + * @generated from field: agent.v1.WriteShellStdinArgs args = 1; + */ + args?: WriteShellStdinArgs | undefined; - /** - * @generated from field: agent.v1.WriteShellStdinResult result = 2; - */ - result?: WriteShellStdinResult; + /** + * @generated from field: agent.v1.WriteShellStdinResult result = 2; + */ + result?: WriteShellStdinResult | undefined; }; /** * Describes the message agent.v1.WriteShellStdinToolCall. * Use `create(WriteShellStdinToolCallSchema)` to create a new message. */ -export const WriteShellStdinToolCallSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 24); +export const WriteShellStdinToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 24); /** * @generated from message agent.v1.ReflectArgs */ export type ReflectArgs = Message<"agent.v1.ReflectArgs"> & { - /** - * @generated from field: string unexpected_action_outcomes = 1; - */ - unexpectedActionOutcomes: string; + /** + * @generated from field: string unexpected_action_outcomes = 1; + */ + unexpectedActionOutcomes: string; - /** - * @generated from field: string relevant_instructions = 2; - */ - relevantInstructions: string; + /** + * @generated from field: string relevant_instructions = 2; + */ + relevantInstructions: string; - /** - * @generated from field: string scenario_analysis = 3; - */ - scenarioAnalysis: string; + /** + * @generated from field: string scenario_analysis = 3; + */ + scenarioAnalysis: string; - /** - * @generated from field: string critical_synthesis = 4; - */ - criticalSynthesis: string; + /** + * @generated from field: string critical_synthesis = 4; + */ + criticalSynthesis: string; - /** - * @generated from field: string next_steps = 5; - */ - nextSteps: string; + /** + * @generated from field: string next_steps = 5; + */ + nextSteps: string; - /** - * @generated from field: string tool_call_id = 6; - */ - toolCallId: string; + /** + * @generated from field: string tool_call_id = 6; + */ + toolCallId: string; }; /** * Describes the message agent.v1.ReflectArgs. * Use `create(ReflectArgsSchema)` to create a new message. */ -export const ReflectArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 25); +export const ReflectArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 25); /** * @generated from message agent.v1.ReflectResult */ export type ReflectResult = Message<"agent.v1.ReflectResult"> & { - /** - * @generated from oneof agent.v1.ReflectResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.ReflectSuccess success = 1; - */ - value: ReflectSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.ReflectError error = 2; - */ - value: ReflectError; - case: "error"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ReflectResult.result + */ + result: { + /** + * @generated from field: agent.v1.ReflectSuccess success = 1; + */ + value: ReflectSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.ReflectError error = 2; + */ + value: ReflectError; + case: "error"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ReflectResult. * Use `create(ReflectResultSchema)` to create a new message. */ -export const ReflectResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 26); +export const ReflectResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 26); /** * @generated from message agent.v1.ReflectSuccess */ -export type ReflectSuccess = Message<"agent.v1.ReflectSuccess"> & {}; +export type ReflectSuccess = Message<"agent.v1.ReflectSuccess"> & { +}; /** * Describes the message agent.v1.ReflectSuccess. * Use `create(ReflectSuccessSchema)` to create a new message. */ -export const ReflectSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 27); +export const ReflectSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 27); /** * @generated from message agent.v1.ReflectError */ export type ReflectError = Message<"agent.v1.ReflectError"> & { - /** - * @generated from field: string error = 1; - */ - error: string; + /** + * @generated from field: string error = 1; + */ + error: string; }; /** * Describes the message agent.v1.ReflectError. * Use `create(ReflectErrorSchema)` to create a new message. */ -export const ReflectErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 28); +export const ReflectErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 28); /** * @generated from message agent.v1.ReflectToolCall */ export type ReflectToolCall = Message<"agent.v1.ReflectToolCall"> & { - /** - * @generated from field: agent.v1.ReflectArgs args = 1; - */ - args?: ReflectArgs; + /** + * @generated from field: agent.v1.ReflectArgs args = 1; + */ + args?: ReflectArgs | undefined; - /** - * @generated from field: agent.v1.ReflectResult result = 2; - */ - result?: ReflectResult; + /** + * @generated from field: agent.v1.ReflectResult result = 2; + */ + result?: ReflectResult | undefined; }; /** * Describes the message agent.v1.ReflectToolCall. * Use `create(ReflectToolCallSchema)` to create a new message. */ -export const ReflectToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 29); +export const ReflectToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 29); /** * @generated from message agent.v1.StartGrindExecutionArgs */ export type StartGrindExecutionArgs = Message<"agent.v1.StartGrindExecutionArgs"> & { - /** - * Optional explanation for why the agent is requesting to begin executing. - * - * @generated from field: optional string explanation = 1; - */ - explanation?: string; + /** + * Optional explanation for why the agent is requesting to begin executing. + * + * @generated from field: optional string explanation = 1; + */ + explanation?: string | undefined; - /** - * @generated from field: string tool_call_id = 2; - */ - toolCallId: string; + /** + * @generated from field: string tool_call_id = 2; + */ + toolCallId: string; }; /** * Describes the message agent.v1.StartGrindExecutionArgs. * Use `create(StartGrindExecutionArgsSchema)` to create a new message. */ -export const StartGrindExecutionArgsSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 30); +export const StartGrindExecutionArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 30); /** * @generated from message agent.v1.StartGrindExecutionResult */ export type StartGrindExecutionResult = Message<"agent.v1.StartGrindExecutionResult"> & { - /** - * @generated from oneof agent.v1.StartGrindExecutionResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.StartGrindExecutionSuccess success = 1; - */ - value: StartGrindExecutionSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.StartGrindExecutionError error = 2; - */ - value: StartGrindExecutionError; - case: "error"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.StartGrindExecutionResult.result + */ + result: { + /** + * @generated from field: agent.v1.StartGrindExecutionSuccess success = 1; + */ + value: StartGrindExecutionSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.StartGrindExecutionError error = 2; + */ + value: StartGrindExecutionError; + case: "error"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.StartGrindExecutionResult. * Use `create(StartGrindExecutionResultSchema)` to create a new message. */ -export const StartGrindExecutionResultSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 31); +export const StartGrindExecutionResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 31); /** * @generated from message agent.v1.StartGrindExecutionSuccess */ -export type StartGrindExecutionSuccess = Message<"agent.v1.StartGrindExecutionSuccess"> & {}; +export type StartGrindExecutionSuccess = Message<"agent.v1.StartGrindExecutionSuccess"> & { +}; /** * Describes the message agent.v1.StartGrindExecutionSuccess. * Use `create(StartGrindExecutionSuccessSchema)` to create a new message. */ -export const StartGrindExecutionSuccessSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 32); +export const StartGrindExecutionSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 32); /** * @generated from message agent.v1.StartGrindExecutionError */ export type StartGrindExecutionError = Message<"agent.v1.StartGrindExecutionError"> & { - /** - * @generated from field: string error = 1; - */ - error: string; + /** + * @generated from field: string error = 1; + */ + error: string; }; /** * Describes the message agent.v1.StartGrindExecutionError. * Use `create(StartGrindExecutionErrorSchema)` to create a new message. */ -export const StartGrindExecutionErrorSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 33); +export const StartGrindExecutionErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 33); /** * @generated from message agent.v1.StartGrindExecutionToolCall */ export type StartGrindExecutionToolCall = Message<"agent.v1.StartGrindExecutionToolCall"> & { - /** - * @generated from field: agent.v1.StartGrindExecutionArgs args = 1; - */ - args?: StartGrindExecutionArgs; + /** + * @generated from field: agent.v1.StartGrindExecutionArgs args = 1; + */ + args?: StartGrindExecutionArgs | undefined; - /** - * @generated from field: agent.v1.StartGrindExecutionResult result = 2; - */ - result?: StartGrindExecutionResult; + /** + * @generated from field: agent.v1.StartGrindExecutionResult result = 2; + */ + result?: StartGrindExecutionResult | undefined; }; /** * Describes the message agent.v1.StartGrindExecutionToolCall. * Use `create(StartGrindExecutionToolCallSchema)` to create a new message. */ -export const StartGrindExecutionToolCallSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 34); +export const StartGrindExecutionToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 34); /** * @generated from message agent.v1.StartGrindPlanningArgs */ export type StartGrindPlanningArgs = Message<"agent.v1.StartGrindPlanningArgs"> & { - /** - * Optional explanation for why the agent is requesting to return to planning. - * - * @generated from field: optional string explanation = 1; - */ - explanation?: string; + /** + * Optional explanation for why the agent is requesting to return to planning. + * + * @generated from field: optional string explanation = 1; + */ + explanation?: string | undefined; - /** - * @generated from field: string tool_call_id = 2; - */ - toolCallId: string; + /** + * @generated from field: string tool_call_id = 2; + */ + toolCallId: string; }; /** * Describes the message agent.v1.StartGrindPlanningArgs. * Use `create(StartGrindPlanningArgsSchema)` to create a new message. */ -export const StartGrindPlanningArgsSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 35); +export const StartGrindPlanningArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 35); /** * @generated from message agent.v1.StartGrindPlanningResult */ export type StartGrindPlanningResult = Message<"agent.v1.StartGrindPlanningResult"> & { - /** - * @generated from oneof agent.v1.StartGrindPlanningResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.StartGrindPlanningSuccess success = 1; - */ - value: StartGrindPlanningSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.StartGrindPlanningError error = 2; - */ - value: StartGrindPlanningError; - case: "error"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.StartGrindPlanningResult.result + */ + result: { + /** + * @generated from field: agent.v1.StartGrindPlanningSuccess success = 1; + */ + value: StartGrindPlanningSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.StartGrindPlanningError error = 2; + */ + value: StartGrindPlanningError; + case: "error"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.StartGrindPlanningResult. * Use `create(StartGrindPlanningResultSchema)` to create a new message. */ -export const StartGrindPlanningResultSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 36); +export const StartGrindPlanningResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 36); /** * @generated from message agent.v1.StartGrindPlanningSuccess */ -export type StartGrindPlanningSuccess = Message<"agent.v1.StartGrindPlanningSuccess"> & {}; +export type StartGrindPlanningSuccess = Message<"agent.v1.StartGrindPlanningSuccess"> & { +}; /** * Describes the message agent.v1.StartGrindPlanningSuccess. * Use `create(StartGrindPlanningSuccessSchema)` to create a new message. */ -export const StartGrindPlanningSuccessSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 37); +export const StartGrindPlanningSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 37); /** * @generated from message agent.v1.StartGrindPlanningError */ export type StartGrindPlanningError = Message<"agent.v1.StartGrindPlanningError"> & { - /** - * @generated from field: string error = 1; - */ - error: string; + /** + * @generated from field: string error = 1; + */ + error: string; }; /** * Describes the message agent.v1.StartGrindPlanningError. * Use `create(StartGrindPlanningErrorSchema)` to create a new message. */ -export const StartGrindPlanningErrorSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 38); +export const StartGrindPlanningErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 38); /** * @generated from message agent.v1.StartGrindPlanningToolCall */ export type StartGrindPlanningToolCall = Message<"agent.v1.StartGrindPlanningToolCall"> & { - /** - * @generated from field: agent.v1.StartGrindPlanningArgs args = 1; - */ - args?: StartGrindPlanningArgs; + /** + * @generated from field: agent.v1.StartGrindPlanningArgs args = 1; + */ + args?: StartGrindPlanningArgs | undefined; - /** - * @generated from field: agent.v1.StartGrindPlanningResult result = 2; - */ - result?: StartGrindPlanningResult; + /** + * @generated from field: agent.v1.StartGrindPlanningResult result = 2; + */ + result?: StartGrindPlanningResult | undefined; }; /** * Describes the message agent.v1.StartGrindPlanningToolCall. * Use `create(StartGrindPlanningToolCallSchema)` to create a new message. */ -export const StartGrindPlanningToolCallSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 39); +export const StartGrindPlanningToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 39); /** * var AgentMode; (function (AgentMode) { AgentMode[AgentMode["UNSPECIFIED"] = 0] = "UNSPECIFIED"; AgentMode[AgentMode["AGENT"] = 1] = "AGENT"; AgentMode[AgentMode["ASK"] = 2] = "ASK"; AgentMode[AgentMode["PLAN"] = 3] = "PLAN"; AgentMode[AgentMode["DEBUG"] = 4] = "DEBUG"; AgentMode[AgentMode["TRIAGE"] = 5] = "TRIAGE"; AgentMode[AgentMode["PROJECT"] = 6] = "PROJECT"; })(AgentMode || (AgentMode = {})); // Retrieve enum metadata with: proto3.getEnumType(AgentMode) proto3/* int32 *\/.C.util.setEnumType(AgentMode, "agent.v1.AgentMode", [ { no: 0, name: "AGENT_MODE_UNSPECIFIED" }, { no: 1, name: "AGENT_MODE_AGENT" }, { no: 2, name: "AGENT_MODE_ASK" }, { no: 3, name: "AGENT_MODE_PLAN" }, { no: 4, name: "AGENT_MODE_DEBUG" }, { no: 5, name: "AGENT_MODE_TRIAGE" }, { no: 6, name: "AGENT_MODE_PROJECT" }, ]); @@ -984,152 +985,155 @@ export const StartGrindPlanningToolCallSchema: GenMessage & { - /** - * @generated from field: string description = 1; - */ - description: string; + /** + * @generated from field: string description = 1; + */ + description: string; - /** - * @generated from field: string prompt = 2; - */ - prompt: string; + /** + * @generated from field: string prompt = 2; + */ + prompt: string; - /** - * @generated from field: agent.v1.SubagentType subagent_type = 3; - */ - subagentType?: SubagentType; + /** + * @generated from field: agent.v1.SubagentType subagent_type = 3; + */ + subagentType?: SubagentType | undefined; - /** - * @generated from field: optional string model = 4; - */ - model?: string; + /** + * @generated from field: optional string model = 4; + */ + model?: string | undefined; - /** - * @generated from field: optional string resume = 5; - */ - resume?: string; + /** + * @generated from field: optional string resume = 5; + */ + resume?: string | undefined; }; /** * Describes the message agent.v1.TaskArgs. * Use `create(TaskArgsSchema)` to create a new message. */ -export const TaskArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 40); +export const TaskArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 40); /** * @generated from message agent.v1.TaskSuccess */ export type TaskSuccess = Message<"agent.v1.TaskSuccess"> & { - /** - * @generated from field: repeated agent.v1.ConversationStep conversation_steps = 1; - */ - conversationSteps: ConversationStep[]; + /** + * @generated from field: repeated agent.v1.ConversationStep conversation_steps = 1; + */ + conversationSteps: ConversationStep[]; - /** - * @generated from field: optional string agent_id = 2; - */ - agentId?: string; + /** + * @generated from field: optional string agent_id = 2; + */ + agentId?: string | undefined; - /** - * @generated from field: bool is_background = 3; - */ - isBackground: boolean; + /** + * @generated from field: bool is_background = 3; + */ + isBackground: boolean; - /** - * @generated from field: optional uint64 duration_ms = 4; - */ - durationMs?: bigint; + /** + * @generated from field: optional uint64 duration_ms = 4; + */ + durationMs?: bigint | undefined; }; /** * Describes the message agent.v1.TaskSuccess. * Use `create(TaskSuccessSchema)` to create a new message. */ -export const TaskSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 41); +export const TaskSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 41); /** * @generated from message agent.v1.TaskError */ export type TaskError = Message<"agent.v1.TaskError"> & { - /** - * @generated from field: string error = 1; - */ - error: string; + /** + * @generated from field: string error = 1; + */ + error: string; }; /** * Describes the message agent.v1.TaskError. * Use `create(TaskErrorSchema)` to create a new message. */ -export const TaskErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 42); +export const TaskErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 42); /** * @generated from message agent.v1.TaskResult */ export type TaskResult = Message<"agent.v1.TaskResult"> & { - /** - * @generated from oneof agent.v1.TaskResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.TaskSuccess success = 1; - */ - value: TaskSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.TaskError error = 2; - */ - value: TaskError; - case: "error"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.TaskResult.result + */ + result: { + /** + * @generated from field: agent.v1.TaskSuccess success = 1; + */ + value: TaskSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.TaskError error = 2; + */ + value: TaskError; + case: "error"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.TaskResult. * Use `create(TaskResultSchema)` to create a new message. */ -export const TaskResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 43); +export const TaskResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 43); /** * @generated from message agent.v1.TaskToolCall */ export type TaskToolCall = Message<"agent.v1.TaskToolCall"> & { - /** - * @generated from field: agent.v1.TaskArgs args = 1; - */ - args?: TaskArgs; + /** + * @generated from field: agent.v1.TaskArgs args = 1; + */ + args?: TaskArgs | undefined; - /** - * @generated from field: agent.v1.TaskResult result = 2; - */ - result?: TaskResult; + /** + * @generated from field: agent.v1.TaskResult result = 2; + */ + result?: TaskResult | undefined; }; /** * Describes the message agent.v1.TaskToolCall. * Use `create(TaskToolCallSchema)` to create a new message. */ -export const TaskToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 44); +export const TaskToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 44); /** * @generated from message agent.v1.TaskToolCallDelta */ export type TaskToolCallDelta = Message<"agent.v1.TaskToolCallDelta"> & { - /** - * @generated from field: agent.v1.InteractionUpdate interaction_update = 1; - */ - interactionUpdate?: InteractionUpdate; + /** + * @generated from field: agent.v1.InteractionUpdate interaction_update = 1; + */ + interactionUpdate?: InteractionUpdate | undefined; }; /** * Describes the message agent.v1.TaskToolCallDelta. * Use `create(TaskToolCallDeltaSchema)` to create a new message. */ -export const TaskToolCallDeltaSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 45); +export const TaskToolCallDeltaSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 45); /** * Tool messages (from tool.proto) @@ -1137,317 +1141,346 @@ export const TaskToolCallDeltaSchema: GenMessage = /*@__PURE_ * @generated from message agent.v1.ToolCall */ export type ToolCall = Message<"agent.v1.ToolCall"> & { - /** - * @generated from oneof agent.v1.ToolCall.tool - */ - tool: - | { - /** - * @generated from field: agent.v1.ShellToolCall shell_tool_call = 1; - */ - value: ShellToolCall; - case: "shellToolCall"; - } - | { - /** - * @generated from field: agent.v1.DeleteToolCall delete_tool_call = 3; - */ - value: DeleteToolCall; - case: "deleteToolCall"; - } - | { - /** - * @generated from field: agent.v1.GlobToolCall glob_tool_call = 4; - */ - value: GlobToolCall; - case: "globToolCall"; - } - | { - /** - * @generated from field: agent.v1.GrepToolCall grep_tool_call = 5; - */ - value: GrepToolCall; - case: "grepToolCall"; - } - | { - /** - * @generated from field: agent.v1.ReadToolCall read_tool_call = 8; - */ - value: ReadToolCall; - case: "readToolCall"; - } - | { - /** - * @generated from field: agent.v1.UpdateTodosToolCall update_todos_tool_call = 9; - */ - value: UpdateTodosToolCall; - case: "updateTodosToolCall"; - } - | { - /** - * @generated from field: agent.v1.ReadTodosToolCall read_todos_tool_call = 10; - */ - value: ReadTodosToolCall; - case: "readTodosToolCall"; - } - | { - /** - * @generated from field: agent.v1.EditToolCall edit_tool_call = 12; - */ - value: EditToolCall; - case: "editToolCall"; - } - | { - /** - * @generated from field: agent.v1.LsToolCall ls_tool_call = 13; - */ - value: LsToolCall; - case: "lsToolCall"; - } - | { - /** - * @generated from field: agent.v1.ReadLintsToolCall read_lints_tool_call = 14; - */ - value: ReadLintsToolCall; - case: "readLintsToolCall"; - } - | { - /** - * @generated from field: agent.v1.McpToolCall mcp_tool_call = 15; - */ - value: McpToolCall; - case: "mcpToolCall"; - } - | { - /** - * @generated from field: agent.v1.SemSearchToolCall sem_search_tool_call = 16; - */ - value: SemSearchToolCall; - case: "semSearchToolCall"; - } - | { - /** - * @generated from field: agent.v1.CreatePlanToolCall create_plan_tool_call = 17; - */ - value: CreatePlanToolCall; - case: "createPlanToolCall"; - } - | { - /** - * @generated from field: agent.v1.WebSearchToolCall web_search_tool_call = 18; - */ - value: WebSearchToolCall; - case: "webSearchToolCall"; - } - | { - /** - * @generated from field: agent.v1.TaskToolCall task_tool_call = 19; - */ - value: TaskToolCall; - case: "taskToolCall"; - } - | { - /** - * @generated from field: agent.v1.ListMcpResourcesToolCall list_mcp_resources_tool_call = 20; - */ - value: ListMcpResourcesToolCall; - case: "listMcpResourcesToolCall"; - } - | { - /** - * @generated from field: agent.v1.ReadMcpResourceToolCall read_mcp_resource_tool_call = 21; - */ - value: ReadMcpResourceToolCall; - case: "readMcpResourceToolCall"; - } - | { - /** - * @generated from field: agent.v1.ApplyAgentDiffToolCall apply_agent_diff_tool_call = 22; - */ - value: ApplyAgentDiffToolCall; - case: "applyAgentDiffToolCall"; - } - | { - /** - * @generated from field: agent.v1.AskQuestionToolCall ask_question_tool_call = 23; - */ - value: AskQuestionToolCall; - case: "askQuestionToolCall"; - } - | { - /** - * @generated from field: agent.v1.FetchToolCall fetch_tool_call = 24; - */ - value: FetchToolCall; - case: "fetchToolCall"; - } - | { - /** - * @generated from field: agent.v1.SwitchModeToolCall switch_mode_tool_call = 25; - */ - value: SwitchModeToolCall; - case: "switchModeToolCall"; - } - | { - /** - * @generated from field: agent.v1.ExaSearchToolCall exa_search_tool_call = 26; - */ - value: ExaSearchToolCall; - case: "exaSearchToolCall"; - } - | { - /** - * @generated from field: agent.v1.ExaFetchToolCall exa_fetch_tool_call = 27; - */ - value: ExaFetchToolCall; - case: "exaFetchToolCall"; - } - | { - /** - * @generated from field: agent.v1.GenerateImageToolCall generate_image_tool_call = 28; - */ - value: GenerateImageToolCall; - case: "generateImageToolCall"; - } - | { - /** - * @generated from field: agent.v1.RecordScreenToolCall record_screen_tool_call = 29; - */ - value: RecordScreenToolCall; - case: "recordScreenToolCall"; - } - | { - /** - * @generated from field: agent.v1.ComputerUseToolCall computer_use_tool_call = 30; - */ - value: ComputerUseToolCall; - case: "computerUseToolCall"; - } - | { - /** - * @generated from field: agent.v1.WriteShellStdinToolCall write_shell_stdin_tool_call = 31; - */ - value: WriteShellStdinToolCall; - case: "writeShellStdinToolCall"; - } - | { - /** - * @generated from field: agent.v1.ReflectToolCall reflect_tool_call = 32; - */ - value: ReflectToolCall; - case: "reflectToolCall"; - } - | { - /** - * @generated from field: agent.v1.SetupVmEnvironmentToolCall setup_vm_environment_tool_call = 33; - */ - value: SetupVmEnvironmentToolCall; - case: "setupVmEnvironmentToolCall"; - } - | { - /** - * @generated from field: agent.v1.TruncatedToolCall truncated_tool_call = 34; - */ - value: TruncatedToolCall; - case: "truncatedToolCall"; - } - | { - /** - * @generated from field: agent.v1.StartGrindExecutionToolCall start_grind_execution_tool_call = 35; - */ - value: StartGrindExecutionToolCall; - case: "startGrindExecutionToolCall"; - } - | { - /** - * @generated from field: agent.v1.StartGrindPlanningToolCall start_grind_planning_tool_call = 36; - */ - value: StartGrindPlanningToolCall; - case: "startGrindPlanningToolCall"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ToolCall.tool + */ + tool: { + /** + * @generated from field: agent.v1.ShellToolCall shell_tool_call = 1; + */ + value: ShellToolCall; + case: "shellToolCall"; + } | { + /** + * @generated from field: agent.v1.DeleteToolCall delete_tool_call = 3; + */ + value: DeleteToolCall; + case: "deleteToolCall"; + } | { + /** + * @generated from field: agent.v1.GlobToolCall glob_tool_call = 4; + */ + value: GlobToolCall; + case: "globToolCall"; + } | { + /** + * @generated from field: agent.v1.GrepToolCall grep_tool_call = 5; + */ + value: GrepToolCall; + case: "grepToolCall"; + } | { + /** + * @generated from field: agent.v1.ReadToolCall read_tool_call = 8; + */ + value: ReadToolCall; + case: "readToolCall"; + } | { + /** + * @generated from field: agent.v1.UpdateTodosToolCall update_todos_tool_call = 9; + */ + value: UpdateTodosToolCall; + case: "updateTodosToolCall"; + } | { + /** + * @generated from field: agent.v1.ReadTodosToolCall read_todos_tool_call = 10; + */ + value: ReadTodosToolCall; + case: "readTodosToolCall"; + } | { + /** + * @generated from field: agent.v1.EditToolCall edit_tool_call = 12; + */ + value: EditToolCall; + case: "editToolCall"; + } | { + /** + * @generated from field: agent.v1.LsToolCall ls_tool_call = 13; + */ + value: LsToolCall; + case: "lsToolCall"; + } | { + /** + * @generated from field: agent.v1.ReadLintsToolCall read_lints_tool_call = 14; + */ + value: ReadLintsToolCall; + case: "readLintsToolCall"; + } | { + /** + * @generated from field: agent.v1.McpToolCall mcp_tool_call = 15; + */ + value: McpToolCall; + case: "mcpToolCall"; + } | { + /** + * @generated from field: agent.v1.SemSearchToolCall sem_search_tool_call = 16; + */ + value: SemSearchToolCall; + case: "semSearchToolCall"; + } | { + /** + * @generated from field: agent.v1.CreatePlanToolCall create_plan_tool_call = 17; + */ + value: CreatePlanToolCall; + case: "createPlanToolCall"; + } | { + /** + * @generated from field: agent.v1.WebSearchToolCall web_search_tool_call = 18; + */ + value: WebSearchToolCall; + case: "webSearchToolCall"; + } | { + /** + * @generated from field: agent.v1.TaskToolCall task_tool_call = 19; + */ + value: TaskToolCall; + case: "taskToolCall"; + } | { + /** + * @generated from field: agent.v1.ListMcpResourcesToolCall list_mcp_resources_tool_call = 20; + */ + value: ListMcpResourcesToolCall; + case: "listMcpResourcesToolCall"; + } | { + /** + * @generated from field: agent.v1.ReadMcpResourceToolCall read_mcp_resource_tool_call = 21; + */ + value: ReadMcpResourceToolCall; + case: "readMcpResourceToolCall"; + } | { + /** + * @generated from field: agent.v1.ApplyAgentDiffToolCall apply_agent_diff_tool_call = 22; + */ + value: ApplyAgentDiffToolCall; + case: "applyAgentDiffToolCall"; + } | { + /** + * @generated from field: agent.v1.AskQuestionToolCall ask_question_tool_call = 23; + */ + value: AskQuestionToolCall; + case: "askQuestionToolCall"; + } | { + /** + * @generated from field: agent.v1.FetchToolCall fetch_tool_call = 24; + */ + value: FetchToolCall; + case: "fetchToolCall"; + } | { + /** + * @generated from field: agent.v1.SwitchModeToolCall switch_mode_tool_call = 25; + */ + value: SwitchModeToolCall; + case: "switchModeToolCall"; + } | { + /** + * @generated from field: agent.v1.ExaSearchToolCall exa_search_tool_call = 26; + */ + value: ExaSearchToolCall; + case: "exaSearchToolCall"; + } | { + /** + * @generated from field: agent.v1.ExaFetchToolCall exa_fetch_tool_call = 27; + */ + value: ExaFetchToolCall; + case: "exaFetchToolCall"; + } | { + /** + * @generated from field: agent.v1.GenerateImageToolCall generate_image_tool_call = 28; + */ + value: GenerateImageToolCall; + case: "generateImageToolCall"; + } | { + /** + * @generated from field: agent.v1.RecordScreenToolCall record_screen_tool_call = 29; + */ + value: RecordScreenToolCall; + case: "recordScreenToolCall"; + } | { + /** + * @generated from field: agent.v1.ComputerUseToolCall computer_use_tool_call = 30; + */ + value: ComputerUseToolCall; + case: "computerUseToolCall"; + } | { + /** + * @generated from field: agent.v1.WriteShellStdinToolCall write_shell_stdin_tool_call = 31; + */ + value: WriteShellStdinToolCall; + case: "writeShellStdinToolCall"; + } | { + /** + * @generated from field: agent.v1.ReflectToolCall reflect_tool_call = 32; + */ + value: ReflectToolCall; + case: "reflectToolCall"; + } | { + /** + * @generated from field: agent.v1.SetupVmEnvironmentToolCall setup_vm_environment_tool_call = 33; + */ + value: SetupVmEnvironmentToolCall; + case: "setupVmEnvironmentToolCall"; + } | { + /** + * @generated from field: agent.v1.TruncatedToolCall truncated_tool_call = 34; + */ + value: TruncatedToolCall; + case: "truncatedToolCall"; + } | { + /** + * @generated from field: agent.v1.StartGrindExecutionToolCall start_grind_execution_tool_call = 35; + */ + value: StartGrindExecutionToolCall; + case: "startGrindExecutionToolCall"; + } | { + /** + * @generated from field: agent.v1.StartGrindPlanningToolCall start_grind_planning_tool_call = 36; + */ + value: StartGrindPlanningToolCall; + case: "startGrindPlanningToolCall"; + } | { + /** + * @generated from field: agent.v1.PiReadToolCall pi_read_tool_call = 61; + */ + value: PiReadToolCall; + case: "piReadToolCall"; + } | { + /** + * @generated from field: agent.v1.PiBashToolCall pi_bash_tool_call = 62; + */ + value: PiBashToolCall; + case: "piBashToolCall"; + } | { + /** + * @generated from field: agent.v1.PiEditToolCall pi_edit_tool_call = 63; + */ + value: PiEditToolCall; + case: "piEditToolCall"; + } | { + /** + * @generated from field: agent.v1.PiWriteToolCall pi_write_tool_call = 64; + */ + value: PiWriteToolCall; + case: "piWriteToolCall"; + } | { + /** + * @generated from field: agent.v1.PiGrepToolCall pi_grep_tool_call = 65; + */ + value: PiGrepToolCall; + case: "piGrepToolCall"; + } | { + /** + * @generated from field: agent.v1.PiFindToolCall pi_find_tool_call = 66; + */ + value: PiFindToolCall; + case: "piFindToolCall"; + } | { + /** + * @generated from field: agent.v1.PiLsToolCall pi_ls_tool_call = 67; + */ + value: PiLsToolCall; + case: "piLsToolCall"; + } | { + /** + * @generated from field: agent.v1.ConnectScmToolCall connect_scm_tool_call = 68; + */ + value: ConnectScmToolCall; + case: "connectScmToolCall"; + } | { + /** + * @generated from field: agent.v1.SearchConversationsToolCall search_conversations_tool_call = 69; + */ + value: SearchConversationsToolCall; + case: "searchConversationsToolCall"; + } | { case: undefined; value?: undefined }; + + /** + * Modern builds carry the call id on the envelope instead of inside each + * variant's args. Fields 37-53, 55, 56, 58 (further tool variants) and 54 + * (hook_additional_contexts), 59/60 (started_at_ms/completed_at_ms) are not + * modelled and decode into unknown fields; do not reuse those numbers. + * + * @generated from field: optional string tool_call_id = 57; + */ + toolCallId?: string | undefined; }; /** * Describes the message agent.v1.ToolCall. * Use `create(ToolCallSchema)` to create a new message. */ -export const ToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 46); +export const ToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 46); /** * @generated from message agent.v1.TruncatedToolCallArgs */ -export type TruncatedToolCallArgs = Message<"agent.v1.TruncatedToolCallArgs"> & {}; +export type TruncatedToolCallArgs = Message<"agent.v1.TruncatedToolCallArgs"> & { +}; /** * Describes the message agent.v1.TruncatedToolCallArgs. * Use `create(TruncatedToolCallArgsSchema)` to create a new message. */ -export const TruncatedToolCallArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 47); +export const TruncatedToolCallArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 47); /** * @generated from message agent.v1.TruncatedToolCallSuccess */ -export type TruncatedToolCallSuccess = Message<"agent.v1.TruncatedToolCallSuccess"> & {}; +export type TruncatedToolCallSuccess = Message<"agent.v1.TruncatedToolCallSuccess"> & { +}; /** * Describes the message agent.v1.TruncatedToolCallSuccess. * Use `create(TruncatedToolCallSuccessSchema)` to create a new message. */ -export const TruncatedToolCallSuccessSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 48); +export const TruncatedToolCallSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 48); /** * @generated from message agent.v1.TruncatedToolCallError */ export type TruncatedToolCallError = Message<"agent.v1.TruncatedToolCallError"> & { - /** - * @generated from field: string error = 1; - */ - error: string; + /** + * @generated from field: string error = 1; + */ + error: string; }; /** * Describes the message agent.v1.TruncatedToolCallError. * Use `create(TruncatedToolCallErrorSchema)` to create a new message. */ -export const TruncatedToolCallErrorSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 49); +export const TruncatedToolCallErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 49); /** * @generated from message agent.v1.TruncatedToolCallResult */ export type TruncatedToolCallResult = Message<"agent.v1.TruncatedToolCallResult"> & { - /** - * @generated from oneof agent.v1.TruncatedToolCallResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.TruncatedToolCallSuccess success = 1; - */ - value: TruncatedToolCallSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.TruncatedToolCallError error = 2; - */ - value: TruncatedToolCallError; - case: "error"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.TruncatedToolCallResult.result + */ + result: { + /** + * @generated from field: agent.v1.TruncatedToolCallSuccess success = 1; + */ + value: TruncatedToolCallSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.TruncatedToolCallError error = 2; + */ + value: TruncatedToolCallError; + case: "error"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.TruncatedToolCallResult. * Use `create(TruncatedToolCallResultSchema)` to create a new message. */ -export const TruncatedToolCallResultSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 50); +export const TruncatedToolCallResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 50); /** * Placeholder for tool calls that were truncated due to size limits. @@ -1455,1444 +1488,1441 @@ export const TruncatedToolCallResultSchema: GenMessage * @generated from message agent.v1.TruncatedToolCall */ export type TruncatedToolCall = Message<"agent.v1.TruncatedToolCall"> & { - /** - * @generated from field: bytes original_step_blob_id = 1; - */ - originalStepBlobId: Uint8Array; + /** + * @generated from field: bytes original_step_blob_id = 1; + */ + originalStepBlobId: Uint8Array; - /** - * unused, just matches the discriminated union for other tool calls - * - * @generated from field: agent.v1.TruncatedToolCallArgs args = 2; - */ - args?: TruncatedToolCallArgs; + /** + * unused, just matches the discriminated union for other tool calls + * + * @generated from field: agent.v1.TruncatedToolCallArgs args = 2; + */ + args?: TruncatedToolCallArgs | undefined; - /** - * @generated from field: agent.v1.TruncatedToolCallResult result = 3; - */ - result?: TruncatedToolCallResult; + /** + * @generated from field: agent.v1.TruncatedToolCallResult result = 3; + */ + result?: TruncatedToolCallResult | undefined; }; /** * Describes the message agent.v1.TruncatedToolCall. * Use `create(TruncatedToolCallSchema)` to create a new message. */ -export const TruncatedToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 51); +export const TruncatedToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 51); /** * @generated from message agent.v1.ToolCallDelta */ export type ToolCallDelta = Message<"agent.v1.ToolCallDelta"> & { - /** - * @generated from oneof agent.v1.ToolCallDelta.delta - */ - delta: - | { - /** - * @generated from field: agent.v1.ShellToolCallDelta shell_tool_call_delta = 1; - */ - value: ShellToolCallDelta; - case: "shellToolCallDelta"; - } - | { - /** - * @generated from field: agent.v1.TaskToolCallDelta task_tool_call_delta = 2; - */ - value: TaskToolCallDelta; - case: "taskToolCallDelta"; - } - | { - /** - * @generated from field: agent.v1.EditToolCallDelta edit_tool_call_delta = 3; - */ - value: EditToolCallDelta; - case: "editToolCallDelta"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ToolCallDelta.delta + */ + delta: { + /** + * @generated from field: agent.v1.ShellToolCallDelta shell_tool_call_delta = 1; + */ + value: ShellToolCallDelta; + case: "shellToolCallDelta"; + } | { + /** + * @generated from field: agent.v1.TaskToolCallDelta task_tool_call_delta = 2; + */ + value: TaskToolCallDelta; + case: "taskToolCallDelta"; + } | { + /** + * @generated from field: agent.v1.EditToolCallDelta edit_tool_call_delta = 3; + */ + value: EditToolCallDelta; + case: "editToolCallDelta"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ToolCallDelta. * Use `create(ToolCallDeltaSchema)` to create a new message. */ -export const ToolCallDeltaSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 52); +export const ToolCallDeltaSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 52); /** * @generated from message agent.v1.ConversationStep */ export type ConversationStep = Message<"agent.v1.ConversationStep"> & { - /** - * @generated from oneof agent.v1.ConversationStep.message - */ - message: - | { - /** - * @generated from field: agent.v1.AssistantMessage assistant_message = 1; - */ - value: AssistantMessage; - case: "assistantMessage"; - } - | { - /** - * @generated from field: agent.v1.ToolCall tool_call = 2; - */ - value: ToolCall; - case: "toolCall"; - } - | { - /** - * @generated from field: agent.v1.ThinkingMessage thinking_message = 3; - */ - value: ThinkingMessage; - case: "thinkingMessage"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ConversationStep.message + */ + message: { + /** + * @generated from field: agent.v1.AssistantMessage assistant_message = 1; + */ + value: AssistantMessage; + case: "assistantMessage"; + } | { + /** + * @generated from field: agent.v1.ToolCall tool_call = 2; + */ + value: ToolCall; + case: "toolCall"; + } | { + /** + * @generated from field: agent.v1.ThinkingMessage thinking_message = 3; + */ + value: ThinkingMessage; + case: "thinkingMessage"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ConversationStep. * Use `create(ConversationStepSchema)` to create a new message. */ -export const ConversationStepSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 53); +export const ConversationStepSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 53); /** * @generated from message agent.v1.ConversationAction */ export type ConversationAction = Message<"agent.v1.ConversationAction"> & { - /** - * @generated from oneof agent.v1.ConversationAction.action - */ - action: - | { - /** - * @generated from field: agent.v1.UserMessageAction user_message_action = 1; - */ - value: UserMessageAction; - case: "userMessageAction"; - } - | { - /** - * @generated from field: agent.v1.ResumeAction resume_action = 2; - */ - value: ResumeAction; - case: "resumeAction"; - } - | { - /** - * @generated from field: agent.v1.CancelAction cancel_action = 3; - */ - value: CancelAction; - case: "cancelAction"; - } - | { - /** - * @generated from field: agent.v1.SummarizeAction summarize_action = 4; - */ - value: SummarizeAction; - case: "summarizeAction"; - } - | { - /** - * @generated from field: agent.v1.ShellCommandAction shell_command_action = 5; - */ - value: ShellCommandAction; - case: "shellCommandAction"; - } - | { - /** - * @generated from field: agent.v1.StartPlanAction start_plan_action = 6; - */ - value: StartPlanAction; - case: "startPlanAction"; - } - | { - /** - * @generated from field: agent.v1.ExecutePlanAction execute_plan_action = 7; - */ - value: ExecutePlanAction; - case: "executePlanAction"; - } - | { - /** - * @generated from field: agent.v1.AsyncAskQuestionCompletionAction async_ask_question_completion_action = 8; - */ - value: AsyncAskQuestionCompletionAction; - case: "asyncAskQuestionCompletionAction"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ConversationAction.action + */ + action: { + /** + * @generated from field: agent.v1.UserMessageAction user_message_action = 1; + */ + value: UserMessageAction; + case: "userMessageAction"; + } | { + /** + * @generated from field: agent.v1.ResumeAction resume_action = 2; + */ + value: ResumeAction; + case: "resumeAction"; + } | { + /** + * @generated from field: agent.v1.CancelAction cancel_action = 3; + */ + value: CancelAction; + case: "cancelAction"; + } | { + /** + * @generated from field: agent.v1.SummarizeAction summarize_action = 4; + */ + value: SummarizeAction; + case: "summarizeAction"; + } | { + /** + * @generated from field: agent.v1.ShellCommandAction shell_command_action = 5; + */ + value: ShellCommandAction; + case: "shellCommandAction"; + } | { + /** + * @generated from field: agent.v1.StartPlanAction start_plan_action = 6; + */ + value: StartPlanAction; + case: "startPlanAction"; + } | { + /** + * @generated from field: agent.v1.ExecutePlanAction execute_plan_action = 7; + */ + value: ExecutePlanAction; + case: "executePlanAction"; + } | { + /** + * @generated from field: agent.v1.AsyncAskQuestionCompletionAction async_ask_question_completion_action = 8; + */ + value: AsyncAskQuestionCompletionAction; + case: "asyncAskQuestionCompletionAction"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ConversationAction. * Use `create(ConversationActionSchema)` to create a new message. */ -export const ConversationActionSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 54); +export const ConversationActionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 54); /** * @generated from message agent.v1.UserMessageAction */ export type UserMessageAction = Message<"agent.v1.UserMessageAction"> & { - /** - * @generated from field: agent.v1.UserMessage user_message = 1; - */ - userMessage?: UserMessage; + /** + * @generated from field: agent.v1.UserMessage user_message = 1; + */ + userMessage?: UserMessage | undefined; - /** - * @generated from field: agent.v1.RequestContext request_context = 2; - */ - requestContext?: RequestContext; + /** + * @generated from field: agent.v1.RequestContext request_context = 2; + */ + requestContext?: RequestContext | undefined; - /** - * @generated from field: optional bool send_to_interaction_listener = 3; - */ - sendToInteractionListener?: boolean; + /** + * @generated from field: optional bool send_to_interaction_listener = 3; + */ + sendToInteractionListener?: boolean | undefined; }; /** * Describes the message agent.v1.UserMessageAction. * Use `create(UserMessageActionSchema)` to create a new message. */ -export const UserMessageActionSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 55); +export const UserMessageActionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 55); /** * @generated from message agent.v1.CancelAction */ -export type CancelAction = Message<"agent.v1.CancelAction"> & {}; +export type CancelAction = Message<"agent.v1.CancelAction"> & { +}; /** * Describes the message agent.v1.CancelAction. * Use `create(CancelActionSchema)` to create a new message. */ -export const CancelActionSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 56); +export const CancelActionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 56); /** * @generated from message agent.v1.ResumeAction */ export type ResumeAction = Message<"agent.v1.ResumeAction"> & { - /** - * @generated from field: agent.v1.RequestContext request_context = 2; - */ - requestContext?: RequestContext; + /** + * @generated from field: agent.v1.RequestContext request_context = 2; + */ + requestContext?: RequestContext | undefined; }; /** * Describes the message agent.v1.ResumeAction. * Use `create(ResumeActionSchema)` to create a new message. */ -export const ResumeActionSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 57); +export const ResumeActionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 57); /** * @generated from message agent.v1.AsyncAskQuestionCompletionAction */ export type AsyncAskQuestionCompletionAction = Message<"agent.v1.AsyncAskQuestionCompletionAction"> & { - /** - * Contains the original tool call ID and the result from the user - * - * @generated from field: string original_tool_call_id = 1; - */ - originalToolCallId: string; + /** + * Contains the original tool call ID and the result from the user + * + * @generated from field: string original_tool_call_id = 1; + */ + originalToolCallId: string; - /** - * @generated from field: agent.v1.AskQuestionArgs original_args = 2; - */ - originalArgs?: AskQuestionArgs; + /** + * @generated from field: agent.v1.AskQuestionArgs original_args = 2; + */ + originalArgs?: AskQuestionArgs | undefined; - /** - * @generated from field: agent.v1.AskQuestionResult result = 3; - */ - result?: AskQuestionResult; + /** + * @generated from field: agent.v1.AskQuestionResult result = 3; + */ + result?: AskQuestionResult | undefined; }; /** * Describes the message agent.v1.AsyncAskQuestionCompletionAction. * Use `create(AsyncAskQuestionCompletionActionSchema)` to create a new message. */ -export const AsyncAskQuestionCompletionActionSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 58); +export const AsyncAskQuestionCompletionActionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 58); /** * @generated from message agent.v1.SummarizeAction */ -export type SummarizeAction = Message<"agent.v1.SummarizeAction"> & {}; +export type SummarizeAction = Message<"agent.v1.SummarizeAction"> & { +}; /** * Describes the message agent.v1.SummarizeAction. * Use `create(SummarizeActionSchema)` to create a new message. */ -export const SummarizeActionSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 59); +export const SummarizeActionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 59); /** * @generated from message agent.v1.ShellCommandAction */ export type ShellCommandAction = Message<"agent.v1.ShellCommandAction"> & { - /** - * @generated from field: agent.v1.ShellCommand shell_command = 1; - */ - shellCommand?: ShellCommand; + /** + * @generated from field: agent.v1.ShellCommand shell_command = 1; + */ + shellCommand?: ShellCommand | undefined; - /** - * unique identifier for preemptive exec attachment - * - * @generated from field: string exec_id = 2; - */ - execId: string; + /** + * unique identifier for preemptive exec attachment + * + * @generated from field: string exec_id = 2; + */ + execId: string; }; /** * Describes the message agent.v1.ShellCommandAction. * Use `create(ShellCommandActionSchema)` to create a new message. */ -export const ShellCommandActionSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 60); +export const ShellCommandActionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 60); /** * @generated from message agent.v1.StartPlanAction */ export type StartPlanAction = Message<"agent.v1.StartPlanAction"> & { - /** - * @generated from field: agent.v1.UserMessage user_message = 1; - */ - userMessage?: UserMessage; + /** + * @generated from field: agent.v1.UserMessage user_message = 1; + */ + userMessage?: UserMessage | undefined; - /** - * @generated from field: agent.v1.RequestContext request_context = 2; - */ - requestContext?: RequestContext; + /** + * @generated from field: agent.v1.RequestContext request_context = 2; + */ + requestContext?: RequestContext | undefined; - /** - * @generated from field: bool is_spec = 3; - */ - isSpec: boolean; + /** + * @generated from field: bool is_spec = 3; + */ + isSpec: boolean; }; /** * Describes the message agent.v1.StartPlanAction. * Use `create(StartPlanActionSchema)` to create a new message. */ -export const StartPlanActionSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 61); +export const StartPlanActionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 61); /** * @generated from message agent.v1.ExecutePlanAction */ export type ExecutePlanAction = Message<"agent.v1.ExecutePlanAction"> & { - /** - * @generated from field: agent.v1.RequestContext request_context = 1; - */ - requestContext?: RequestContext; + /** + * @generated from field: agent.v1.RequestContext request_context = 1; + */ + requestContext?: RequestContext | undefined; - /** - * @generated from field: optional agent.v1.ConversationPlan plan = 2; - */ - plan?: ConversationPlan; + /** + * @generated from field: optional agent.v1.ConversationPlan plan = 2; + */ + plan?: ConversationPlan | undefined; - /** - * e.g., "cursor-plan://composerId/plan.md" - * - * @generated from field: optional string plan_file_uri = 3; - */ - planFileUri?: string; + /** + * e.g., "cursor-plan://composerId/plan.md" + * + * @generated from field: optional string plan_file_uri = 3; + */ + planFileUri?: string | undefined; - /** - * The actual plan content from the file - * - * @generated from field: optional string plan_file_content = 4; - */ - planFileContent?: string; + /** + * The actual plan content from the file + * + * @generated from field: optional string plan_file_content = 4; + */ + planFileContent?: string | undefined; }; /** * Describes the message agent.v1.ExecutePlanAction. * Use `create(ExecutePlanActionSchema)` to create a new message. */ -export const ExecutePlanActionSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 62); +export const ExecutePlanActionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 62); /** * @generated from message agent.v1.UserMessage */ export type UserMessage = Message<"agent.v1.UserMessage"> & { - /** - * @generated from field: string text = 1; - */ - text: string; + /** + * @generated from field: string text = 1; + */ + text: string; - /** - * @generated from field: string message_id = 2; - */ - messageId: string; + /** + * @generated from field: string message_id = 2; + */ + messageId: string; - /** - * @generated from field: optional agent.v1.SelectedContext selected_context = 3; - */ - selectedContext?: SelectedContext; + /** + * @generated from field: optional agent.v1.SelectedContext selected_context = 3; + */ + selectedContext?: SelectedContext | undefined; - /** - * @generated from field: int32 mode = 4; - */ - mode: number; + /** + * @generated from field: int32 mode = 4; + */ + mode: number; - /** - * @generated from field: optional bool is_simulated_msg = 5; - */ - isSimulatedMsg?: boolean; + /** + * @generated from field: optional bool is_simulated_msg = 5; + */ + isSimulatedMsg?: boolean | undefined; - /** - * @generated from field: optional string best_of_n_group_id = 6; - */ - bestOfNGroupId?: string; + /** + * @generated from field: optional string best_of_n_group_id = 6; + */ + bestOfNGroupId?: string | undefined; - /** - * @generated from field: optional bool try_use_best_of_n_promotion = 7; - */ - tryUseBestOfNPromotion?: boolean; + /** + * @generated from field: optional bool try_use_best_of_n_promotion = 7; + */ + tryUseBestOfNPromotion?: boolean | undefined; - /** - * @generated from field: optional string rich_text = 8; - */ - richText?: string; + /** + * @generated from field: optional string rich_text = 8; + */ + richText?: string | undefined; }; /** * Describes the message agent.v1.UserMessage. * Use `create(UserMessageSchema)` to create a new message. */ -export const UserMessageSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 63); +export const UserMessageSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 63); /** * @generated from message agent.v1.AssistantMessage */ export type AssistantMessage = Message<"agent.v1.AssistantMessage"> & { - /** - * @generated from field: string text = 1; - */ - text: string; + /** + * @generated from field: string text = 1; + */ + text: string; }; /** * Describes the message agent.v1.AssistantMessage. * Use `create(AssistantMessageSchema)` to create a new message. */ -export const AssistantMessageSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 64); +export const AssistantMessageSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 64); /** * @generated from message agent.v1.ThinkingMessage */ export type ThinkingMessage = Message<"agent.v1.ThinkingMessage"> & { - /** - * @generated from field: string text = 1; - */ - text: string; + /** + * @generated from field: string text = 1; + */ + text: string; - /** - * @generated from field: uint32 duration_ms = 2; - */ - durationMs: number; + /** + * @generated from field: uint32 duration_ms = 2; + */ + durationMs: number; }; /** * Describes the message agent.v1.ThinkingMessage. * Use `create(ThinkingMessageSchema)` to create a new message. */ -export const ThinkingMessageSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 65); +export const ThinkingMessageSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 65); /** * @generated from message agent.v1.ShellCommand */ export type ShellCommand = Message<"agent.v1.ShellCommand"> & { - /** - * @generated from field: string command = 1; - */ - command: string; + /** + * @generated from field: string command = 1; + */ + command: string; }; /** * Describes the message agent.v1.ShellCommand. * Use `create(ShellCommandSchema)` to create a new message. */ -export const ShellCommandSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 66); +export const ShellCommandSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 66); /** * @generated from message agent.v1.ShellOutput */ export type ShellOutput = Message<"agent.v1.ShellOutput"> & { - /** - * @generated from field: string stdout = 1; - */ - stdout: string; + /** + * @generated from field: string stdout = 1; + */ + stdout: string; - /** - * @generated from field: string stderr = 2; - */ - stderr: string; + /** + * @generated from field: string stderr = 2; + */ + stderr: string; - /** - * @generated from field: int32 exit_code = 3; - */ - exitCode: number; + /** + * @generated from field: int32 exit_code = 3; + */ + exitCode: number; }; /** * Describes the message agent.v1.ShellOutput. * Use `create(ShellOutputSchema)` to create a new message. */ -export const ShellOutputSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 67); +export const ShellOutputSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 67); /** * @generated from message agent.v1.ConversationTurn */ export type ConversationTurn = Message<"agent.v1.ConversationTurn"> & { - /** - * @generated from oneof agent.v1.ConversationTurn.turn - */ - turn: - | { - /** - * @generated from field: agent.v1.AgentConversationTurn agent_conversation_turn = 1; - */ - value: AgentConversationTurn; - case: "agentConversationTurn"; - } - | { - /** - * @generated from field: agent.v1.ShellConversationTurn shell_conversation_turn = 2; - */ - value: ShellConversationTurn; - case: "shellConversationTurn"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ConversationTurn.turn + */ + turn: { + /** + * @generated from field: agent.v1.AgentConversationTurn agent_conversation_turn = 1; + */ + value: AgentConversationTurn; + case: "agentConversationTurn"; + } | { + /** + * @generated from field: agent.v1.ShellConversationTurn shell_conversation_turn = 2; + */ + value: ShellConversationTurn; + case: "shellConversationTurn"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ConversationTurn. * Use `create(ConversationTurnSchema)` to create a new message. */ -export const ConversationTurnSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 68); +export const ConversationTurnSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 68); /** * @generated from message agent.v1.ConversationPlan */ export type ConversationPlan = Message<"agent.v1.ConversationPlan"> & { - /** - * @generated from field: string plan = 1; - */ - plan: string; + /** + * @generated from field: string plan = 1; + */ + plan: string; }; /** * Describes the message agent.v1.ConversationPlan. * Use `create(ConversationPlanSchema)` to create a new message. */ -export const ConversationPlanSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 69); +export const ConversationPlanSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 69); /** * @generated from message agent.v1.ConversationTurnStructure */ export type ConversationTurnStructure = Message<"agent.v1.ConversationTurnStructure"> & { - /** - * @generated from oneof agent.v1.ConversationTurnStructure.turn - */ - turn: - | { - /** - * @generated from field: agent.v1.AgentConversationTurnStructure agent_conversation_turn = 1; - */ - value: AgentConversationTurnStructure; - case: "agentConversationTurn"; - } - | { - /** - * @generated from field: agent.v1.ShellConversationTurnStructure shell_conversation_turn = 2; - */ - value: ShellConversationTurnStructure; - case: "shellConversationTurn"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ConversationTurnStructure.turn + */ + turn: { + /** + * @generated from field: agent.v1.AgentConversationTurnStructure agent_conversation_turn = 1; + */ + value: AgentConversationTurnStructure; + case: "agentConversationTurn"; + } | { + /** + * @generated from field: agent.v1.ShellConversationTurnStructure shell_conversation_turn = 2; + */ + value: ShellConversationTurnStructure; + case: "shellConversationTurn"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ConversationTurnStructure. * Use `create(ConversationTurnStructureSchema)` to create a new message. */ -export const ConversationTurnStructureSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 70); +export const ConversationTurnStructureSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 70); /** * @generated from message agent.v1.AgentConversationTurn */ export type AgentConversationTurn = Message<"agent.v1.AgentConversationTurn"> & { - /** - * @generated from field: agent.v1.UserMessage user_message = 1; - */ - userMessage?: UserMessage; + /** + * @generated from field: agent.v1.UserMessage user_message = 1; + */ + userMessage?: UserMessage | undefined; - /** - * @generated from field: repeated agent.v1.ConversationStep steps = 2; - */ - steps: ConversationStep[]; + /** + * @generated from field: repeated agent.v1.ConversationStep steps = 2; + */ + steps: ConversationStep[]; - /** - * The request ID associated with this turn, used for analytics tracking - * - * @generated from field: optional string request_id = 3; - */ - requestId?: string; + /** + * The request ID associated with this turn, used for analytics tracking + * + * @generated from field: optional string request_id = 3; + */ + requestId?: string | undefined; }; /** * Describes the message agent.v1.AgentConversationTurn. * Use `create(AgentConversationTurnSchema)` to create a new message. */ -export const AgentConversationTurnSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 71); +export const AgentConversationTurnSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 71); /** * @generated from message agent.v1.AgentConversationTurnStructure */ export type AgentConversationTurnStructure = Message<"agent.v1.AgentConversationTurnStructure"> & { - /** - * @generated from field: bytes user_message = 1; - */ - userMessage: Uint8Array; + /** + * @generated from field: bytes user_message = 1; + */ + userMessage: Uint8Array; - /** - * @generated from field: repeated bytes steps = 2; - */ - steps: Uint8Array[]; + /** + * @generated from field: repeated bytes steps = 2; + */ + steps: Uint8Array[]; - /** - * The request ID associated with this turn, used for analytics tracking - * - * @generated from field: optional string request_id = 3; - */ - requestId?: string; + /** + * The request ID associated with this turn, used for analytics tracking + * + * @generated from field: optional string request_id = 3; + */ + requestId?: string | undefined; }; /** * Describes the message agent.v1.AgentConversationTurnStructure. * Use `create(AgentConversationTurnStructureSchema)` to create a new message. */ -export const AgentConversationTurnStructureSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 72); +export const AgentConversationTurnStructureSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 72); /** * @generated from message agent.v1.ShellConversationTurn */ export type ShellConversationTurn = Message<"agent.v1.ShellConversationTurn"> & { - /** - * @generated from field: agent.v1.ShellCommand shell_command = 1; - */ - shellCommand?: ShellCommand; + /** + * @generated from field: agent.v1.ShellCommand shell_command = 1; + */ + shellCommand?: ShellCommand | undefined; - /** - * @generated from field: agent.v1.ShellOutput shell_output = 2; - */ - shellOutput?: ShellOutput; + /** + * @generated from field: agent.v1.ShellOutput shell_output = 2; + */ + shellOutput?: ShellOutput | undefined; }; /** * Describes the message agent.v1.ShellConversationTurn. * Use `create(ShellConversationTurnSchema)` to create a new message. */ -export const ShellConversationTurnSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 73); +export const ShellConversationTurnSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 73); /** * @generated from message agent.v1.ShellConversationTurnStructure */ export type ShellConversationTurnStructure = Message<"agent.v1.ShellConversationTurnStructure"> & { - /** - * @generated from field: bytes shell_command = 1; - */ - shellCommand: Uint8Array; + /** + * @generated from field: bytes shell_command = 1; + */ + shellCommand: Uint8Array; - /** - * @generated from field: bytes shell_output = 2; - */ - shellOutput: Uint8Array; + /** + * @generated from field: bytes shell_output = 2; + */ + shellOutput: Uint8Array; }; /** * Describes the message agent.v1.ShellConversationTurnStructure. * Use `create(ShellConversationTurnStructureSchema)` to create a new message. */ -export const ShellConversationTurnStructureSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 74); +export const ShellConversationTurnStructureSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 74); /** * @generated from message agent.v1.ConversationSummary */ export type ConversationSummary = Message<"agent.v1.ConversationSummary"> & { - /** - * @generated from field: string summary = 1; - */ - summary: string; + /** + * @generated from field: string summary = 1; + */ + summary: string; }; /** * Describes the message agent.v1.ConversationSummary. * Use `create(ConversationSummarySchema)` to create a new message. */ -export const ConversationSummarySchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 75); +export const ConversationSummarySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 75); /** * @generated from message agent.v1.ConversationSummaryArchive */ export type ConversationSummaryArchive = Message<"agent.v1.ConversationSummaryArchive"> & { - /** - * @generated from field: repeated bytes summarized_messages = 1; - */ - summarizedMessages: Uint8Array[]; + /** + * @generated from field: repeated bytes summarized_messages = 1; + */ + summarizedMessages: Uint8Array[]; - /** - * @generated from field: string summary = 2; - */ - summary: string; + /** + * @generated from field: string summary = 2; + */ + summary: string; - /** - * @generated from field: uint32 window_tail = 3; - */ - windowTail: number; + /** + * @generated from field: uint32 window_tail = 3; + */ + windowTail: number; - /** - * @generated from field: bytes summary_message = 4; - */ - summaryMessage: Uint8Array; + /** + * @generated from field: bytes summary_message = 4; + */ + summaryMessage: Uint8Array; }; /** * Describes the message agent.v1.ConversationSummaryArchive. * Use `create(ConversationSummaryArchiveSchema)` to create a new message. */ -export const ConversationSummaryArchiveSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 76); +export const ConversationSummaryArchiveSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 76); /** * @generated from message agent.v1.ConversationTokenDetails */ export type ConversationTokenDetails = Message<"agent.v1.ConversationTokenDetails"> & { - /** - * @generated from field: uint32 used_tokens = 1; - */ - usedTokens: number; + /** + * @generated from field: uint32 used_tokens = 1; + */ + usedTokens: number; - /** - * @generated from field: uint32 max_tokens = 2; - */ - maxTokens: number; + /** + * @generated from field: uint32 max_tokens = 2; + */ + maxTokens: number; }; /** * Describes the message agent.v1.ConversationTokenDetails. * Use `create(ConversationTokenDetailsSchema)` to create a new message. */ -export const ConversationTokenDetailsSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 77); +export const ConversationTokenDetailsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 77); /** * @generated from message agent.v1.FileState */ export type FileState = Message<"agent.v1.FileState"> & { - /** - * Optional content. If not set or undefined, the file is considered deleted. - * - * @generated from field: optional string content = 1; - */ - content?: string; + /** + * Optional content. If not set or undefined, the file is considered deleted. + * + * @generated from field: optional string content = 1; + */ + content?: string | undefined; - /** - * Optional initial content captured when the file was first tracked. If not set or undefined, the file did not exist when tracking began. - * - * @generated from field: optional string initial_content = 2; - */ - initialContent?: string; + /** + * Optional initial content captured when the file was first tracked. If not set or undefined, the file did not exist when tracking began. + * + * @generated from field: optional string initial_content = 2; + */ + initialContent?: string | undefined; }; /** * Describes the message agent.v1.FileState. * Use `create(FileStateSchema)` to create a new message. */ -export const FileStateSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 78); +export const FileStateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 78); /** * @generated from message agent.v1.FileStateStructure */ export type FileStateStructure = Message<"agent.v1.FileStateStructure"> & { - /** - * Optional content. If not set or undefined, the file is considered deleted. - * - * @generated from field: optional bytes content = 1; - */ - content?: Uint8Array; + /** + * Optional content. If not set or undefined, the file is considered deleted. + * + * @generated from field: optional bytes content = 1; + */ + content?: Uint8Array | undefined; - /** - * Optional initial content captured when the file was first tracked. If not set or undefined, the file did not exist when tracking began. - * - * @generated from field: optional bytes initial_content = 2; - */ - initialContent?: Uint8Array; + /** + * Optional initial content captured when the file was first tracked. If not set or undefined, the file did not exist when tracking began. + * + * @generated from field: optional bytes initial_content = 2; + */ + initialContent?: Uint8Array | undefined; }; /** * Describes the message agent.v1.FileStateStructure. * Use `create(FileStateStructureSchema)` to create a new message. */ -export const FileStateStructureSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 79); +export const FileStateStructureSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 79); /** * @generated from message agent.v1.StepTiming */ export type StepTiming = Message<"agent.v1.StepTiming"> & { - /** - * @generated from field: uint64 duration_ms = 1; - */ - durationMs: bigint; + /** + * @generated from field: uint64 duration_ms = 1; + */ + durationMs: bigint; - /** - * @generated from field: uint64 timestamp_ms = 2; - */ - timestampMs: bigint; + /** + * @generated from field: uint64 timestamp_ms = 2; + */ + timestampMs: bigint; }; /** * Describes the message agent.v1.StepTiming. * Use `create(StepTimingSchema)` to create a new message. */ -export const StepTimingSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 80); +export const StepTimingSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 80); /** * @generated from message agent.v1.ConversationState */ export type ConversationState = Message<"agent.v1.ConversationState"> & { - /** - * @generated from field: repeated string root_prompt_messages_json = 1; - */ - rootPromptMessagesJson: string[]; + /** + * @generated from field: repeated string root_prompt_messages_json = 1; + */ + rootPromptMessagesJson: string[]; - /** - * @generated from field: repeated agent.v1.ConversationTurn turns = 8; - */ - turns: ConversationTurn[]; + /** + * @generated from field: repeated agent.v1.ConversationTurn turns = 8; + */ + turns: ConversationTurn[]; - /** - * @generated from field: repeated agent.v1.TodoItem todos = 3; - */ - todos: TodoItem[]; + /** + * @generated from field: repeated agent.v1.TodoItem todos = 3; + */ + todos: TodoItem[]; - /** - * Raw JSON stringified tool-call content parts awaiting execution - * - * @generated from field: repeated string pending_tool_calls = 4; - */ - pendingToolCalls: string[]; + /** + * Raw JSON stringified tool-call content parts awaiting execution + * + * @generated from field: repeated string pending_tool_calls = 4; + */ + pendingToolCalls: string[]; - /** - * @generated from field: agent.v1.ConversationTokenDetails token_details = 5; - */ - tokenDetails?: ConversationTokenDetails; + /** + * @generated from field: agent.v1.ConversationTokenDetails token_details = 5; + */ + tokenDetails?: ConversationTokenDetails | undefined; - /** - * only for when the user explicitly asks for a summary through the summary action - * - * @generated from field: optional agent.v1.ConversationSummary summary = 6; - */ - summary?: ConversationSummary; + /** + * only for when the user explicitly asks for a summary through the summary action + * + * @generated from field: optional agent.v1.ConversationSummary summary = 6; + */ + summary?: ConversationSummary | undefined; - /** - * @generated from field: optional agent.v1.ConversationPlan plan = 7; - */ - plan?: ConversationPlan; + /** + * @generated from field: optional agent.v1.ConversationPlan plan = 7; + */ + plan?: ConversationPlan | undefined; - /** - * @generated from field: optional agent.v1.ConversationSummaryArchive summary_archive = 9; - */ - summaryArchive?: ConversationSummaryArchive; + /** + * @generated from field: optional agent.v1.ConversationSummaryArchive summary_archive = 9; + */ + summaryArchive?: ConversationSummaryArchive | undefined; - /** - * Deprecated, use summary_archives instead @deprecated summaryArchive; - * - * @generated from field: map file_states = 10; - */ - fileStates: { [key: string]: FileState }; + /** + * Deprecated, use summary_archives instead @deprecated summaryArchive; + * + * @generated from field: map file_states = 10; + */ + fileStates: { [key: string]: FileState }; - /** - * @generated from field: repeated agent.v1.ConversationSummaryArchive summary_archives = 11; - */ - summaryArchives: ConversationSummaryArchive[]; + /** + * @generated from field: repeated agent.v1.ConversationSummaryArchive summary_archives = 11; + */ + summaryArchives: ConversationSummaryArchive[]; }; /** * Describes the message agent.v1.ConversationState. * Use `create(ConversationStateSchema)` to create a new message. */ -export const ConversationStateSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 81); +export const ConversationStateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 81); /** * @generated from message agent.v1.SubagentPersistedState */ export type SubagentPersistedState = Message<"agent.v1.SubagentPersistedState"> & { - /** - * The subagent's conversation state structure - * - * @generated from field: agent.v1.ConversationStateStructure conversation_state = 1; - */ - conversationState?: ConversationStateStructure; + /** + * The subagent's conversation state structure + * + * @generated from field: agent.v1.ConversationStateStructure conversation_state = 1; + */ + conversationState?: ConversationStateStructure | undefined; - /** - * Timestamp when this subagent was first created - * - * @generated from field: uint64 created_timestamp_ms = 2; - */ - createdTimestampMs: bigint; + /** + * Timestamp when this subagent was first created + * + * @generated from field: uint64 created_timestamp_ms = 2; + */ + createdTimestampMs: bigint; - /** - * Timestamp when this subagent was last used (by task tool call) - * - * @generated from field: uint64 last_used_timestamp_ms = 3; - */ - lastUsedTimestampMs: bigint; + /** + * Timestamp when this subagent was last used (by task tool call) + * + * @generated from field: uint64 last_used_timestamp_ms = 3; + */ + lastUsedTimestampMs: bigint; - /** - * The subagent type (e.g., computerUse, custom with name) - * - * @generated from field: agent.v1.SubagentType subagent_type = 4; - */ - subagentType?: SubagentType; + /** + * The subagent type (e.g., computerUse, custom with name) + * + * @generated from field: agent.v1.SubagentType subagent_type = 4; + */ + subagentType?: SubagentType | undefined; }; /** * Describes the message agent.v1.SubagentPersistedState. * Use `create(SubagentPersistedStateSchema)` to create a new message. */ -export const SubagentPersistedStateSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 82); +export const SubagentPersistedStateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 82); /** * @generated from message agent.v1.ConversationStateStructure */ export type ConversationStateStructure = Message<"agent.v1.ConversationStateStructure"> & { - /** - * @generated from field: repeated bytes turns_old = 2; - */ - turnsOld: Uint8Array[]; + /** + * @generated from field: repeated bytes turns_old = 2; + */ + turnsOld: Uint8Array[]; - /** - * @deprecated turnsOld = []; - * - * @generated from field: repeated bytes root_prompt_messages_json = 1; - */ - rootPromptMessagesJson: Uint8Array[]; + /** + * @deprecated turnsOld = []; + * + * @generated from field: repeated bytes root_prompt_messages_json = 1; + */ + rootPromptMessagesJson: Uint8Array[]; - /** - * @generated from field: repeated bytes turns = 8; - */ - turns: Uint8Array[]; + /** + * @generated from field: repeated bytes turns = 8; + */ + turns: Uint8Array[]; - /** - * @generated from field: repeated bytes todos = 3; - */ - todos: Uint8Array[]; + /** + * @generated from field: repeated bytes todos = 3; + */ + todos: Uint8Array[]; - /** - * Raw JSON stringified tool-call content parts awaiting execution - * - * @generated from field: repeated string pending_tool_calls = 4; - */ - pendingToolCalls: string[]; + /** + * Raw JSON stringified tool-call content parts awaiting execution + * + * @generated from field: repeated string pending_tool_calls = 4; + */ + pendingToolCalls: string[]; - /** - * @generated from field: agent.v1.ConversationTokenDetails token_details = 5; - */ - tokenDetails?: ConversationTokenDetails; + /** + * @generated from field: agent.v1.ConversationTokenDetails token_details = 5; + */ + tokenDetails?: ConversationTokenDetails | undefined; - /** - * only for when the user explicitly asks for a summary through the summary action - * - * @generated from field: optional bytes summary = 6; - */ - summary?: Uint8Array; + /** + * only for when the user explicitly asks for a summary through the summary action + * + * @generated from field: optional bytes summary = 6; + */ + summary?: Uint8Array | undefined; - /** - * @generated from field: optional bytes plan = 7; - */ - plan?: Uint8Array; + /** + * @generated from field: optional bytes plan = 7; + */ + plan?: Uint8Array | undefined; - /** - * @generated from field: repeated string previous_workspace_uris = 9; - */ - previousWorkspaceUris: string[]; + /** + * @generated from field: repeated string previous_workspace_uris = 9; + */ + previousWorkspaceUris: string[]; - /** - * Current mode of the conversation - * - * @generated from field: optional int32 mode = 10; - */ - mode?: number; + /** + * Current mode of the conversation + * + * @generated from field: optional int32 mode = 10; + */ + mode?: number | undefined; - /** - * @generated from field: optional bytes summary_archive = 11; - */ - summaryArchive?: Uint8Array; + /** + * @generated from field: optional bytes summary_archive = 11; + */ + summaryArchive?: Uint8Array | undefined; - /** - * @generated from field: map file_states = 12; - */ - fileStates: { [key: string]: Uint8Array }; + /** + * @generated from field: map file_states = 12; + */ + fileStates: { [key: string]: Uint8Array }; - /** - * Deprecated, use summary_archives instead @deprecated summaryArchive; Map of file paths to their latest content (stored as blob IDs in the KV store) Each blob contains a serialized FileState message @deprecated fileStates = {}; Map of file paths to their latest content (stored as FileStateStructure) - * - * @generated from field: map file_states_v2 = 15; - */ - fileStatesV2: { [key: string]: FileStateStructure }; + /** + * Deprecated, use summary_archives instead @deprecated summaryArchive; Map of file paths to their latest content (stored as blob IDs in the KV store) Each blob contains a serialized FileState message @deprecated fileStates = {}; Map of file paths to their latest content (stored as FileStateStructure) + * + * @generated from field: map file_states_v2 = 15; + */ + fileStatesV2: { [key: string]: FileStateStructure }; - /** - * @generated from field: repeated bytes summary_archives = 13; - */ - summaryArchives: Uint8Array[]; + /** + * @generated from field: repeated bytes summary_archives = 13; + */ + summaryArchives: Uint8Array[]; - /** - * @generated from field: repeated agent.v1.StepTiming turn_timings = 14; - */ - turnTimings: StepTiming[]; + /** + * @generated from field: repeated agent.v1.StepTiming turn_timings = 14; + */ + turnTimings: StepTiming[]; - /** - * Subagent resume tracking Map of subagent ID to the persisted subagent state (stored inline) - * - * @generated from field: map subagent_states = 16; - */ - subagentStates: { [key: string]: SubagentPersistedState }; + /** + * Subagent resume tracking Map of subagent ID to the persisted subagent state (stored inline) + * + * @generated from field: map subagent_states = 16; + */ + subagentStates: { [key: string]: SubagentPersistedState }; - /** - * Count of self-summaries generated for this conversation - * - * @generated from field: uint32 self_summary_count = 17; - */ - selfSummaryCount: number; + /** + * Count of self-summaries generated for this conversation + * + * @generated from field: uint32 self_summary_count = 17; + */ + selfSummaryCount: number; - /** - * Set of file paths that have been read during this conversation - * - * @generated from field: repeated string read_paths = 18; - */ - readPaths: string[]; + /** + * Set of file paths that have been read during this conversation + * + * @generated from field: repeated string read_paths = 18; + */ + readPaths: string[]; }; /** * Describes the message agent.v1.ConversationStateStructure. * Use `create(ConversationStateStructureSchema)` to create a new message. */ -export const ConversationStateStructureSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 83); +export const ConversationStateStructureSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 83); /** * @generated from message agent.v1.ThinkingDetails */ -export type ThinkingDetails = Message<"agent.v1.ThinkingDetails"> & {}; +export type ThinkingDetails = Message<"agent.v1.ThinkingDetails"> & { +}; /** * Describes the message agent.v1.ThinkingDetails. * Use `create(ThinkingDetailsSchema)` to create a new message. */ -export const ThinkingDetailsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 84); +export const ThinkingDetailsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 84); /** * @generated from message agent.v1.ApiKeyCredentials */ export type ApiKeyCredentials = Message<"agent.v1.ApiKeyCredentials"> & { - /** - * @generated from field: string api_key = 1; - */ - apiKey: string; + /** + * @generated from field: string api_key = 1; + */ + apiKey: string; - /** - * For OpenAI-compatible endpoints - * - * @generated from field: optional string base_url = 2; - */ - baseUrl?: string; + /** + * For OpenAI-compatible endpoints + * + * @generated from field: optional string base_url = 2; + */ + baseUrl?: string | undefined; }; /** * Describes the message agent.v1.ApiKeyCredentials. * Use `create(ApiKeyCredentialsSchema)` to create a new message. */ -export const ApiKeyCredentialsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 85); +export const ApiKeyCredentialsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 85); /** * @generated from message agent.v1.AzureCredentials */ export type AzureCredentials = Message<"agent.v1.AzureCredentials"> & { - /** - * @generated from field: string api_key = 1; - */ - apiKey: string; + /** + * @generated from field: string api_key = 1; + */ + apiKey: string; - /** - * @generated from field: string base_url = 2; - */ - baseUrl: string; + /** + * @generated from field: string base_url = 2; + */ + baseUrl: string; - /** - * @generated from field: string deployment = 3; - */ - deployment: string; + /** + * @generated from field: string deployment = 3; + */ + deployment: string; }; /** * Describes the message agent.v1.AzureCredentials. * Use `create(AzureCredentialsSchema)` to create a new message. */ -export const AzureCredentialsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 86); +export const AzureCredentialsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 86); /** * @generated from message agent.v1.BedrockCredentials */ export type BedrockCredentials = Message<"agent.v1.BedrockCredentials"> & { - /** - * @generated from field: string access_key = 1; - */ - accessKey: string; + /** + * @generated from field: string access_key = 1; + */ + accessKey: string; - /** - * @generated from field: string secret_key = 2; - */ - secretKey: string; + /** + * @generated from field: string secret_key = 2; + */ + secretKey: string; - /** - * @generated from field: string region = 3; - */ - region: string; + /** + * @generated from field: string region = 3; + */ + region: string; - /** - * @generated from field: optional string session_token = 4; - */ - sessionToken?: string; + /** + * @generated from field: optional string session_token = 4; + */ + sessionToken?: string | undefined; }; /** * Describes the message agent.v1.BedrockCredentials. * Use `create(BedrockCredentialsSchema)` to create a new message. */ -export const BedrockCredentialsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 87); +export const BedrockCredentialsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 87); /** * @generated from message agent.v1.ModelDetails */ export type ModelDetails = Message<"agent.v1.ModelDetails"> & { - /** - * @generated from field: string model_id = 1; - */ - modelId: string; + /** + * @generated from field: string model_id = 1; + */ + modelId: string; - /** - * @generated from field: string display_model_id = 3; - */ - displayModelId: string; + /** + * @generated from field: string display_model_id = 3; + */ + displayModelId: string; - /** - * @generated from field: string display_name = 4; - */ - displayName: string; + /** + * @generated from field: string display_name = 4; + */ + displayName: string; - /** - * @generated from field: string display_name_short = 5; - */ - displayNameShort: string; + /** + * @generated from field: string display_name_short = 5; + */ + displayNameShort: string; - /** - * @generated from field: repeated string aliases = 6; - */ - aliases: string[]; + /** + * @generated from field: repeated string aliases = 6; + */ + aliases: string[]; - /** - * @generated from field: optional agent.v1.ThinkingDetails thinking_details = 2; - */ - thinkingDetails?: ThinkingDetails; + /** + * @generated from field: optional agent.v1.ThinkingDetails thinking_details = 2; + */ + thinkingDetails?: ThinkingDetails | undefined; - /** - * @generated from field: optional bool max_mode = 7; - */ - maxMode?: boolean; + /** + * @generated from field: optional bool max_mode = 7; + */ + maxMode?: boolean | undefined; - /** - * @generated from oneof agent.v1.ModelDetails.credentials - */ - credentials: - | { - /** - * @generated from field: agent.v1.ApiKeyCredentials api_key_credentials = 8; - */ - value: ApiKeyCredentials; - case: "apiKeyCredentials"; - } - | { - /** - * @generated from field: agent.v1.AzureCredentials azure_credentials = 9; - */ - value: AzureCredentials; - case: "azureCredentials"; - } - | { - /** - * @generated from field: agent.v1.BedrockCredentials bedrock_credentials = 10; - */ - value: BedrockCredentials; - case: "bedrockCredentials"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ModelDetails.credentials + */ + credentials: { + /** + * @generated from field: agent.v1.ApiKeyCredentials api_key_credentials = 8; + */ + value: ApiKeyCredentials; + case: "apiKeyCredentials"; + } | { + /** + * @generated from field: agent.v1.AzureCredentials azure_credentials = 9; + */ + value: AzureCredentials; + case: "azureCredentials"; + } | { + /** + * @generated from field: agent.v1.BedrockCredentials bedrock_credentials = 10; + */ + value: BedrockCredentials; + case: "bedrockCredentials"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ModelDetails. * Use `create(ModelDetailsSchema)` to create a new message. */ -export const ModelDetailsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 88); +export const ModelDetailsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 88); /** * @generated from message agent.v1.RequestedModel */ export type RequestedModel = Message<"agent.v1.RequestedModel"> & { - /** - * @generated from field: string model_id = 1; - */ - modelId: string; + /** + * @generated from field: string model_id = 1; + */ + modelId: string; - /** - * @generated from field: bool max_mode = 2; - */ - maxMode: boolean; + /** + * @generated from field: bool max_mode = 2; + */ + maxMode: boolean; - /** - * @generated from field: repeated agent.v1.RequestedModel_ModelParameterbytes parameters = 3; - */ - parameters: RequestedModel_ModelParameterbytes[]; + /** + * @generated from field: repeated agent.v1.RequestedModel_ModelParameterbytes parameters = 3; + */ + parameters: RequestedModel_ModelParameterbytes[]; - /** - * @generated from oneof agent.v1.RequestedModel.credentials - */ - credentials: - | { - /** - * @generated from field: agent.v1.ApiKeyCredentials api_key_credentials = 4; - */ - value: ApiKeyCredentials; - case: "apiKeyCredentials"; - } - | { - /** - * @generated from field: agent.v1.AzureCredentials azure_credentials = 5; - */ - value: AzureCredentials; - case: "azureCredentials"; - } - | { - /** - * @generated from field: agent.v1.BedrockCredentials bedrock_credentials = 6; - */ - value: BedrockCredentials; - case: "bedrockCredentials"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.RequestedModel.credentials + */ + credentials: { + /** + * @generated from field: agent.v1.ApiKeyCredentials api_key_credentials = 4; + */ + value: ApiKeyCredentials; + case: "apiKeyCredentials"; + } | { + /** + * @generated from field: agent.v1.AzureCredentials azure_credentials = 5; + */ + value: AzureCredentials; + case: "azureCredentials"; + } | { + /** + * @generated from field: agent.v1.BedrockCredentials bedrock_credentials = 6; + */ + value: BedrockCredentials; + case: "bedrockCredentials"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.RequestedModel. * Use `create(RequestedModelSchema)` to create a new message. */ -export const RequestedModelSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 89); +export const RequestedModelSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 89); /** * @generated from message agent.v1.RequestedModel_ModelParameterbytes */ export type RequestedModel_ModelParameterbytes = Message<"agent.v1.RequestedModel_ModelParameterbytes"> & { - /** - * @generated from field: string id = 1; - */ - id: string; + /** + * @generated from field: string id = 1; + */ + id: string; - /** - * All paramters are encoded as strings. For boolean parameters, the value is either "true" or "false". For enum parameters, the value is one of the values in the enum. - * - * @generated from field: string value = 2; - */ - value: string; + /** + * All paramters are encoded as strings. For boolean parameters, the value is either "true" or "false". For enum parameters, the value is one of the values in the enum. + * + * @generated from field: string value = 2; + */ + value: string; }; /** * Describes the message agent.v1.RequestedModel_ModelParameterbytes. * Use `create(RequestedModel_ModelParameterbytesSchema)` to create a new message. */ -export const RequestedModel_ModelParameterbytesSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 90); +export const RequestedModel_ModelParameterbytesSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 90); /** * @generated from message agent.v1.AgentRunRequest */ export type AgentRunRequest = Message<"agent.v1.AgentRunRequest"> & { - /** - * @generated from field: agent.v1.ConversationStateStructure conversation_state = 1; - */ - conversationState?: ConversationStateStructure; + /** + * @generated from field: agent.v1.ConversationStateStructure conversation_state = 1; + */ + conversationState?: ConversationStateStructure | undefined; - /** - * @generated from field: agent.v1.ConversationAction action = 2; - */ - action?: ConversationAction; + /** + * @generated from field: agent.v1.ConversationAction action = 2; + */ + action?: ConversationAction | undefined; - /** - * TODO: Today we use model_details, but we are getting ready to deprecate that and use requested_model instead. - * - * @generated from field: agent.v1.ModelDetails model_details = 3; - */ - modelDetails?: ModelDetails; + /** + * TODO: Today we use model_details, but we are getting ready to deprecate that and use requested_model instead. + * + * @generated from field: agent.v1.ModelDetails model_details = 3; + */ + modelDetails?: ModelDetails | undefined; - /** - * @generated from field: optional agent.v1.RequestedModel requested_model = 9; - */ - requestedModel?: RequestedModel; + /** + * @generated from field: optional agent.v1.RequestedModel requested_model = 9; + */ + requestedModel?: RequestedModel | undefined; - /** - * @generated from field: agent.v1.McpTools mcp_tools = 4; - */ - mcpTools?: McpTools; + /** + * @generated from field: agent.v1.McpTools mcp_tools = 4; + */ + mcpTools?: McpTools | undefined; - /** - * @generated from field: optional string conversation_id = 5; - */ - conversationId?: string; + /** + * @generated from field: optional string conversation_id = 5; + */ + conversationId?: string | undefined; - /** - * @generated from field: optional agent.v1.McpFileSystemOptions mcp_file_system_options = 6; - */ - mcpFileSystemOptions?: McpFileSystemOptions; + /** + * @generated from field: optional agent.v1.McpFileSystemOptions mcp_file_system_options = 6; + */ + mcpFileSystemOptions?: McpFileSystemOptions | undefined; - /** - * Deprecated, use the one in RequestContext message in request_context_exec.proto instead - * - * @generated from field: optional agent.v1.SkillOptions skill_options = 7; - */ - skillOptions?: SkillOptions; + /** + * Deprecated, use the one in RequestContext message in request_context_exec.proto instead + * + * @generated from field: optional agent.v1.SkillOptions skill_options = 7; + */ + skillOptions?: SkillOptions | undefined; - /** - * Custom system prompt override. Allowlisted for specific teams only. - * - * @generated from field: optional string custom_system_prompt = 8; - */ - customSystemPrompt?: string; + /** + * Custom system prompt override. Allowlisted for specific teams only. + * + * @generated from field: optional string custom_system_prompt = 8; + */ + customSystemPrompt?: string | undefined; }; /** * Describes the message agent.v1.AgentRunRequest. * Use `create(AgentRunRequestSchema)` to create a new message. */ -export const AgentRunRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 91); +export const AgentRunRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 91); /** * @generated from message agent.v1.TextDeltaUpdate */ export type TextDeltaUpdate = Message<"agent.v1.TextDeltaUpdate"> & { - /** - * @generated from field: string text = 1; - */ - text: string; + /** + * @generated from field: string text = 1; + */ + text: string; }; /** * Describes the message agent.v1.TextDeltaUpdate. * Use `create(TextDeltaUpdateSchema)` to create a new message. */ -export const TextDeltaUpdateSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 92); +export const TextDeltaUpdateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 92); /** * @generated from message agent.v1.ToolCallStartedUpdate */ export type ToolCallStartedUpdate = Message<"agent.v1.ToolCallStartedUpdate"> & { - /** - * @generated from field: string call_id = 1; - */ - callId: string; + /** + * @generated from field: string call_id = 1; + */ + callId: string; - /** - * @generated from field: agent.v1.ToolCall tool_call = 2; - */ - toolCall?: ToolCall; + /** + * @generated from field: agent.v1.ToolCall tool_call = 2; + */ + toolCall?: ToolCall | undefined; - /** - * groups tool calls that originate from the same model provider call - * - * @generated from field: string model_call_id = 3; - */ - modelCallId: string; + /** + * groups tool calls that originate from the same model provider call + * + * @generated from field: string model_call_id = 3; + */ + modelCallId: string; }; /** * Describes the message agent.v1.ToolCallStartedUpdate. * Use `create(ToolCallStartedUpdateSchema)` to create a new message. */ -export const ToolCallStartedUpdateSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 93); +export const ToolCallStartedUpdateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 93); /** * @generated from message agent.v1.ToolCallCompletedUpdate */ export type ToolCallCompletedUpdate = Message<"agent.v1.ToolCallCompletedUpdate"> & { - /** - * @generated from field: string call_id = 1; - */ - callId: string; + /** + * @generated from field: string call_id = 1; + */ + callId: string; - /** - * @generated from field: agent.v1.ToolCall tool_call = 2; - */ - toolCall?: ToolCall; + /** + * @generated from field: agent.v1.ToolCall tool_call = 2; + */ + toolCall?: ToolCall | undefined; - /** - * groups tool calls that originate from the same model provider call - * - * @generated from field: string model_call_id = 3; - */ - modelCallId: string; + /** + * groups tool calls that originate from the same model provider call + * + * @generated from field: string model_call_id = 3; + */ + modelCallId: string; }; /** * Describes the message agent.v1.ToolCallCompletedUpdate. * Use `create(ToolCallCompletedUpdateSchema)` to create a new message. */ -export const ToolCallCompletedUpdateSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 94); +export const ToolCallCompletedUpdateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 94); /** * @generated from message agent.v1.ToolCallDeltaUpdate */ export type ToolCallDeltaUpdate = Message<"agent.v1.ToolCallDeltaUpdate"> & { - /** - * @generated from field: string call_id = 1; - */ - callId: string; + /** + * @generated from field: string call_id = 1; + */ + callId: string; - /** - * @generated from field: agent.v1.ToolCallDelta tool_call_delta = 2; - */ - toolCallDelta?: ToolCallDelta; + /** + * @generated from field: agent.v1.ToolCallDelta tool_call_delta = 2; + */ + toolCallDelta?: ToolCallDelta | undefined; - /** - * groups tool calls that originate from the same model provider call - * - * @generated from field: string model_call_id = 3; - */ - modelCallId: string; + /** + * groups tool calls that originate from the same model provider call + * + * @generated from field: string model_call_id = 3; + */ + modelCallId: string; }; /** * Describes the message agent.v1.ToolCallDeltaUpdate. * Use `create(ToolCallDeltaUpdateSchema)` to create a new message. */ -export const ToolCallDeltaUpdateSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 95); +export const ToolCallDeltaUpdateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 95); /** * Streaming update for partial tool call arguments @@ -2900,195 +2930,198 @@ export const ToolCallDeltaUpdateSchema: GenMessage = /*@__P * @generated from message agent.v1.PartialToolCallUpdate */ export type PartialToolCallUpdate = Message<"agent.v1.PartialToolCallUpdate"> & { - /** - * @generated from field: string call_id = 1; - */ - callId: string; + /** + * @generated from field: string call_id = 1; + */ + callId: string; - /** - * @generated from field: agent.v1.ToolCall tool_call = 2; - */ - toolCall?: ToolCall; + /** + * @generated from field: agent.v1.ToolCall tool_call = 2; + */ + toolCall?: ToolCall | undefined; - /** - * Aggregated args text so far (as JSON text). May be incomplete until final tool call. - * - * @generated from field: string args_text_delta = 3; - */ - argsTextDelta: string; + /** + * Aggregated args text so far (as JSON text). May be incomplete until final tool call. + * + * @generated from field: string args_text_delta = 3; + */ + argsTextDelta: string; - /** - * groups tool calls that originate from the same model provider call - * - * @generated from field: string model_call_id = 4; - */ - modelCallId: string; + /** + * groups tool calls that originate from the same model provider call + * + * @generated from field: string model_call_id = 4; + */ + modelCallId: string; }; /** * Describes the message agent.v1.PartialToolCallUpdate. * Use `create(PartialToolCallUpdateSchema)` to create a new message. */ -export const PartialToolCallUpdateSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 96); +export const PartialToolCallUpdateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 96); /** * @generated from message agent.v1.ThinkingDeltaUpdate */ export type ThinkingDeltaUpdate = Message<"agent.v1.ThinkingDeltaUpdate"> & { - /** - * @generated from field: string text = 1; - */ - text: string; + /** + * @generated from field: string text = 1; + */ + text: string; }; /** * Describes the message agent.v1.ThinkingDeltaUpdate. * Use `create(ThinkingDeltaUpdateSchema)` to create a new message. */ -export const ThinkingDeltaUpdateSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 97); +export const ThinkingDeltaUpdateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 97); /** * @generated from message agent.v1.ThinkingCompletedUpdate */ export type ThinkingCompletedUpdate = Message<"agent.v1.ThinkingCompletedUpdate"> & { - /** - * @generated from field: int32 thinking_duration_ms = 1; - */ - thinkingDurationMs: number; + /** + * @generated from field: int32 thinking_duration_ms = 1; + */ + thinkingDurationMs: number; }; /** * Describes the message agent.v1.ThinkingCompletedUpdate. * Use `create(ThinkingCompletedUpdateSchema)` to create a new message. */ -export const ThinkingCompletedUpdateSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 98); +export const ThinkingCompletedUpdateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 98); /** * @generated from message agent.v1.TokenDeltaUpdate */ export type TokenDeltaUpdate = Message<"agent.v1.TokenDeltaUpdate"> & { - /** - * @generated from field: int32 tokens = 1; - */ - tokens: number; + /** + * @generated from field: int32 tokens = 1; + */ + tokens: number; }; /** * Describes the message agent.v1.TokenDeltaUpdate. * Use `create(TokenDeltaUpdateSchema)` to create a new message. */ -export const TokenDeltaUpdateSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 99); +export const TokenDeltaUpdateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 99); /** * @generated from message agent.v1.SummaryUpdate */ export type SummaryUpdate = Message<"agent.v1.SummaryUpdate"> & { - /** - * @generated from field: string summary = 1; - */ - summary: string; + /** + * @generated from field: string summary = 1; + */ + summary: string; }; /** * Describes the message agent.v1.SummaryUpdate. * Use `create(SummaryUpdateSchema)` to create a new message. */ -export const SummaryUpdateSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 100); +export const SummaryUpdateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 100); /** * @generated from message agent.v1.SummaryStartedUpdate */ -export type SummaryStartedUpdate = Message<"agent.v1.SummaryStartedUpdate"> & {}; +export type SummaryStartedUpdate = Message<"agent.v1.SummaryStartedUpdate"> & { +}; /** * Describes the message agent.v1.SummaryStartedUpdate. * Use `create(SummaryStartedUpdateSchema)` to create a new message. */ -export const SummaryStartedUpdateSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 101); +export const SummaryStartedUpdateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 101); /** * @generated from message agent.v1.HeartbeatUpdate */ -export type HeartbeatUpdate = Message<"agent.v1.HeartbeatUpdate"> & {}; +export type HeartbeatUpdate = Message<"agent.v1.HeartbeatUpdate"> & { +}; /** * Describes the message agent.v1.HeartbeatUpdate. * Use `create(HeartbeatUpdateSchema)` to create a new message. */ -export const HeartbeatUpdateSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 102); +export const HeartbeatUpdateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 102); /** * @generated from message agent.v1.SummaryCompletedUpdate */ -export type SummaryCompletedUpdate = Message<"agent.v1.SummaryCompletedUpdate"> & {}; +export type SummaryCompletedUpdate = Message<"agent.v1.SummaryCompletedUpdate"> & { +}; /** * Describes the message agent.v1.SummaryCompletedUpdate. * Use `create(SummaryCompletedUpdateSchema)` to create a new message. */ -export const SummaryCompletedUpdateSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 103); +export const SummaryCompletedUpdateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 103); /** * @generated from message agent.v1.ShellOutputDeltaUpdate */ export type ShellOutputDeltaUpdate = Message<"agent.v1.ShellOutputDeltaUpdate"> & { - /** - * @generated from oneof agent.v1.ShellOutputDeltaUpdate.event - */ - event: - | { - /** - * @generated from field: agent.v1.ShellStreamStdout stdout = 1; - */ - value: ShellStreamStdout; - case: "stdout"; - } - | { - /** - * @generated from field: agent.v1.ShellStreamStderr stderr = 2; - */ - value: ShellStreamStderr; - case: "stderr"; - } - | { - /** - * @generated from field: agent.v1.ShellStreamExit exit = 3; - */ - value: ShellStreamExit; - case: "exit"; - } - | { - /** - * @generated from field: agent.v1.ShellStreamStart start = 4; - */ - value: ShellStreamStart; - case: "start"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ShellOutputDeltaUpdate.event + */ + event: { + /** + * @generated from field: agent.v1.ShellStreamStdout stdout = 1; + */ + value: ShellStreamStdout; + case: "stdout"; + } | { + /** + * @generated from field: agent.v1.ShellStreamStderr stderr = 2; + */ + value: ShellStreamStderr; + case: "stderr"; + } | { + /** + * @generated from field: agent.v1.ShellStreamExit exit = 3; + */ + value: ShellStreamExit; + case: "exit"; + } | { + /** + * @generated from field: agent.v1.ShellStreamStart start = 4; + */ + value: ShellStreamStart; + case: "start"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ShellOutputDeltaUpdate. * Use `create(ShellOutputDeltaUpdateSchema)` to create a new message. */ -export const ShellOutputDeltaUpdateSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 104); +export const ShellOutputDeltaUpdateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 104); /** * @generated from message agent.v1.TurnEndedUpdate */ -export type TurnEndedUpdate = Message<"agent.v1.TurnEndedUpdate"> & {}; +export type TurnEndedUpdate = Message<"agent.v1.TurnEndedUpdate"> & { +}; /** * Describes the message agent.v1.TurnEndedUpdate. * Use `create(TurnEndedUpdateSchema)` to create a new message. */ -export const TurnEndedUpdateSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 105); +export const TurnEndedUpdateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 105); /** * Only: user message appended update @@ -3096,192 +3129,176 @@ export const TurnEndedUpdateSchema: GenMessage = /*@__PURE__*/ * @generated from message agent.v1.UserMessageAppendedUpdate */ export type UserMessageAppendedUpdate = Message<"agent.v1.UserMessageAppendedUpdate"> & { - /** - * @generated from field: agent.v1.UserMessage user_message = 1; - */ - userMessage?: UserMessage; + /** + * @generated from field: agent.v1.UserMessage user_message = 1; + */ + userMessage?: UserMessage | undefined; }; /** * Describes the message agent.v1.UserMessageAppendedUpdate. * Use `create(UserMessageAppendedUpdateSchema)` to create a new message. */ -export const UserMessageAppendedUpdateSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 106); +export const UserMessageAppendedUpdateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 106); /** * @generated from message agent.v1.StepStartedUpdate */ export type StepStartedUpdate = Message<"agent.v1.StepStartedUpdate"> & { - /** - * @generated from field: uint64 step_id = 1; - */ - stepId: bigint; + /** + * @generated from field: uint64 step_id = 1; + */ + stepId: bigint; }; /** * Describes the message agent.v1.StepStartedUpdate. * Use `create(StepStartedUpdateSchema)` to create a new message. */ -export const StepStartedUpdateSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 107); +export const StepStartedUpdateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 107); /** * @generated from message agent.v1.StepCompletedUpdate */ export type StepCompletedUpdate = Message<"agent.v1.StepCompletedUpdate"> & { - /** - * @generated from field: uint64 step_id = 1; - */ - stepId: bigint; + /** + * @generated from field: uint64 step_id = 1; + */ + stepId: bigint; - /** - * @generated from field: int64 step_duration_ms = 2; - */ - stepDurationMs: bigint; + /** + * @generated from field: int64 step_duration_ms = 2; + */ + stepDurationMs: bigint; }; /** * Describes the message agent.v1.StepCompletedUpdate. * Use `create(StepCompletedUpdateSchema)` to create a new message. */ -export const StepCompletedUpdateSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 108); +export const StepCompletedUpdateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 108); /** * @generated from message agent.v1.InteractionUpdate */ export type InteractionUpdate = Message<"agent.v1.InteractionUpdate"> & { - /** - * @generated from oneof agent.v1.InteractionUpdate.message - */ - message: - | { - /** - * @generated from field: agent.v1.TextDeltaUpdate text_delta = 1; - */ - value: TextDeltaUpdate; - case: "textDelta"; - } - | { - /** - * @generated from field: agent.v1.PartialToolCallUpdate partial_tool_call = 7; - */ - value: PartialToolCallUpdate; - case: "partialToolCall"; - } - | { - /** - * @generated from field: agent.v1.ToolCallDeltaUpdate tool_call_delta = 15; - */ - value: ToolCallDeltaUpdate; - case: "toolCallDelta"; - } - | { - /** - * @generated from field: agent.v1.ToolCallStartedUpdate tool_call_started = 2; - */ - value: ToolCallStartedUpdate; - case: "toolCallStarted"; - } - | { - /** - * @generated from field: agent.v1.ToolCallCompletedUpdate tool_call_completed = 3; - */ - value: ToolCallCompletedUpdate; - case: "toolCallCompleted"; - } - | { - /** - * @generated from field: agent.v1.ThinkingDeltaUpdate thinking_delta = 4; - */ - value: ThinkingDeltaUpdate; - case: "thinkingDelta"; - } - | { - /** - * @generated from field: agent.v1.ThinkingCompletedUpdate thinking_completed = 5; - */ - value: ThinkingCompletedUpdate; - case: "thinkingCompleted"; - } - | { - /** - * @generated from field: agent.v1.UserMessageAppendedUpdate user_message_appended = 6; - */ - value: UserMessageAppendedUpdate; - case: "userMessageAppended"; - } - | { - /** - * @generated from field: agent.v1.TokenDeltaUpdate token_delta = 8; - */ - value: TokenDeltaUpdate; - case: "tokenDelta"; - } - | { - /** - * @generated from field: agent.v1.SummaryUpdate summary = 9; - */ - value: SummaryUpdate; - case: "summary"; - } - | { - /** - * @generated from field: agent.v1.SummaryStartedUpdate summary_started = 10; - */ - value: SummaryStartedUpdate; - case: "summaryStarted"; - } - | { - /** - * @generated from field: agent.v1.SummaryCompletedUpdate summary_completed = 11; - */ - value: SummaryCompletedUpdate; - case: "summaryCompleted"; - } - | { - /** - * @generated from field: agent.v1.ShellOutputDeltaUpdate shell_output_delta = 12; - */ - value: ShellOutputDeltaUpdate; - case: "shellOutputDelta"; - } - | { - /** - * @generated from field: agent.v1.HeartbeatUpdate heartbeat = 13; - */ - value: HeartbeatUpdate; - case: "heartbeat"; - } - | { - /** - * @generated from field: agent.v1.TurnEndedUpdate turn_ended = 14; - */ - value: TurnEndedUpdate; - case: "turnEnded"; - } - | { - /** - * @generated from field: agent.v1.StepStartedUpdate step_started = 16; - */ - value: StepStartedUpdate; - case: "stepStarted"; - } - | { - /** - * @generated from field: agent.v1.StepCompletedUpdate step_completed = 17; - */ - value: StepCompletedUpdate; - case: "stepCompleted"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.InteractionUpdate.message + */ + message: { + /** + * @generated from field: agent.v1.TextDeltaUpdate text_delta = 1; + */ + value: TextDeltaUpdate; + case: "textDelta"; + } | { + /** + * @generated from field: agent.v1.PartialToolCallUpdate partial_tool_call = 7; + */ + value: PartialToolCallUpdate; + case: "partialToolCall"; + } | { + /** + * @generated from field: agent.v1.ToolCallDeltaUpdate tool_call_delta = 15; + */ + value: ToolCallDeltaUpdate; + case: "toolCallDelta"; + } | { + /** + * @generated from field: agent.v1.ToolCallStartedUpdate tool_call_started = 2; + */ + value: ToolCallStartedUpdate; + case: "toolCallStarted"; + } | { + /** + * @generated from field: agent.v1.ToolCallCompletedUpdate tool_call_completed = 3; + */ + value: ToolCallCompletedUpdate; + case: "toolCallCompleted"; + } | { + /** + * @generated from field: agent.v1.ThinkingDeltaUpdate thinking_delta = 4; + */ + value: ThinkingDeltaUpdate; + case: "thinkingDelta"; + } | { + /** + * @generated from field: agent.v1.ThinkingCompletedUpdate thinking_completed = 5; + */ + value: ThinkingCompletedUpdate; + case: "thinkingCompleted"; + } | { + /** + * @generated from field: agent.v1.UserMessageAppendedUpdate user_message_appended = 6; + */ + value: UserMessageAppendedUpdate; + case: "userMessageAppended"; + } | { + /** + * @generated from field: agent.v1.TokenDeltaUpdate token_delta = 8; + */ + value: TokenDeltaUpdate; + case: "tokenDelta"; + } | { + /** + * @generated from field: agent.v1.SummaryUpdate summary = 9; + */ + value: SummaryUpdate; + case: "summary"; + } | { + /** + * @generated from field: agent.v1.SummaryStartedUpdate summary_started = 10; + */ + value: SummaryStartedUpdate; + case: "summaryStarted"; + } | { + /** + * @generated from field: agent.v1.SummaryCompletedUpdate summary_completed = 11; + */ + value: SummaryCompletedUpdate; + case: "summaryCompleted"; + } | { + /** + * @generated from field: agent.v1.ShellOutputDeltaUpdate shell_output_delta = 12; + */ + value: ShellOutputDeltaUpdate; + case: "shellOutputDelta"; + } | { + /** + * @generated from field: agent.v1.HeartbeatUpdate heartbeat = 13; + */ + value: HeartbeatUpdate; + case: "heartbeat"; + } | { + /** + * @generated from field: agent.v1.TurnEndedUpdate turn_ended = 14; + */ + value: TurnEndedUpdate; + case: "turnEnded"; + } | { + /** + * @generated from field: agent.v1.StepStartedUpdate step_started = 16; + */ + value: StepStartedUpdate; + case: "stepStarted"; + } | { + /** + * @generated from field: agent.v1.StepCompletedUpdate step_completed = 17; + */ + value: StepCompletedUpdate; + case: "stepCompleted"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.InteractionUpdate. * Use `create(InteractionUpdateSchema)` to create a new message. */ -export const InteractionUpdateSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 109); +export const InteractionUpdateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 109); /** * Interaction query messages for bidirectional communication @@ -3289,195 +3306,181 @@ export const InteractionUpdateSchema: GenMessage = /*@__PURE_ * @generated from message agent.v1.InteractionQuery */ export type InteractionQuery = Message<"agent.v1.InteractionQuery"> & { - /** - * @generated from field: uint32 id = 1; - */ - id: number; + /** + * @generated from field: uint32 id = 1; + */ + id: number; - /** - * @generated from oneof agent.v1.InteractionQuery.query - */ - query: - | { - /** - * @generated from field: agent.v1.WebSearchRequestQuery web_search_request_query = 2; - */ - value: WebSearchRequestQuery; - case: "webSearchRequestQuery"; - } - | { - /** - * @generated from field: agent.v1.AskQuestionInteractionQuery ask_question_interaction_query = 3; - */ - value: AskQuestionInteractionQuery; - case: "askQuestionInteractionQuery"; - } - | { - /** - * @generated from field: agent.v1.SwitchModeRequestQuery switch_mode_request_query = 4; - */ - value: SwitchModeRequestQuery; - case: "switchModeRequestQuery"; - } - | { - /** - * @generated from field: agent.v1.ExaSearchRequestQuery exa_search_request_query = 5; - */ - value: ExaSearchRequestQuery; - case: "exaSearchRequestQuery"; - } - | { - /** - * @generated from field: agent.v1.ExaFetchRequestQuery exa_fetch_request_query = 6; - */ - value: ExaFetchRequestQuery; - case: "exaFetchRequestQuery"; - } - | { - /** - * @generated from field: agent.v1.CreatePlanRequestQuery create_plan_request_query = 7; - */ - value: CreatePlanRequestQuery; - case: "createPlanRequestQuery"; - } - | { - /** - * @generated from field: agent.v1.SetupVmEnvironmentArgs setup_vm_environment_args = 8; - */ - value: SetupVmEnvironmentArgs; - case: "setupVmEnvironmentArgs"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.InteractionQuery.query + */ + query: { + /** + * @generated from field: agent.v1.WebSearchRequestQuery web_search_request_query = 2; + */ + value: WebSearchRequestQuery; + case: "webSearchRequestQuery"; + } | { + /** + * @generated from field: agent.v1.AskQuestionInteractionQuery ask_question_interaction_query = 3; + */ + value: AskQuestionInteractionQuery; + case: "askQuestionInteractionQuery"; + } | { + /** + * @generated from field: agent.v1.SwitchModeRequestQuery switch_mode_request_query = 4; + */ + value: SwitchModeRequestQuery; + case: "switchModeRequestQuery"; + } | { + /** + * @generated from field: agent.v1.ExaSearchRequestQuery exa_search_request_query = 5; + */ + value: ExaSearchRequestQuery; + case: "exaSearchRequestQuery"; + } | { + /** + * @generated from field: agent.v1.ExaFetchRequestQuery exa_fetch_request_query = 6; + */ + value: ExaFetchRequestQuery; + case: "exaFetchRequestQuery"; + } | { + /** + * @generated from field: agent.v1.CreatePlanRequestQuery create_plan_request_query = 7; + */ + value: CreatePlanRequestQuery; + case: "createPlanRequestQuery"; + } | { + /** + * @generated from field: agent.v1.SetupVmEnvironmentArgs setup_vm_environment_args = 8; + */ + value: SetupVmEnvironmentArgs; + case: "setupVmEnvironmentArgs"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.InteractionQuery. * Use `create(InteractionQuerySchema)` to create a new message. */ -export const InteractionQuerySchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 110); +export const InteractionQuerySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 110); /** * @generated from message agent.v1.InteractionResponse */ export type InteractionResponse = Message<"agent.v1.InteractionResponse"> & { - /** - * @generated from field: uint32 id = 1; - */ - id: number; + /** + * @generated from field: uint32 id = 1; + */ + id: number; - /** - * @generated from oneof agent.v1.InteractionResponse.result - */ - result: - | { - /** - * @generated from field: agent.v1.WebSearchRequestResponse web_search_request_response = 2; - */ - value: WebSearchRequestResponse; - case: "webSearchRequestResponse"; - } - | { - /** - * @generated from field: agent.v1.AskQuestionInteractionResponse ask_question_interaction_response = 3; - */ - value: AskQuestionInteractionResponse; - case: "askQuestionInteractionResponse"; - } - | { - /** - * @generated from field: agent.v1.SwitchModeRequestResponse switch_mode_request_response = 4; - */ - value: SwitchModeRequestResponse; - case: "switchModeRequestResponse"; - } - | { - /** - * @generated from field: agent.v1.ExaSearchRequestResponse exa_search_request_response = 5; - */ - value: ExaSearchRequestResponse; - case: "exaSearchRequestResponse"; - } - | { - /** - * @generated from field: agent.v1.ExaFetchRequestResponse exa_fetch_request_response = 6; - */ - value: ExaFetchRequestResponse; - case: "exaFetchRequestResponse"; - } - | { - /** - * @generated from field: agent.v1.CreatePlanRequestResponse create_plan_request_response = 7; - */ - value: CreatePlanRequestResponse; - case: "createPlanRequestResponse"; - } - | { - /** - * @generated from field: agent.v1.SetupVmEnvironmentResult setup_vm_environment_result = 8; - */ - value: SetupVmEnvironmentResult; - case: "setupVmEnvironmentResult"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.InteractionResponse.result + */ + result: { + /** + * @generated from field: agent.v1.WebSearchRequestResponse web_search_request_response = 2; + */ + value: WebSearchRequestResponse; + case: "webSearchRequestResponse"; + } | { + /** + * @generated from field: agent.v1.AskQuestionInteractionResponse ask_question_interaction_response = 3; + */ + value: AskQuestionInteractionResponse; + case: "askQuestionInteractionResponse"; + } | { + /** + * @generated from field: agent.v1.SwitchModeRequestResponse switch_mode_request_response = 4; + */ + value: SwitchModeRequestResponse; + case: "switchModeRequestResponse"; + } | { + /** + * @generated from field: agent.v1.ExaSearchRequestResponse exa_search_request_response = 5; + */ + value: ExaSearchRequestResponse; + case: "exaSearchRequestResponse"; + } | { + /** + * @generated from field: agent.v1.ExaFetchRequestResponse exa_fetch_request_response = 6; + */ + value: ExaFetchRequestResponse; + case: "exaFetchRequestResponse"; + } | { + /** + * @generated from field: agent.v1.CreatePlanRequestResponse create_plan_request_response = 7; + */ + value: CreatePlanRequestResponse; + case: "createPlanRequestResponse"; + } | { + /** + * @generated from field: agent.v1.SetupVmEnvironmentResult setup_vm_environment_result = 8; + */ + value: SetupVmEnvironmentResult; + case: "setupVmEnvironmentResult"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.InteractionResponse. * Use `create(InteractionResponseSchema)` to create a new message. */ -export const InteractionResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 111); +export const InteractionResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 111); /** * @generated from message agent.v1.AskQuestionInteractionQuery */ export type AskQuestionInteractionQuery = Message<"agent.v1.AskQuestionInteractionQuery"> & { - /** - * @generated from field: agent.v1.AskQuestionArgs args = 1; - */ - args?: AskQuestionArgs; + /** + * @generated from field: agent.v1.AskQuestionArgs args = 1; + */ + args?: AskQuestionArgs | undefined; - /** - * @generated from field: string tool_call_id = 2; - */ - toolCallId: string; + /** + * @generated from field: string tool_call_id = 2; + */ + toolCallId: string; }; /** * Describes the message agent.v1.AskQuestionInteractionQuery. * Use `create(AskQuestionInteractionQuerySchema)` to create a new message. */ -export const AskQuestionInteractionQuerySchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 112); +export const AskQuestionInteractionQuerySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 112); /** * @generated from message agent.v1.AskQuestionInteractionResponse */ export type AskQuestionInteractionResponse = Message<"agent.v1.AskQuestionInteractionResponse"> & { - /** - * @generated from field: agent.v1.AskQuestionResult result = 1; - */ - result?: AskQuestionResult; + /** + * @generated from field: agent.v1.AskQuestionResult result = 1; + */ + result?: AskQuestionResult | undefined; }; /** * Describes the message agent.v1.AskQuestionInteractionResponse. * Use `create(AskQuestionInteractionResponseSchema)` to create a new message. */ -export const AskQuestionInteractionResponseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 113); +export const AskQuestionInteractionResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 113); /** * @generated from message agent.v1.ClientHeartbeat */ -export type ClientHeartbeat = Message<"agent.v1.ClientHeartbeat"> & {}; +export type ClientHeartbeat = Message<"agent.v1.ClientHeartbeat"> & { +}; /** * Describes the message agent.v1.ClientHeartbeat. * Use `create(ClientHeartbeatSchema)` to create a new message. */ -export const ClientHeartbeatSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 114); +export const ClientHeartbeatSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 114); /** * Prewarm request - sent before the actual action to prepare the backend Contains all config needed for auth, model routing, and session building The actual ConversationAction is sent separately after prewarming completes @@ -3485,235 +3488,220 @@ export const ClientHeartbeatSchema: GenMessage = /*@__PURE__*/ * @generated from message agent.v1.PrewarmRequest */ export type PrewarmRequest = Message<"agent.v1.PrewarmRequest"> & { - /** - * @generated from field: agent.v1.ModelDetails model_details = 1; - */ - modelDetails?: ModelDetails; + /** + * @generated from field: agent.v1.ModelDetails model_details = 1; + */ + modelDetails?: ModelDetails | undefined; - /** - * @generated from field: optional agent.v1.RequestedModel requested_model = 9; - */ - requestedModel?: RequestedModel; + /** + * @generated from field: optional agent.v1.RequestedModel requested_model = 9; + */ + requestedModel?: RequestedModel | undefined; - /** - * @generated from field: optional string conversation_id = 2; - */ - conversationId?: string; + /** + * @generated from field: optional string conversation_id = 2; + */ + conversationId?: string | undefined; - /** - * @generated from field: agent.v1.ConversationStateStructure conversation_state = 3; - */ - conversationState?: ConversationStateStructure; + /** + * @generated from field: agent.v1.ConversationStateStructure conversation_state = 3; + */ + conversationState?: ConversationStateStructure | undefined; - /** - * @generated from field: agent.v1.McpTools mcp_tools = 4; - */ - mcpTools?: McpTools; + /** + * @generated from field: agent.v1.McpTools mcp_tools = 4; + */ + mcpTools?: McpTools | undefined; - /** - * @generated from field: optional agent.v1.McpFileSystemOptions mcp_file_system_options = 5; - */ - mcpFileSystemOptions?: McpFileSystemOptions; + /** + * @generated from field: optional agent.v1.McpFileSystemOptions mcp_file_system_options = 5; + */ + mcpFileSystemOptions?: McpFileSystemOptions | undefined; - /** - * Best-of-N context for usage billing (same fields as UserMessage) - * - * @generated from field: optional string best_of_n_group_id = 6; - */ - bestOfNGroupId?: string; + /** + * Best-of-N context for usage billing (same fields as UserMessage) + * + * @generated from field: optional string best_of_n_group_id = 6; + */ + bestOfNGroupId?: string | undefined; - /** - * @generated from field: optional bool try_use_best_of_n_promotion = 7; - */ - tryUseBestOfNPromotion?: boolean; + /** + * @generated from field: optional bool try_use_best_of_n_promotion = 7; + */ + tryUseBestOfNPromotion?: boolean | undefined; - /** - * Custom system prompt override. Allowlisted for specific teams only. - * - * @generated from field: optional string custom_system_prompt = 8; - */ - customSystemPrompt?: string; + /** + * Custom system prompt override. Allowlisted for specific teams only. + * + * @generated from field: optional string custom_system_prompt = 8; + */ + customSystemPrompt?: string | undefined; }; /** * Describes the message agent.v1.PrewarmRequest. * Use `create(PrewarmRequestSchema)` to create a new message. */ -export const PrewarmRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 115); +export const PrewarmRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 115); /** * @generated from message agent.v1.ExecServerAbort */ export type ExecServerAbort = Message<"agent.v1.ExecServerAbort"> & { - /** - * @generated from field: uint32 id = 1; - */ - id: number; + /** + * @generated from field: uint32 id = 1; + */ + id: number; }; /** * Describes the message agent.v1.ExecServerAbort. * Use `create(ExecServerAbortSchema)` to create a new message. */ -export const ExecServerAbortSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 116); +export const ExecServerAbortSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 116); /** * @generated from message agent.v1.ExecServerControlMessage */ export type ExecServerControlMessage = Message<"agent.v1.ExecServerControlMessage"> & { - /** - * @generated from oneof agent.v1.ExecServerControlMessage.message - */ - message: - | { - /** - * @generated from field: agent.v1.ExecServerAbort abort = 1; - */ - value: ExecServerAbort; - case: "abort"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ExecServerControlMessage.message + */ + message: { + /** + * @generated from field: agent.v1.ExecServerAbort abort = 1; + */ + value: ExecServerAbort; + case: "abort"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ExecServerControlMessage. * Use `create(ExecServerControlMessageSchema)` to create a new message. */ -export const ExecServerControlMessageSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 117); +export const ExecServerControlMessageSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 117); /** * @generated from message agent.v1.AgentClientMessage */ export type AgentClientMessage = Message<"agent.v1.AgentClientMessage"> & { - /** - * @generated from oneof agent.v1.AgentClientMessage.message - */ - message: - | { - /** - * @generated from field: agent.v1.AgentRunRequest run_request = 1; - */ - value: AgentRunRequest; - case: "runRequest"; - } - | { - /** - * @generated from field: agent.v1.ExecClientMessage exec_client_message = 2; - */ - value: ExecClientMessage; - case: "execClientMessage"; - } - | { - /** - * @generated from field: agent.v1.ExecClientControlMessage exec_client_control_message = 5; - */ - value: ExecClientControlMessage; - case: "execClientControlMessage"; - } - | { - /** - * @generated from field: agent.v1.KvClientMessage kv_client_message = 3; - */ - value: KvClientMessage; - case: "kvClientMessage"; - } - | { - /** - * @generated from field: agent.v1.ConversationAction conversation_action = 4; - */ - value: ConversationAction; - case: "conversationAction"; - } - | { - /** - * @generated from field: agent.v1.InteractionResponse interaction_response = 6; - */ - value: InteractionResponse; - case: "interactionResponse"; - } - | { - /** - * @generated from field: agent.v1.ClientHeartbeat client_heartbeat = 7; - */ - value: ClientHeartbeat; - case: "clientHeartbeat"; - } - | { - /** - * @generated from field: agent.v1.PrewarmRequest prewarm_request = 8; - */ - value: PrewarmRequest; - case: "prewarmRequest"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.AgentClientMessage.message + */ + message: { + /** + * @generated from field: agent.v1.AgentRunRequest run_request = 1; + */ + value: AgentRunRequest; + case: "runRequest"; + } | { + /** + * @generated from field: agent.v1.ExecClientMessage exec_client_message = 2; + */ + value: ExecClientMessage; + case: "execClientMessage"; + } | { + /** + * @generated from field: agent.v1.ExecClientControlMessage exec_client_control_message = 5; + */ + value: ExecClientControlMessage; + case: "execClientControlMessage"; + } | { + /** + * @generated from field: agent.v1.KvClientMessage kv_client_message = 3; + */ + value: KvClientMessage; + case: "kvClientMessage"; + } | { + /** + * @generated from field: agent.v1.ConversationAction conversation_action = 4; + */ + value: ConversationAction; + case: "conversationAction"; + } | { + /** + * @generated from field: agent.v1.InteractionResponse interaction_response = 6; + */ + value: InteractionResponse; + case: "interactionResponse"; + } | { + /** + * @generated from field: agent.v1.ClientHeartbeat client_heartbeat = 7; + */ + value: ClientHeartbeat; + case: "clientHeartbeat"; + } | { + /** + * @generated from field: agent.v1.PrewarmRequest prewarm_request = 8; + */ + value: PrewarmRequest; + case: "prewarmRequest"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.AgentClientMessage. * Use `create(AgentClientMessageSchema)` to create a new message. */ -export const AgentClientMessageSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 118); +export const AgentClientMessageSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 118); /** * @generated from message agent.v1.AgentServerMessage */ export type AgentServerMessage = Message<"agent.v1.AgentServerMessage"> & { - /** - * @generated from oneof agent.v1.AgentServerMessage.message - */ - message: - | { - /** - * @generated from field: agent.v1.InteractionUpdate interaction_update = 1; - */ - value: InteractionUpdate; - case: "interactionUpdate"; - } - | { - /** - * @generated from field: agent.v1.ExecServerMessage exec_server_message = 2; - */ - value: ExecServerMessage; - case: "execServerMessage"; - } - | { - /** - * @generated from field: agent.v1.ExecServerControlMessage exec_server_control_message = 5; - */ - value: ExecServerControlMessage; - case: "execServerControlMessage"; - } - | { - /** - * @generated from field: agent.v1.ConversationStateStructure conversation_checkpoint_update = 3; - */ - value: ConversationStateStructure; - case: "conversationCheckpointUpdate"; - } - | { - /** - * @generated from field: agent.v1.KvServerMessage kv_server_message = 4; - */ - value: KvServerMessage; - case: "kvServerMessage"; - } - | { - /** - * @generated from field: agent.v1.InteractionQuery interaction_query = 7; - */ - value: InteractionQuery; - case: "interactionQuery"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.AgentServerMessage.message + */ + message: { + /** + * @generated from field: agent.v1.InteractionUpdate interaction_update = 1; + */ + value: InteractionUpdate; + case: "interactionUpdate"; + } | { + /** + * @generated from field: agent.v1.ExecServerMessage exec_server_message = 2; + */ + value: ExecServerMessage; + case: "execServerMessage"; + } | { + /** + * @generated from field: agent.v1.ExecServerControlMessage exec_server_control_message = 5; + */ + value: ExecServerControlMessage; + case: "execServerControlMessage"; + } | { + /** + * @generated from field: agent.v1.ConversationStateStructure conversation_checkpoint_update = 3; + */ + value: ConversationStateStructure; + case: "conversationCheckpointUpdate"; + } | { + /** + * @generated from field: agent.v1.KvServerMessage kv_server_message = 4; + */ + value: KvServerMessage; + case: "kvServerMessage"; + } | { + /** + * @generated from field: agent.v1.InteractionQuery interaction_query = 7; + */ + value: InteractionQuery; + case: "interactionQuery"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.AgentServerMessage. * Use `create(AgentServerMessageSchema)` to create a new message. */ -export const AgentServerMessageSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 119); +export const AgentServerMessageSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 119); /** * New unary API for naming an agent from a user message @@ -3721,135 +3709,133 @@ export const AgentServerMessageSchema: GenMessage = /*@__PUR * @generated from message agent.v1.NameAgentRequest */ export type NameAgentRequest = Message<"agent.v1.NameAgentRequest"> & { - /** - * @generated from field: string user_message = 1; - */ - userMessage: string; + /** + * @generated from field: string user_message = 1; + */ + userMessage: string; }; /** * Describes the message agent.v1.NameAgentRequest. * Use `create(NameAgentRequestSchema)` to create a new message. */ -export const NameAgentRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 120); +export const NameAgentRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 120); /** * @generated from message agent.v1.NameAgentResponse */ export type NameAgentResponse = Message<"agent.v1.NameAgentResponse"> & { - /** - * @generated from field: string name = 1; - */ - name: string; + /** + * @generated from field: string name = 1; + */ + name: string; }; /** * Describes the message agent.v1.NameAgentResponse. * Use `create(NameAgentResponseSchema)` to create a new message. */ -export const NameAgentResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 121); +export const NameAgentResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 121); /** * @generated from message agent.v1.GetUsableModelsRequest */ export type GetUsableModelsRequest = Message<"agent.v1.GetUsableModelsRequest"> & { - /** - * Not used right now, but can use to populate info about custom models the user passes in that we don't send down by default - * - * @generated from field: repeated string custom_model_ids = 1; - */ - customModelIds: string[]; + /** + * Not used right now, but can use to populate info about custom models the user passes in that we don't send down by default + * + * @generated from field: repeated string custom_model_ids = 1; + */ + customModelIds: string[]; }; /** * Describes the message agent.v1.GetUsableModelsRequest. * Use `create(GetUsableModelsRequestSchema)` to create a new message. */ -export const GetUsableModelsRequestSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 122); +export const GetUsableModelsRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 122); /** * @generated from message agent.v1.GetUsableModelsResponse */ export type GetUsableModelsResponse = Message<"agent.v1.GetUsableModelsResponse"> & { - /** - * @generated from field: repeated agent.v1.ModelDetails models = 1; - */ - models: ModelDetails[]; + /** + * @generated from field: repeated agent.v1.ModelDetails models = 1; + */ + models: ModelDetails[]; }; /** * Describes the message agent.v1.GetUsableModelsResponse. * Use `create(GetUsableModelsResponseSchema)` to create a new message. */ -export const GetUsableModelsResponseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 123); +export const GetUsableModelsResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 123); /** * @generated from message agent.v1.GetDefaultModelForCliRequest */ -export type GetDefaultModelForCliRequest = Message<"agent.v1.GetDefaultModelForCliRequest"> & {}; +export type GetDefaultModelForCliRequest = Message<"agent.v1.GetDefaultModelForCliRequest"> & { +}; /** * Describes the message agent.v1.GetDefaultModelForCliRequest. * Use `create(GetDefaultModelForCliRequestSchema)` to create a new message. */ -export const GetDefaultModelForCliRequestSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 124); +export const GetDefaultModelForCliRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 124); /** * @generated from message agent.v1.GetDefaultModelForCliResponse */ export type GetDefaultModelForCliResponse = Message<"agent.v1.GetDefaultModelForCliResponse"> & { - /** - * @generated from field: agent.v1.ModelDetails model = 1; - */ - model?: ModelDetails; + /** + * @generated from field: agent.v1.ModelDetails model = 1; + */ + model?: ModelDetails | undefined; }; /** * Describes the message agent.v1.GetDefaultModelForCliResponse. * Use `create(GetDefaultModelForCliResponseSchema)` to create a new message. */ -export const GetDefaultModelForCliResponseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 125); +export const GetDefaultModelForCliResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 125); /** * Internal endpoint: returns all allowed model intents for devs * * @generated from message agent.v1.GetAllowedModelIntentsRequest */ -export type GetAllowedModelIntentsRequest = Message<"agent.v1.GetAllowedModelIntentsRequest"> & {}; +export type GetAllowedModelIntentsRequest = Message<"agent.v1.GetAllowedModelIntentsRequest"> & { +}; /** * Describes the message agent.v1.GetAllowedModelIntentsRequest. * Use `create(GetAllowedModelIntentsRequestSchema)` to create a new message. */ -export const GetAllowedModelIntentsRequestSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 126); +export const GetAllowedModelIntentsRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 126); /** * @generated from message agent.v1.GetAllowedModelIntentsResponse */ export type GetAllowedModelIntentsResponse = Message<"agent.v1.GetAllowedModelIntentsResponse"> & { - /** - * @generated from field: repeated string model_intents = 1; - */ - modelIntents: string[]; + /** + * @generated from field: repeated string model_intents = 1; + */ + modelIntents: string[]; }; /** * Describes the message agent.v1.GetAllowedModelIntentsResponse. * Use `create(GetAllowedModelIntentsResponseSchema)` to create a new message. */ -export const GetAllowedModelIntentsResponseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 127); +export const GetAllowedModelIntentsResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 127); /** * IDE state persistence for clients (CLI / VSCode integration) Mirrors a subset of aiserver.v1.ConversationMessage.IdeEditorsState, but only contains the recently viewed files and avoids any deprecated fields. @@ -3857,512 +3843,547 @@ export const GetAllowedModelIntentsResponseSchema: GenMessage & { - /** - * @generated from field: string relative_path = 1; - */ - relativePath: string; + /** + * @generated from field: string relative_path = 1; + */ + relativePath: string; - /** - * @generated from field: string absolute_path = 2; - */ - absolutePath: string; + /** + * @generated from field: string absolute_path = 2; + */ + absolutePath: string; - /** - * @generated from field: optional bool is_currently_focused = 3; - */ - isCurrentlyFocused?: boolean; + /** + * @generated from field: optional bool is_currently_focused = 3; + */ + isCurrentlyFocused?: boolean | undefined; - /** - * @generated from field: optional int32 current_line_number = 4; - */ - currentLineNumber?: number; + /** + * @generated from field: optional int32 current_line_number = 4; + */ + currentLineNumber?: number | undefined; - /** - * @generated from field: optional string current_line_text = 5; - */ - currentLineText?: string; + /** + * @generated from field: optional string current_line_text = 5; + */ + currentLineText?: string | undefined; - /** - * @generated from field: optional int32 line_count = 6; - */ - lineCount?: number; + /** + * @generated from field: optional int32 line_count = 6; + */ + lineCount?: number | undefined; }; /** * Describes the message agent.v1.IdeEditorsStateFile. * Use `create(IdeEditorsStateFileSchema)` to create a new message. */ -export const IdeEditorsStateFileSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 128); +export const IdeEditorsStateFileSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 128); /** * @generated from message agent.v1.IdeEditorsStateLite */ export type IdeEditorsStateLite = Message<"agent.v1.IdeEditorsStateLite"> & { - /** - * @generated from field: repeated agent.v1.IdeEditorsStateFile recently_viewed_files = 1; - */ - recentlyViewedFiles: IdeEditorsStateFile[]; + /** + * @generated from field: repeated agent.v1.IdeEditorsStateFile recently_viewed_files = 1; + */ + recentlyViewedFiles: IdeEditorsStateFile[]; }; /** * Describes the message agent.v1.IdeEditorsStateLite. * Use `create(IdeEditorsStateLiteSchema)` to create a new message. */ -export const IdeEditorsStateLiteSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 129); +export const IdeEditorsStateLiteSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 129); /** * @generated from message agent.v1.ApplyAgentDiffToolCall */ export type ApplyAgentDiffToolCall = Message<"agent.v1.ApplyAgentDiffToolCall"> & { - /** - * @generated from field: agent.v1.ApplyAgentDiffArgs args = 1; - */ - args?: ApplyAgentDiffArgs; + /** + * @generated from field: agent.v1.ApplyAgentDiffArgs args = 1; + */ + args?: ApplyAgentDiffArgs | undefined; - /** - * @generated from field: agent.v1.ApplyAgentDiffResult result = 2; - */ - result?: ApplyAgentDiffResult; + /** + * @generated from field: agent.v1.ApplyAgentDiffResult result = 2; + */ + result?: ApplyAgentDiffResult | undefined; }; /** * Describes the message agent.v1.ApplyAgentDiffToolCall. * Use `create(ApplyAgentDiffToolCallSchema)` to create a new message. */ -export const ApplyAgentDiffToolCallSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 130); +export const ApplyAgentDiffToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 130); /** * @generated from message agent.v1.ApplyAgentDiffArgs */ export type ApplyAgentDiffArgs = Message<"agent.v1.ApplyAgentDiffArgs"> & { - /** - * @generated from field: string agent_id = 1; - */ - agentId: string; + /** + * @generated from field: string agent_id = 1; + */ + agentId: string; }; /** * Describes the message agent.v1.ApplyAgentDiffArgs. * Use `create(ApplyAgentDiffArgsSchema)` to create a new message. */ -export const ApplyAgentDiffArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 131); +export const ApplyAgentDiffArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 131); /** * @generated from message agent.v1.ApplyAgentDiffResult */ export type ApplyAgentDiffResult = Message<"agent.v1.ApplyAgentDiffResult"> & { - /** - * @generated from oneof agent.v1.ApplyAgentDiffResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.ApplyAgentDiffSuccess success = 1; - */ - value: ApplyAgentDiffSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.ApplyAgentDiffError error = 2; - */ - value: ApplyAgentDiffError; - case: "error"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ApplyAgentDiffResult.result + */ + result: { + /** + * @generated from field: agent.v1.ApplyAgentDiffSuccess success = 1; + */ + value: ApplyAgentDiffSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.ApplyAgentDiffError error = 2; + */ + value: ApplyAgentDiffError; + case: "error"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ApplyAgentDiffResult. * Use `create(ApplyAgentDiffResultSchema)` to create a new message. */ -export const ApplyAgentDiffResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 132); +export const ApplyAgentDiffResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 132); /** * @generated from message agent.v1.ApplyAgentDiffSuccess */ export type ApplyAgentDiffSuccess = Message<"agent.v1.ApplyAgentDiffSuccess"> & { - /** - * @generated from field: repeated agent.v1.AppliedAgentChange applied_changes = 1; - */ - appliedChanges: AppliedAgentChange[]; + /** + * @generated from field: repeated agent.v1.AppliedAgentChange applied_changes = 1; + */ + appliedChanges: AppliedAgentChange[]; }; /** * Describes the message agent.v1.ApplyAgentDiffSuccess. * Use `create(ApplyAgentDiffSuccessSchema)` to create a new message. */ -export const ApplyAgentDiffSuccessSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 133); +export const ApplyAgentDiffSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 133); /** * @generated from message agent.v1.AppliedAgentChange */ export type AppliedAgentChange = Message<"agent.v1.AppliedAgentChange"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: int32 change_type = 2; - */ - changeType: number; + /** + * @generated from field: int32 change_type = 2; + */ + changeType: number; - /** - * @generated from field: optional string before_content = 3; - */ - beforeContent?: string; + /** + * @generated from field: optional string before_content = 3; + */ + beforeContent?: string | undefined; - /** - * @generated from field: optional string after_content = 4; - */ - afterContent?: string; + /** + * @generated from field: optional string after_content = 4; + */ + afterContent?: string | undefined; - /** - * @generated from field: optional string error = 5; - */ - error?: string; + /** + * @generated from field: optional string error = 5; + */ + error?: string | undefined; - /** - * Detailed result message from the execution (e.g., "Successfully deleted file: path (123 bytes)") - * - * @generated from field: optional string message_for_model = 6; - */ - messageForModel?: string; + /** + * Detailed result message from the execution (e.g., "Successfully deleted file: path (123 bytes)") + * + * @generated from field: optional string message_for_model = 6; + */ + messageForModel?: string | undefined; }; /** * Describes the message agent.v1.AppliedAgentChange. * Use `create(AppliedAgentChangeSchema)` to create a new message. */ -export const AppliedAgentChangeSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 134); +export const AppliedAgentChangeSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 134); /** * @generated from message agent.v1.ApplyAgentDiffError */ export type ApplyAgentDiffError = Message<"agent.v1.ApplyAgentDiffError"> & { - /** - * @generated from field: string error = 1; - */ - error: string; + /** + * @generated from field: string error = 1; + */ + error: string; - /** - * @generated from field: repeated agent.v1.AppliedAgentChange applied_changes = 2; - */ - appliedChanges: AppliedAgentChange[]; + /** + * @generated from field: repeated agent.v1.AppliedAgentChange applied_changes = 2; + */ + appliedChanges: AppliedAgentChange[]; }; /** * Describes the message agent.v1.ApplyAgentDiffError. * Use `create(ApplyAgentDiffErrorSchema)` to create a new message. */ -export const ApplyAgentDiffErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 135); +export const ApplyAgentDiffErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 135); /** * @generated from message agent.v1.AskQuestionToolCall */ export type AskQuestionToolCall = Message<"agent.v1.AskQuestionToolCall"> & { - /** - * @generated from field: agent.v1.AskQuestionArgs args = 1; - */ - args?: AskQuestionArgs; + /** + * @generated from field: agent.v1.AskQuestionArgs args = 1; + */ + args?: AskQuestionArgs | undefined; - /** - * @generated from field: agent.v1.AskQuestionResult result = 2; - */ - result?: AskQuestionResult; + /** + * @generated from field: agent.v1.AskQuestionResult result = 2; + */ + result?: AskQuestionResult | undefined; }; /** * Describes the message agent.v1.AskQuestionToolCall. * Use `create(AskQuestionToolCallSchema)` to create a new message. */ -export const AskQuestionToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 136); +export const AskQuestionToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 136); /** * @generated from message agent.v1.AskQuestionArgs */ export type AskQuestionArgs = Message<"agent.v1.AskQuestionArgs"> & { - /** - * optional form title - * - * @generated from field: string title = 1; - */ - title: string; + /** + * optional form title + * + * @generated from field: string title = 1; + */ + title: string; - /** - * 1+ questions - * - * @generated from field: repeated agent.v1.AskQuestionArgs_Question questions = 2; - */ - questions: AskQuestionArgs_Question[]; + /** + * 1+ questions + * + * @generated from field: repeated agent.v1.AskQuestionArgs_Question questions = 2; + */ + questions: AskQuestionArgs_Question[]; - /** - * if true, return immediately with async marker instead of blocking - * - * @generated from field: bool run_async = 5; - */ - runAsync: boolean; + /** + * if true, return immediately with async marker instead of blocking + * + * @generated from field: bool run_async = 5; + */ + runAsync: boolean; - /** - * if set, indicates this is a synthetic completion for the original async tool call with this ID - * - * @generated from field: string async_original_tool_call_id = 6; - */ - asyncOriginalToolCallId: string; + /** + * if set, indicates this is a synthetic completion for the original async tool call with this ID + * + * @generated from field: string async_original_tool_call_id = 6; + */ + asyncOriginalToolCallId: string; }; /** * Describes the message agent.v1.AskQuestionArgs. * Use `create(AskQuestionArgsSchema)` to create a new message. */ -export const AskQuestionArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 137); +export const AskQuestionArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 137); /** * @generated from message agent.v1.AskQuestionArgs_Question */ export type AskQuestionArgs_Question = Message<"agent.v1.AskQuestionArgs_Question"> & { - /** - * unique, model-provided - * - * @generated from field: string id = 1; - */ - id: string; + /** + * unique, model-provided + * + * @generated from field: string id = 1; + */ + id: string; - /** - * the question text - * - * @generated from field: string prompt = 2; - */ - prompt: string; + /** + * the question text + * + * @generated from field: string prompt = 2; + */ + prompt: string; - /** - * choices - * - * @generated from field: repeated agent.v1.AskQuestionArgs_Option options = 3; - */ - options: AskQuestionArgs_Option[]; + /** + * choices + * + * @generated from field: repeated agent.v1.AskQuestionArgs_Option options = 3; + */ + options: AskQuestionArgs_Option[]; - /** - * multi-select vs single-select - * - * @generated from field: bool allow_multiple = 4; - */ - allowMultiple: boolean; + /** + * multi-select vs single-select + * + * @generated from field: bool allow_multiple = 4; + */ + allowMultiple: boolean; }; /** * Describes the message agent.v1.AskQuestionArgs_Question. * Use `create(AskQuestionArgs_QuestionSchema)` to create a new message. */ -export const AskQuestionArgs_QuestionSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 138); +export const AskQuestionArgs_QuestionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 138); /** * @generated from message agent.v1.AskQuestionArgs_Option */ export type AskQuestionArgs_Option = Message<"agent.v1.AskQuestionArgs_Option"> & { - /** - * stable option id - * - * @generated from field: string id = 1; - */ - id: string; + /** + * stable option id + * + * @generated from field: string id = 1; + */ + id: string; - /** - * display text - * - * @generated from field: string label = 2; - */ - label: string; + /** + * display text + * + * @generated from field: string label = 2; + */ + label: string; }; /** * Describes the message agent.v1.AskQuestionArgs_Option. * Use `create(AskQuestionArgs_OptionSchema)` to create a new message. */ -export const AskQuestionArgs_OptionSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 139); +export const AskQuestionArgs_OptionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 139); /** * Marker indicating that questions have been sent asynchronously Answers will arrive later as a separate ask_question tool call * * @generated from message agent.v1.AskQuestionAsync */ -export type AskQuestionAsync = Message<"agent.v1.AskQuestionAsync"> & {}; +export type AskQuestionAsync = Message<"agent.v1.AskQuestionAsync"> & { +}; /** * Describes the message agent.v1.AskQuestionAsync. * Use `create(AskQuestionAsyncSchema)` to create a new message. */ -export const AskQuestionAsyncSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 140); +export const AskQuestionAsyncSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 140); /** * @generated from message agent.v1.AskQuestionResult */ export type AskQuestionResult = Message<"agent.v1.AskQuestionResult"> & { - /** - * @generated from oneof agent.v1.AskQuestionResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.AskQuestionSuccess success = 1; - */ - value: AskQuestionSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.AskQuestionError error = 2; - */ - value: AskQuestionError; - case: "error"; - } - | { - /** - * @generated from field: agent.v1.AskQuestionRejected rejected = 3; - */ - value: AskQuestionRejected; - case: "rejected"; - } - | { - /** - * @generated from field: agent.v1.AskQuestionAsync async = 4; - */ - value: AskQuestionAsync; - case: "async"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.AskQuestionResult.result + */ + result: { + /** + * @generated from field: agent.v1.AskQuestionSuccess success = 1; + */ + value: AskQuestionSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.AskQuestionError error = 2; + */ + value: AskQuestionError; + case: "error"; + } | { + /** + * @generated from field: agent.v1.AskQuestionRejected rejected = 3; + */ + value: AskQuestionRejected; + case: "rejected"; + } | { + /** + * @generated from field: agent.v1.AskQuestionAsync async = 4; + */ + value: AskQuestionAsync; + case: "async"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.AskQuestionResult. * Use `create(AskQuestionResultSchema)` to create a new message. */ -export const AskQuestionResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 141); +export const AskQuestionResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 141); /** * @generated from message agent.v1.AskQuestionSuccess */ export type AskQuestionSuccess = Message<"agent.v1.AskQuestionSuccess"> & { - /** - * @generated from field: repeated agent.v1.AskQuestionSuccess_Answer answers = 1; - */ - answers: AskQuestionSuccess_Answer[]; + /** + * @generated from field: repeated agent.v1.AskQuestionSuccess_Answer answers = 1; + */ + answers: AskQuestionSuccess_Answer[]; }; /** * Describes the message agent.v1.AskQuestionSuccess. * Use `create(AskQuestionSuccessSchema)` to create a new message. */ -export const AskQuestionSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 142); +export const AskQuestionSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 142); /** * @generated from message agent.v1.AskQuestionSuccess_Answer */ export type AskQuestionSuccess_Answer = Message<"agent.v1.AskQuestionSuccess_Answer"> & { - /** - * @generated from field: string question_id = 1; - */ - questionId: string; + /** + * @generated from field: string question_id = 1; + */ + questionId: string; - /** - * empty if unanswered - * - * @generated from field: repeated string selected_option_ids = 2; - */ - selectedOptionIds: string[]; + /** + * empty if unanswered + * + * @generated from field: repeated string selected_option_ids = 2; + */ + selectedOptionIds: string[]; }; /** * Describes the message agent.v1.AskQuestionSuccess_Answer. * Use `create(AskQuestionSuccess_AnswerSchema)` to create a new message. */ -export const AskQuestionSuccess_AnswerSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 143); +export const AskQuestionSuccess_AnswerSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 143); /** * @generated from message agent.v1.AskQuestionError */ export type AskQuestionError = Message<"agent.v1.AskQuestionError"> & { - /** - * @generated from field: string error_message = 1; - */ - errorMessage: string; + /** + * @generated from field: string error_message = 1; + */ + errorMessage: string; }; /** * Describes the message agent.v1.AskQuestionError. * Use `create(AskQuestionErrorSchema)` to create a new message. */ -export const AskQuestionErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 144); +export const AskQuestionErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 144); /** * @generated from message agent.v1.AskQuestionRejected */ export type AskQuestionRejected = Message<"agent.v1.AskQuestionRejected"> & { - /** - * @generated from field: string reason = 1; - */ - reason: string; + /** + * @generated from field: string reason = 1; + */ + reason: string; }; /** * Describes the message agent.v1.AskQuestionRejected. * Use `create(AskQuestionRejectedSchema)` to create a new message. */ -export const AskQuestionRejectedSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 145); +export const AskQuestionRejectedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 145); /** * @generated from message agent.v1.BackgroundShellSpawnArgs */ export type BackgroundShellSpawnArgs = Message<"agent.v1.BackgroundShellSpawnArgs"> & { - /** - * @generated from field: string command = 1; - */ - command: string; + /** + * @generated from field: string command = 1; + */ + command: string; - /** - * @generated from field: string working_directory = 2; - */ - workingDirectory: string; + /** + * @generated from field: string working_directory = 2; + */ + workingDirectory: string; - /** - * @generated from field: string tool_call_id = 3; - */ - toolCallId: string; + /** + * @generated from field: string tool_call_id = 3; + */ + toolCallId: string; - /** - * @generated from field: agent.v1.ShellCommandParsingResult parsing_result = 4; - */ - parsingResult?: ShellCommandParsingResult; + /** + * @generated from field: agent.v1.ShellCommandParsingResult parsing_result = 4; + */ + parsingResult?: ShellCommandParsingResult | undefined; - /** - * @generated from field: optional agent.v1.SandboxPolicy sandbox_policy = 5; - */ - sandboxPolicy?: SandboxPolicy; + /** + * @generated from field: optional agent.v1.SandboxPolicy sandbox_policy = 5; + */ + sandboxPolicy?: SandboxPolicy | undefined; - /** - * @generated from field: bool enable_write_shell_stdin_tool = 6; - */ - enableWriteShellStdinTool: boolean; + /** + * @generated from field: bool enable_write_shell_stdin_tool = 6; + */ + enableWriteShellStdinTool: boolean; + + /** + * @generated from field: optional string description = 7; + */ + description?: string | undefined; + + /** + * @generated from field: optional agent.v1.CommandClassifierResult classifier_result = 8; + */ + classifierResult?: CommandClassifierResult | undefined; + + /** + * @generated from field: optional agent.v1.ShellOutputNotificationConfig output_notification = 9; + */ + outputNotification?: ShellOutputNotificationConfig | undefined; + + /** + * @generated from field: optional agent.v1.SmartModeApproval smart_mode_approval = 10; + */ + smartModeApproval?: SmartModeApproval | undefined; + + /** + * @generated from field: optional agent.v1.ShellHookApprovalRequirement hook_approval_requirement = 11; + */ + hookApprovalRequirement?: ShellHookApprovalRequirement | undefined; + + /** + * @generated from field: bool skip_approval = 12; + */ + skipApproval: boolean; + + /** + * @generated from field: optional string conversation_id = 13; + */ + conversationId?: string | undefined; }; /** * Describes the message agent.v1.BackgroundShellSpawnArgs. * Use `create(BackgroundShellSpawnArgsSchema)` to create a new message. */ -export const BackgroundShellSpawnArgsSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 146); +export const BackgroundShellSpawnArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 146); /** * Result of spawning a background shell @@ -4370,225 +4391,215 @@ export const BackgroundShellSpawnArgsSchema: GenMessage & { - /** - * @generated from oneof agent.v1.BackgroundShellSpawnResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.BackgroundShellSpawnSuccess success = 1; - */ - value: BackgroundShellSpawnSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.BackgroundShellSpawnError error = 2; - */ - value: BackgroundShellSpawnError; - case: "error"; - } - | { - /** - * @generated from field: agent.v1.ShellRejected rejected = 3; - */ - value: ShellRejected; - case: "rejected"; - } - | { - /** - * @generated from field: agent.v1.ShellPermissionDenied permission_denied = 4; - */ - value: ShellPermissionDenied; - case: "permissionDenied"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.BackgroundShellSpawnResult.result + */ + result: { + /** + * @generated from field: agent.v1.BackgroundShellSpawnSuccess success = 1; + */ + value: BackgroundShellSpawnSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.BackgroundShellSpawnError error = 2; + */ + value: BackgroundShellSpawnError; + case: "error"; + } | { + /** + * @generated from field: agent.v1.ShellRejected rejected = 3; + */ + value: ShellRejected; + case: "rejected"; + } | { + /** + * @generated from field: agent.v1.ShellPermissionDenied permission_denied = 4; + */ + value: ShellPermissionDenied; + case: "permissionDenied"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.BackgroundShellSpawnResult. * Use `create(BackgroundShellSpawnResultSchema)` to create a new message. */ -export const BackgroundShellSpawnResultSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 147); +export const BackgroundShellSpawnResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 147); /** * @generated from message agent.v1.BackgroundShellSpawnSuccess */ export type BackgroundShellSpawnSuccess = Message<"agent.v1.BackgroundShellSpawnSuccess"> & { - /** - * @generated from field: uint32 shell_id = 1; - */ - shellId: number; + /** + * @generated from field: uint32 shell_id = 1; + */ + shellId: number; - /** - * @generated from field: string command = 2; - */ - command: string; + /** + * @generated from field: string command = 2; + */ + command: string; - /** - * @generated from field: string working_directory = 3; - */ - workingDirectory: string; + /** + * @generated from field: string working_directory = 3; + */ + workingDirectory: string; - /** - * Process ID of the spawned shell - * - * @generated from field: optional uint32 pid = 4; - */ - pid?: number; + /** + * Process ID of the spawned shell + * + * @generated from field: optional uint32 pid = 4; + */ + pid?: number | undefined; }; /** * Describes the message agent.v1.BackgroundShellSpawnSuccess. * Use `create(BackgroundShellSpawnSuccessSchema)` to create a new message. */ -export const BackgroundShellSpawnSuccessSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 148); +export const BackgroundShellSpawnSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 148); /** * @generated from message agent.v1.BackgroundShellSpawnError */ export type BackgroundShellSpawnError = Message<"agent.v1.BackgroundShellSpawnError"> & { - /** - * @generated from field: string command = 1; - */ - command: string; + /** + * @generated from field: string command = 1; + */ + command: string; - /** - * @generated from field: string working_directory = 2; - */ - workingDirectory: string; + /** + * @generated from field: string working_directory = 2; + */ + workingDirectory: string; - /** - * @generated from field: string error = 3; - */ - error: string; + /** + * @generated from field: string error = 3; + */ + error: string; }; /** * Describes the message agent.v1.BackgroundShellSpawnError. * Use `create(BackgroundShellSpawnErrorSchema)` to create a new message. */ -export const BackgroundShellSpawnErrorSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 149); +export const BackgroundShellSpawnErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 149); /** * @generated from message agent.v1.WriteShellStdinArgs */ export type WriteShellStdinArgs = Message<"agent.v1.WriteShellStdinArgs"> & { - /** - * @generated from field: uint32 shell_id = 1; - */ - shellId: number; + /** + * @generated from field: uint32 shell_id = 1; + */ + shellId: number; - /** - * @generated from field: string chars = 2; - */ - chars: string; + /** + * @generated from field: string chars = 2; + */ + chars: string; }; /** * Describes the message agent.v1.WriteShellStdinArgs. * Use `create(WriteShellStdinArgsSchema)` to create a new message. */ -export const WriteShellStdinArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 150); +export const WriteShellStdinArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 150); /** * @generated from message agent.v1.WriteShellStdinResult */ export type WriteShellStdinResult = Message<"agent.v1.WriteShellStdinResult"> & { - /** - * @generated from oneof agent.v1.WriteShellStdinResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.WriteShellStdinSuccess success = 1; - */ - value: WriteShellStdinSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.WriteShellStdinError error = 2; - */ - value: WriteShellStdinError; - case: "error"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.WriteShellStdinResult.result + */ + result: { + /** + * @generated from field: agent.v1.WriteShellStdinSuccess success = 1; + */ + value: WriteShellStdinSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.WriteShellStdinError error = 2; + */ + value: WriteShellStdinError; + case: "error"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.WriteShellStdinResult. * Use `create(WriteShellStdinResultSchema)` to create a new message. */ -export const WriteShellStdinResultSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 151); +export const WriteShellStdinResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 151); /** * @generated from message agent.v1.WriteShellStdinSuccess */ export type WriteShellStdinSuccess = Message<"agent.v1.WriteShellStdinSuccess"> & { - /** - * @generated from field: uint32 shell_id = 1; - */ - shellId: number; + /** + * @generated from field: uint32 shell_id = 1; + */ + shellId: number; - /** - * @generated from field: uint32 terminal_file_length_before_input_written = 2; - */ - terminalFileLengthBeforeInputWritten: number; + /** + * @generated from field: uint32 terminal_file_length_before_input_written = 2; + */ + terminalFileLengthBeforeInputWritten: number; }; /** * Describes the message agent.v1.WriteShellStdinSuccess. * Use `create(WriteShellStdinSuccessSchema)` to create a new message. */ -export const WriteShellStdinSuccessSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 152); +export const WriteShellStdinSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 152); /** * @generated from message agent.v1.WriteShellStdinError */ export type WriteShellStdinError = Message<"agent.v1.WriteShellStdinError"> & { - /** - * @generated from field: string error = 1; - */ - error: string; + /** + * @generated from field: string error = 1; + */ + error: string; }; /** * Describes the message agent.v1.WriteShellStdinError. * Use `create(WriteShellStdinErrorSchema)` to create a new message. */ -export const WriteShellStdinErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 153); +export const WriteShellStdinErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 153); /** * @generated from message agent.v1.Coordinate */ export type Coordinate = Message<"agent.v1.Coordinate"> & { - /** - * @generated from field: int32 x = 1; - */ - x: number; + /** + * @generated from field: int32 x = 1; + */ + x: number; - /** - * @generated from field: int32 y = 2; - */ - y: number; + /** + * @generated from field: int32 y = 2; + */ + y: number; }; /** * Describes the message agent.v1.Coordinate. * Use `create(CoordinateSchema)` to create a new message. */ -export const CoordinateSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 154); +export const CoordinateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 154); /** * Arguments for the computer-use tool @@ -4596,22 +4607,23 @@ export const CoordinateSchema: GenMessage = /*@__PURE__*/ messageDes * @generated from message agent.v1.ComputerUseArgs */ export type ComputerUseArgs = Message<"agent.v1.ComputerUseArgs"> & { - /** - * @generated from field: string tool_call_id = 1; - */ - toolCallId: string; + /** + * @generated from field: string tool_call_id = 1; + */ + toolCallId: string; - /** - * @generated from field: repeated agent.v1.ComputerUseAction actions = 2; - */ - actions: ComputerUseAction[]; + /** + * @generated from field: repeated agent.v1.ComputerUseAction actions = 2; + */ + actions: ComputerUseAction[]; }; /** * Describes the message agent.v1.ComputerUseArgs. * Use `create(ComputerUseArgsSchema)` to create a new message. */ -export const ComputerUseArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 155); +export const ComputerUseArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 155); /** * A single computer-use action. This is our internal canonical representation. Provider-specific formats are converted to this by adapters. @@ -4619,95 +4631,84 @@ export const ComputerUseArgsSchema: GenMessage = /*@__PURE__*/ * @generated from message agent.v1.ComputerUseAction */ export type ComputerUseAction = Message<"agent.v1.ComputerUseAction"> & { - /** - * @generated from oneof agent.v1.ComputerUseAction.action - */ - action: - | { - /** - * @generated from field: agent.v1.MouseMoveAction mouse_move = 1; - */ - value: MouseMoveAction; - case: "mouseMove"; - } - | { - /** - * @generated from field: agent.v1.ClickAction click = 2; - */ - value: ClickAction; - case: "click"; - } - | { - /** - * @generated from field: agent.v1.MouseDownAction mouse_down = 3; - */ - value: MouseDownAction; - case: "mouseDown"; - } - | { - /** - * @generated from field: agent.v1.MouseUpAction mouse_up = 4; - */ - value: MouseUpAction; - case: "mouseUp"; - } - | { - /** - * @generated from field: agent.v1.DragAction drag = 5; - */ - value: DragAction; - case: "drag"; - } - | { - /** - * @generated from field: agent.v1.ScrollAction scroll = 6; - */ - value: ScrollAction; - case: "scroll"; - } - | { - /** - * @generated from field: agent.v1.TypeAction type = 7; - */ - value: TypeAction; - case: "type"; - } - | { - /** - * @generated from field: agent.v1.KeyAction key = 8; - */ - value: KeyAction; - case: "key"; - } - | { - /** - * @generated from field: agent.v1.WaitAction wait = 9; - */ - value: WaitAction; - case: "wait"; - } - | { - /** - * @generated from field: agent.v1.ScreenshotAction screenshot = 10; - */ - value: ScreenshotAction; - case: "screenshot"; - } - | { - /** - * @generated from field: agent.v1.CursorPositionAction cursor_position = 11; - */ - value: CursorPositionAction; - case: "cursorPosition"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ComputerUseAction.action + */ + action: { + /** + * @generated from field: agent.v1.MouseMoveAction mouse_move = 1; + */ + value: MouseMoveAction; + case: "mouseMove"; + } | { + /** + * @generated from field: agent.v1.ClickAction click = 2; + */ + value: ClickAction; + case: "click"; + } | { + /** + * @generated from field: agent.v1.MouseDownAction mouse_down = 3; + */ + value: MouseDownAction; + case: "mouseDown"; + } | { + /** + * @generated from field: agent.v1.MouseUpAction mouse_up = 4; + */ + value: MouseUpAction; + case: "mouseUp"; + } | { + /** + * @generated from field: agent.v1.DragAction drag = 5; + */ + value: DragAction; + case: "drag"; + } | { + /** + * @generated from field: agent.v1.ScrollAction scroll = 6; + */ + value: ScrollAction; + case: "scroll"; + } | { + /** + * @generated from field: agent.v1.TypeAction type = 7; + */ + value: TypeAction; + case: "type"; + } | { + /** + * @generated from field: agent.v1.KeyAction key = 8; + */ + value: KeyAction; + case: "key"; + } | { + /** + * @generated from field: agent.v1.WaitAction wait = 9; + */ + value: WaitAction; + case: "wait"; + } | { + /** + * @generated from field: agent.v1.ScreenshotAction screenshot = 10; + */ + value: ScreenshotAction; + case: "screenshot"; + } | { + /** + * @generated from field: agent.v1.CursorPositionAction cursor_position = 11; + */ + value: CursorPositionAction; + case: "cursorPosition"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ComputerUseAction. * Use `create(ComputerUseActionSchema)` to create a new message. */ -export const ComputerUseActionSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 156); +export const ComputerUseActionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 156); /** * Move mouse to coordinate (required) @@ -4715,17 +4716,18 @@ export const ComputerUseActionSchema: GenMessage = /*@__PURE_ * @generated from message agent.v1.MouseMoveAction */ export type MouseMoveAction = Message<"agent.v1.MouseMoveAction"> & { - /** - * @generated from field: agent.v1.Coordinate coordinate = 1; - */ - coordinate?: Coordinate; + /** + * @generated from field: agent.v1.Coordinate coordinate = 1; + */ + coordinate?: Coordinate | undefined; }; /** * Describes the message agent.v1.MouseMoveAction. * Use `create(MouseMoveActionSchema)` to create a new message. */ -export const MouseMoveActionSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 157); +export const MouseMoveActionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 157); /** * Unified click action - coordinate: optional, clicks at current cursor if omitted - button: which mouse button (default: LEFT) - count: click count (1=single, 2=double, 3=triple, default: 1) - modifier_keys: optional, held during click (e.g., "ctrl", "shift", "ctrl+shift") @@ -4733,32 +4735,33 @@ export const MouseMoveActionSchema: GenMessage = /*@__PURE__*/ * @generated from message agent.v1.ClickAction */ export type ClickAction = Message<"agent.v1.ClickAction"> & { - /** - * @generated from field: optional agent.v1.Coordinate coordinate = 1; - */ - coordinate?: Coordinate; + /** + * @generated from field: optional agent.v1.Coordinate coordinate = 1; + */ + coordinate?: Coordinate | undefined; - /** - * @generated from field: int32 button = 2; - */ - button: number; + /** + * @generated from field: int32 button = 2; + */ + button: number; - /** - * @generated from field: int32 count = 3; - */ - count: number; + /** + * @generated from field: int32 count = 3; + */ + count: number; - /** - * @generated from field: optional string modifier_keys = 4; - */ - modifierKeys?: string; + /** + * @generated from field: optional string modifier_keys = 4; + */ + modifierKeys?: string | undefined; }; /** * Describes the message agent.v1.ClickAction. * Use `create(ClickActionSchema)` to create a new message. */ -export const ClickActionSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 158); +export const ClickActionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 158); /** * Press mouse button down (for fine-grained drag control) @@ -4766,17 +4769,18 @@ export const ClickActionSchema: GenMessage = /*@__PURE__*/ messageD * @generated from message agent.v1.MouseDownAction */ export type MouseDownAction = Message<"agent.v1.MouseDownAction"> & { - /** - * @generated from field: int32 button = 1; - */ - button: number; + /** + * @generated from field: int32 button = 1; + */ + button: number; }; /** * Describes the message agent.v1.MouseDownAction. * Use `create(MouseDownActionSchema)` to create a new message. */ -export const MouseDownActionSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 159); +export const MouseDownActionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 159); /** * Release mouse button (for fine-grained drag control) @@ -4784,17 +4788,18 @@ export const MouseDownActionSchema: GenMessage = /*@__PURE__*/ * @generated from message agent.v1.MouseUpAction */ export type MouseUpAction = Message<"agent.v1.MouseUpAction"> & { - /** - * @generated from field: int32 button = 1; - */ - button: number; + /** + * @generated from field: int32 button = 1; + */ + button: number; }; /** * Describes the message agent.v1.MouseUpAction. * Use `create(MouseUpActionSchema)` to create a new message. */ -export const MouseUpActionSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 160); +export const MouseUpActionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 160); /** * Drag action - path of coordinates (at least 2 points: [start, ..., end]) @@ -4802,22 +4807,23 @@ export const MouseUpActionSchema: GenMessage = /*@__PURE__*/ mess * @generated from message agent.v1.DragAction */ export type DragAction = Message<"agent.v1.DragAction"> & { - /** - * @generated from field: repeated agent.v1.Coordinate path = 1; - */ - path: Coordinate[]; + /** + * @generated from field: repeated agent.v1.Coordinate path = 1; + */ + path: Coordinate[]; - /** - * @generated from field: int32 button = 2; - */ - button: number; + /** + * @generated from field: int32 button = 2; + */ + button: number; }; /** * Describes the message agent.v1.DragAction. * Use `create(DragActionSchema)` to create a new message. */ -export const DragActionSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 161); +export const DragActionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 161); /** * Scroll action - coordinate: optional, scrolls at current cursor if omitted - direction: scroll direction (required) - amount: number of scroll "clicks" (default: 3) - modifier_keys: optional, held during scroll (e.g., "ctrl" for zoom) @@ -4825,32 +4831,33 @@ export const DragActionSchema: GenMessage = /*@__PURE__*/ messageDes * @generated from message agent.v1.ScrollAction */ export type ScrollAction = Message<"agent.v1.ScrollAction"> & { - /** - * @generated from field: optional agent.v1.Coordinate coordinate = 1; - */ - coordinate?: Coordinate; + /** + * @generated from field: optional agent.v1.Coordinate coordinate = 1; + */ + coordinate?: Coordinate | undefined; - /** - * @generated from field: int32 direction = 2; - */ - direction: number; + /** + * @generated from field: int32 direction = 2; + */ + direction: number; - /** - * @generated from field: int32 amount = 3; - */ - amount: number; + /** + * @generated from field: int32 amount = 3; + */ + amount: number; - /** - * @generated from field: optional string modifier_keys = 4; - */ - modifierKeys?: string; + /** + * @generated from field: optional string modifier_keys = 4; + */ + modifierKeys?: string | undefined; }; /** * Describes the message agent.v1.ScrollAction. * Use `create(ScrollActionSchema)` to create a new message. */ -export const ScrollActionSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 162); +export const ScrollActionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 162); /** * Type text @@ -4858,17 +4865,18 @@ export const ScrollActionSchema: GenMessage = /*@__PURE__*/ messag * @generated from message agent.v1.TypeAction */ export type TypeAction = Message<"agent.v1.TypeAction"> & { - /** - * @generated from field: string text = 1; - */ - text: string; + /** + * @generated from field: string text = 1; + */ + text: string; }; /** * Describes the message agent.v1.TypeAction. * Use `create(TypeActionSchema)` to create a new message. */ -export const TypeActionSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 163); +export const TypeActionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 163); /** * Press key or key combination (xdotool-style: "ctrl+a", "Return", "Alt+Left") If hold_duration_ms is set, holds the key for that duration @@ -4876,22 +4884,23 @@ export const TypeActionSchema: GenMessage = /*@__PURE__*/ messageDes * @generated from message agent.v1.KeyAction */ export type KeyAction = Message<"agent.v1.KeyAction"> & { - /** - * @generated from field: string key = 1; - */ - key: string; + /** + * @generated from field: string key = 1; + */ + key: string; - /** - * @generated from field: optional int32 hold_duration_ms = 2; - */ - holdDurationMs?: number; + /** + * @generated from field: optional int32 hold_duration_ms = 2; + */ + holdDurationMs?: number | undefined; }; /** * Describes the message agent.v1.KeyAction. * Use `create(KeyActionSchema)` to create a new message. */ -export const KeyActionSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 164); +export const KeyActionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 164); /** * Wait for a duration @@ -4899,43 +4908,48 @@ export const KeyActionSchema: GenMessage = /*@__PURE__*/ messageDesc( * @generated from message agent.v1.WaitAction */ export type WaitAction = Message<"agent.v1.WaitAction"> & { - /** - * @generated from field: int32 duration_ms = 1; - */ - durationMs: number; + /** + * @generated from field: int32 duration_ms = 1; + */ + durationMs: number; }; /** * Describes the message agent.v1.WaitAction. * Use `create(WaitActionSchema)` to create a new message. */ -export const WaitActionSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 165); +export const WaitActionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 165); /** * Take a screenshot * * @generated from message agent.v1.ScreenshotAction */ -export type ScreenshotAction = Message<"agent.v1.ScreenshotAction"> & {}; +export type ScreenshotAction = Message<"agent.v1.ScreenshotAction"> & { +}; /** * Describes the message agent.v1.ScreenshotAction. * Use `create(ScreenshotActionSchema)` to create a new message. */ -export const ScreenshotActionSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 166); +export const ScreenshotActionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 166); /** * Get current cursor position * * @generated from message agent.v1.CursorPositionAction */ -export type CursorPositionAction = Message<"agent.v1.CursorPositionAction"> & {}; +export type CursorPositionAction = Message<"agent.v1.CursorPositionAction"> & { +}; /** * Describes the message agent.v1.CursorPositionAction. * Use `create(CursorPositionActionSchema)` to create a new message. */ -export const CursorPositionActionSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 167); +export const CursorPositionActionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 167); /** * Result of computer-use execution @@ -4943,164 +4957,166 @@ export const CursorPositionActionSchema: GenMessage = /*@_ * @generated from message agent.v1.ComputerUseResult */ export type ComputerUseResult = Message<"agent.v1.ComputerUseResult"> & { - /** - * @generated from oneof agent.v1.ComputerUseResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.ComputerUseSuccess success = 1; - */ - value: ComputerUseSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.ComputerUseError error = 2; - */ - value: ComputerUseError; - case: "error"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ComputerUseResult.result + */ + result: { + /** + * @generated from field: agent.v1.ComputerUseSuccess success = 1; + */ + value: ComputerUseSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.ComputerUseError error = 2; + */ + value: ComputerUseError; + case: "error"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ComputerUseResult. * Use `create(ComputerUseResultSchema)` to create a new message. */ -export const ComputerUseResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 168); +export const ComputerUseResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 168); /** * @generated from message agent.v1.ComputerUseSuccess */ export type ComputerUseSuccess = Message<"agent.v1.ComputerUseSuccess"> & { - /** - * @generated from field: int32 action_count = 1; - */ - actionCount: number; + /** + * @generated from field: int32 action_count = 1; + */ + actionCount: number; - /** - * @generated from field: int32 duration_ms = 2; - */ - durationMs: number; + /** + * @generated from field: int32 duration_ms = 2; + */ + durationMs: number; - /** - * Base64 WebP at API resolution - * - * @generated from field: optional string screenshot = 3; - */ - screenshot?: string; + /** + * Base64 WebP at API resolution + * + * @generated from field: optional string screenshot = 3; + */ + screenshot?: string | undefined; - /** - * @generated from field: optional string log = 4; - */ - log?: string; + /** + * @generated from field: optional string log = 4; + */ + log?: string | undefined; - /** - * @generated from field: optional string screenshot_path = 5; - */ - screenshotPath?: string; + /** + * @generated from field: optional string screenshot_path = 5; + */ + screenshotPath?: string | undefined; - /** - * In API resolution - * - * @generated from field: optional agent.v1.Coordinate cursor_position = 6; - */ - cursorPosition?: Coordinate; + /** + * In API resolution + * + * @generated from field: optional agent.v1.Coordinate cursor_position = 6; + */ + cursorPosition?: Coordinate | undefined; }; /** * Describes the message agent.v1.ComputerUseSuccess. * Use `create(ComputerUseSuccessSchema)` to create a new message. */ -export const ComputerUseSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 169); +export const ComputerUseSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 169); /** * @generated from message agent.v1.ComputerUseError */ export type ComputerUseError = Message<"agent.v1.ComputerUseError"> & { - /** - * @generated from field: string error = 1; - */ - error: string; + /** + * @generated from field: string error = 1; + */ + error: string; - /** - * @generated from field: int32 action_count = 2; - */ - actionCount: number; + /** + * @generated from field: int32 action_count = 2; + */ + actionCount: number; - /** - * @generated from field: int32 duration_ms = 3; - */ - durationMs: number; + /** + * @generated from field: int32 duration_ms = 3; + */ + durationMs: number; - /** - * @generated from field: optional string log = 4; - */ - log?: string; + /** + * @generated from field: optional string log = 4; + */ + log?: string | undefined; - /** - * Base64 WebP of screen state at error time - * - * @generated from field: optional string screenshot = 5; - */ - screenshot?: string; + /** + * Base64 WebP of screen state at error time + * + * @generated from field: optional string screenshot = 5; + */ + screenshot?: string | undefined; - /** - * Path where screenshot was saved - * - * @generated from field: optional string screenshot_path = 6; - */ - screenshotPath?: string; + /** + * Path where screenshot was saved + * + * @generated from field: optional string screenshot_path = 6; + */ + screenshotPath?: string | undefined; }; /** * Describes the message agent.v1.ComputerUseError. * Use `create(ComputerUseErrorSchema)` to create a new message. */ -export const ComputerUseErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 170); +export const ComputerUseErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 170); /** * @generated from message agent.v1.ComputerUseToolCall */ export type ComputerUseToolCall = Message<"agent.v1.ComputerUseToolCall"> & { - /** - * @generated from field: agent.v1.ComputerUseArgs args = 1; - */ - args?: ComputerUseArgs; + /** + * @generated from field: agent.v1.ComputerUseArgs args = 1; + */ + args?: ComputerUseArgs | undefined; - /** - * @generated from field: agent.v1.ComputerUseResult result = 2; - */ - result?: ComputerUseResult; + /** + * @generated from field: agent.v1.ComputerUseResult result = 2; + */ + result?: ComputerUseResult | undefined; }; /** * Describes the message agent.v1.ComputerUseToolCall. * Use `create(ComputerUseToolCallSchema)` to create a new message. */ -export const ComputerUseToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 171); +export const ComputerUseToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 171); /** * @generated from message agent.v1.CreatePlanToolCall */ export type CreatePlanToolCall = Message<"agent.v1.CreatePlanToolCall"> & { - /** - * @generated from field: agent.v1.CreatePlanArgs args = 1; - */ - args?: CreatePlanArgs; + /** + * @generated from field: agent.v1.CreatePlanArgs args = 1; + */ + args?: CreatePlanArgs | undefined; - /** - * @generated from field: agent.v1.CreatePlanResult result = 2; - */ - result?: CreatePlanResult; + /** + * @generated from field: agent.v1.CreatePlanResult result = 2; + */ + result?: CreatePlanResult | undefined; }; /** * Describes the message agent.v1.CreatePlanToolCall. * Use `create(CreatePlanToolCallSchema)` to create a new message. */ -export const CreatePlanToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 172); +export const CreatePlanToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 172); /** * A phase groups related todos together for project-mode plans @@ -5108,132 +5124,135 @@ export const CreatePlanToolCallSchema: GenMessage = /*@__PUR * @generated from message agent.v1.Phase */ export type Phase = Message<"agent.v1.Phase"> & { - /** - * @generated from field: string name = 1; - */ - name: string; + /** + * @generated from field: string name = 1; + */ + name: string; - /** - * @generated from field: repeated agent.v1.TodoItem todos = 2; - */ - todos: TodoItem[]; + /** + * @generated from field: repeated agent.v1.TodoItem todos = 2; + */ + todos: TodoItem[]; }; /** * Describes the message agent.v1.Phase. * Use `create(PhaseSchema)` to create a new message. */ -export const PhaseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 173); +export const PhaseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 173); /** * @generated from message agent.v1.CreatePlanArgs */ export type CreatePlanArgs = Message<"agent.v1.CreatePlanArgs"> & { - /** - * @generated from field: string plan = 1; - */ - plan: string; + /** + * @generated from field: string plan = 1; + */ + plan: string; - /** - * @generated from field: repeated agent.v1.TodoItem todos = 2; - */ - todos: TodoItem[]; + /** + * @generated from field: repeated agent.v1.TodoItem todos = 2; + */ + todos: TodoItem[]; - /** - * @generated from field: string overview = 3; - */ - overview: string; + /** + * @generated from field: string overview = 3; + */ + overview: string; - /** - * @generated from field: string name = 4; - */ - name: string; + /** + * @generated from field: string name = 4; + */ + name: string; - /** - * When true, uses phases instead of flat todos (mutually exclusive) - * - * @generated from field: bool is_project = 5; - */ - isProject: boolean; + /** + * When true, uses phases instead of flat todos (mutually exclusive) + * + * @generated from field: bool is_project = 5; + */ + isProject: boolean; - /** - * Implementation phases (only valid when is_project=true) - * - * @generated from field: repeated agent.v1.Phase phases = 6; - */ - phases: Phase[]; + /** + * Implementation phases (only valid when is_project=true) + * + * @generated from field: repeated agent.v1.Phase phases = 6; + */ + phases: Phase[]; }; /** * Describes the message agent.v1.CreatePlanArgs. * Use `create(CreatePlanArgsSchema)` to create a new message. */ -export const CreatePlanArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 174); +export const CreatePlanArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 174); /** * @generated from message agent.v1.CreatePlanResult */ export type CreatePlanResult = Message<"agent.v1.CreatePlanResult"> & { - /** - * URI of the plan file (returned when file_based_plan_edits is enabled) - * - * @generated from field: string plan_uri = 3; - */ - planUri: string; + /** + * URI of the plan file (returned when file_based_plan_edits is enabled) + * + * @generated from field: string plan_uri = 3; + */ + planUri: string; - /** - * @generated from oneof agent.v1.CreatePlanResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.CreatePlanSuccess success = 1; - */ - value: CreatePlanSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.CreatePlanError error = 2; - */ - value: CreatePlanError; - case: "error"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.CreatePlanResult.result + */ + result: { + /** + * @generated from field: agent.v1.CreatePlanSuccess success = 1; + */ + value: CreatePlanSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.CreatePlanError error = 2; + */ + value: CreatePlanError; + case: "error"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.CreatePlanResult. * Use `create(CreatePlanResultSchema)` to create a new message. */ -export const CreatePlanResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 175); +export const CreatePlanResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 175); /** * @generated from message agent.v1.CreatePlanSuccess */ -export type CreatePlanSuccess = Message<"agent.v1.CreatePlanSuccess"> & {}; +export type CreatePlanSuccess = Message<"agent.v1.CreatePlanSuccess"> & { +}; /** * Describes the message agent.v1.CreatePlanSuccess. * Use `create(CreatePlanSuccessSchema)` to create a new message. */ -export const CreatePlanSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 176); +export const CreatePlanSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 176); /** * @generated from message agent.v1.CreatePlanError */ export type CreatePlanError = Message<"agent.v1.CreatePlanError"> & { - /** - * @generated from field: string error = 1; - */ - error: string; + /** + * @generated from field: string error = 1; + */ + error: string; }; /** * Describes the message agent.v1.CreatePlanError. * Use `create(CreatePlanErrorSchema)` to create a new message. */ -export const CreatePlanErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 177); +export const CreatePlanErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 177); /** * Query sent from server to client to create a plan file @@ -5241,24 +5260,23 @@ export const CreatePlanErrorSchema: GenMessage = /*@__PURE__*/ * @generated from message agent.v1.CreatePlanRequestQuery */ export type CreatePlanRequestQuery = Message<"agent.v1.CreatePlanRequestQuery"> & { - /** - * @generated from field: agent.v1.CreatePlanArgs args = 1; - */ - args?: CreatePlanArgs; + /** + * @generated from field: agent.v1.CreatePlanArgs args = 1; + */ + args?: CreatePlanArgs | undefined; - /** - * @generated from field: string tool_call_id = 2; - */ - toolCallId: string; + /** + * @generated from field: string tool_call_id = 2; + */ + toolCallId: string; }; /** * Describes the message agent.v1.CreatePlanRequestQuery. * Use `create(CreatePlanRequestQuerySchema)` to create a new message. */ -export const CreatePlanRequestQuerySchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 178); +export const CreatePlanRequestQuerySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 178); /** * Response from client with the created plan URI @@ -5266,1527 +5284,1517 @@ export const CreatePlanRequestQuerySchema: GenMessage = * @generated from message agent.v1.CreatePlanRequestResponse */ export type CreatePlanRequestResponse = Message<"agent.v1.CreatePlanRequestResponse"> & { - /** - * @generated from field: agent.v1.CreatePlanResult result = 1; - */ - result?: CreatePlanResult; + /** + * @generated from field: agent.v1.CreatePlanResult result = 1; + */ + result?: CreatePlanResult | undefined; }; /** * Describes the message agent.v1.CreatePlanRequestResponse. * Use `create(CreatePlanRequestResponseSchema)` to create a new message. */ -export const CreatePlanRequestResponseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 179); +export const CreatePlanRequestResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 179); /** * @generated from message agent.v1.CursorRuleTypeGlobal */ -export type CursorRuleTypeGlobal = Message<"agent.v1.CursorRuleTypeGlobal"> & {}; +export type CursorRuleTypeGlobal = Message<"agent.v1.CursorRuleTypeGlobal"> & { +}; /** * Describes the message agent.v1.CursorRuleTypeGlobal. * Use `create(CursorRuleTypeGlobalSchema)` to create a new message. */ -export const CursorRuleTypeGlobalSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 180); +export const CursorRuleTypeGlobalSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 180); /** * @generated from message agent.v1.CursorRuleTypeFileGlobs */ export type CursorRuleTypeFileGlobs = Message<"agent.v1.CursorRuleTypeFileGlobs"> & { - /** - * @generated from field: repeated string globs = 1; - */ - globs: string[]; + /** + * @generated from field: repeated string globs = 1; + */ + globs: string[]; }; /** * Describes the message agent.v1.CursorRuleTypeFileGlobs. * Use `create(CursorRuleTypeFileGlobsSchema)` to create a new message. */ -export const CursorRuleTypeFileGlobsSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 181); +export const CursorRuleTypeFileGlobsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 181); /** * @generated from message agent.v1.CursorRuleTypeAgentFetched */ export type CursorRuleTypeAgentFetched = Message<"agent.v1.CursorRuleTypeAgentFetched"> & { - /** - * @generated from field: string description = 1; - */ - description: string; + /** + * @generated from field: string description = 1; + */ + description: string; }; /** * Describes the message agent.v1.CursorRuleTypeAgentFetched. * Use `create(CursorRuleTypeAgentFetchedSchema)` to create a new message. */ -export const CursorRuleTypeAgentFetchedSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 182); +export const CursorRuleTypeAgentFetchedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 182); /** * @generated from message agent.v1.CursorRuleTypeManuallyAttached */ -export type CursorRuleTypeManuallyAttached = Message<"agent.v1.CursorRuleTypeManuallyAttached"> & {}; +export type CursorRuleTypeManuallyAttached = Message<"agent.v1.CursorRuleTypeManuallyAttached"> & { +}; /** * Describes the message agent.v1.CursorRuleTypeManuallyAttached. * Use `create(CursorRuleTypeManuallyAttachedSchema)` to create a new message. */ -export const CursorRuleTypeManuallyAttachedSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 183); +export const CursorRuleTypeManuallyAttachedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 183); /** * @generated from message agent.v1.CursorRuleType */ export type CursorRuleType = Message<"agent.v1.CursorRuleType"> & { - /** - * @generated from oneof agent.v1.CursorRuleType.type - */ - type: - | { - /** - * @generated from field: agent.v1.CursorRuleTypeGlobal global = 1; - */ - value: CursorRuleTypeGlobal; - case: "global"; - } - | { - /** - * @generated from field: agent.v1.CursorRuleTypeFileGlobs file_globbed = 2; - */ - value: CursorRuleTypeFileGlobs; - case: "fileGlobbed"; - } - | { - /** - * @generated from field: agent.v1.CursorRuleTypeAgentFetched agent_fetched = 3; - */ - value: CursorRuleTypeAgentFetched; - case: "agentFetched"; - } - | { - /** - * @generated from field: agent.v1.CursorRuleTypeManuallyAttached manually_attached = 4; - */ - value: CursorRuleTypeManuallyAttached; - case: "manuallyAttached"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.CursorRuleType.type + */ + type: { + /** + * @generated from field: agent.v1.CursorRuleTypeGlobal global = 1; + */ + value: CursorRuleTypeGlobal; + case: "global"; + } | { + /** + * @generated from field: agent.v1.CursorRuleTypeFileGlobs file_globbed = 2; + */ + value: CursorRuleTypeFileGlobs; + case: "fileGlobbed"; + } | { + /** + * @generated from field: agent.v1.CursorRuleTypeAgentFetched agent_fetched = 3; + */ + value: CursorRuleTypeAgentFetched; + case: "agentFetched"; + } | { + /** + * @generated from field: agent.v1.CursorRuleTypeManuallyAttached manually_attached = 4; + */ + value: CursorRuleTypeManuallyAttached; + case: "manuallyAttached"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.CursorRuleType. * Use `create(CursorRuleTypeSchema)` to create a new message. */ -export const CursorRuleTypeSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 184); +export const CursorRuleTypeSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 184); /** * @generated from message agent.v1.CursorRule */ export type CursorRule = Message<"agent.v1.CursorRule"> & { - /** - * absolute path to the .mdc file - * - * @generated from field: string full_path = 1; - */ - fullPath: string; + /** + * absolute path to the .mdc file + * + * @generated from field: string full_path = 1; + */ + fullPath: string; - /** - * rule body, trimmed to reasonable size if needed by client - * - * @generated from field: string content = 2; - */ - content: string; + /** + * rule body, trimmed to reasonable size if needed by client + * + * @generated from field: string content = 2; + */ + content: string; - /** - * classification of rule - * - * @generated from field: agent.v1.CursorRuleType type = 3; - */ - type?: CursorRuleType; + /** + * classification of rule + * + * @generated from field: agent.v1.CursorRuleType type = 3; + */ + type?: CursorRuleType | undefined; - /** - * source of the rule - * - * @generated from field: int32 source = 4; - */ - source: number; + /** + * source of the rule + * + * @generated from field: int32 source = 4; + */ + source: number; - /** - * Git remote origin URL for the repository containing this rule, if available. Normalized to host/path format (e.g., "github.com/owner/repo"). - * - * @generated from field: optional string git_remote_origin = 5; - */ - gitRemoteOrigin?: string; + /** + * Git remote origin URL for the repository containing this rule, if available. Normalized to host/path format (e.g., "github.com/owner/repo"). + * + * @generated from field: optional string git_remote_origin = 5; + */ + gitRemoteOrigin?: string | undefined; - /** - * @generated from field: optional string parse_error = 6; - */ - parseError?: string; + /** + * @generated from field: optional string parse_error = 6; + */ + parseError?: string | undefined; }; /** * Describes the message agent.v1.CursorRule. * Use `create(CursorRuleSchema)` to create a new message. */ -export const CursorRuleSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 185); +export const CursorRuleSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 185); /** * @generated from message agent.v1.DeleteArgs */ export type DeleteArgs = Message<"agent.v1.DeleteArgs"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: string tool_call_id = 2; - */ - toolCallId: string; + /** + * @generated from field: string tool_call_id = 2; + */ + toolCallId: string; }; /** * Describes the message agent.v1.DeleteArgs. * Use `create(DeleteArgsSchema)` to create a new message. */ -export const DeleteArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 186); +export const DeleteArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 186); /** * @generated from message agent.v1.DeleteResult */ export type DeleteResult = Message<"agent.v1.DeleteResult"> & { - /** - * @generated from oneof agent.v1.DeleteResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.DeleteSuccess success = 1; - */ - value: DeleteSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.DeleteFileNotFound file_not_found = 2; - */ - value: DeleteFileNotFound; - case: "fileNotFound"; - } - | { - /** - * @generated from field: agent.v1.DeleteNotFile not_file = 3; - */ - value: DeleteNotFile; - case: "notFile"; - } - | { - /** - * @generated from field: agent.v1.DeletePermissionDenied permission_denied = 4; - */ - value: DeletePermissionDenied; - case: "permissionDenied"; - } - | { - /** - * @generated from field: agent.v1.DeleteFileBusy file_busy = 5; - */ - value: DeleteFileBusy; - case: "fileBusy"; - } - | { - /** - * @generated from field: agent.v1.DeleteRejected rejected = 6; - */ - value: DeleteRejected; - case: "rejected"; - } - | { - /** - * @generated from field: agent.v1.DeleteError error = 7; - */ - value: DeleteError; - case: "error"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.DeleteResult.result + */ + result: { + /** + * @generated from field: agent.v1.DeleteSuccess success = 1; + */ + value: DeleteSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.DeleteFileNotFound file_not_found = 2; + */ + value: DeleteFileNotFound; + case: "fileNotFound"; + } | { + /** + * @generated from field: agent.v1.DeleteNotFile not_file = 3; + */ + value: DeleteNotFile; + case: "notFile"; + } | { + /** + * @generated from field: agent.v1.DeletePermissionDenied permission_denied = 4; + */ + value: DeletePermissionDenied; + case: "permissionDenied"; + } | { + /** + * @generated from field: agent.v1.DeleteFileBusy file_busy = 5; + */ + value: DeleteFileBusy; + case: "fileBusy"; + } | { + /** + * @generated from field: agent.v1.DeleteRejected rejected = 6; + */ + value: DeleteRejected; + case: "rejected"; + } | { + /** + * @generated from field: agent.v1.DeleteError error = 7; + */ + value: DeleteError; + case: "error"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.DeleteResult. * Use `create(DeleteResultSchema)` to create a new message. */ -export const DeleteResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 187); +export const DeleteResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 187); /** * @generated from message agent.v1.DeleteSuccess */ export type DeleteSuccess = Message<"agent.v1.DeleteSuccess"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: string deleted_file = 2; - */ - deletedFile: string; + /** + * @generated from field: string deleted_file = 2; + */ + deletedFile: string; - /** - * @generated from field: int64 file_size = 3; - */ - fileSize: bigint; + /** + * @generated from field: int64 file_size = 3; + */ + fileSize: bigint; - /** - * @generated from field: string prev_content = 4; - */ - prevContent: string; + /** + * @generated from field: string prev_content = 4; + */ + prevContent: string; }; /** * Describes the message agent.v1.DeleteSuccess. * Use `create(DeleteSuccessSchema)` to create a new message. */ -export const DeleteSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 188); +export const DeleteSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 188); /** * @generated from message agent.v1.DeleteFileNotFound */ export type DeleteFileNotFound = Message<"agent.v1.DeleteFileNotFound"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; }; /** * Describes the message agent.v1.DeleteFileNotFound. * Use `create(DeleteFileNotFoundSchema)` to create a new message. */ -export const DeleteFileNotFoundSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 189); +export const DeleteFileNotFoundSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 189); /** * @generated from message agent.v1.DeleteNotFile */ export type DeleteNotFile = Message<"agent.v1.DeleteNotFile"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * "directory" or "other" - * - * @generated from field: string actual_type = 2; - */ - actualType: string; + /** + * "directory" or "other" + * + * @generated from field: string actual_type = 2; + */ + actualType: string; }; /** * Describes the message agent.v1.DeleteNotFile. * Use `create(DeleteNotFileSchema)` to create a new message. */ -export const DeleteNotFileSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 190); +export const DeleteNotFileSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 190); /** * @generated from message agent.v1.DeletePermissionDenied */ export type DeletePermissionDenied = Message<"agent.v1.DeletePermissionDenied"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: string client_visible_error = 2; - */ - clientVisibleError: string; + /** + * @generated from field: string client_visible_error = 2; + */ + clientVisibleError: string; - /** - * @generated from field: bool is_readonly = 3; - */ - isReadonly: boolean; + /** + * @generated from field: bool is_readonly = 3; + */ + isReadonly: boolean; }; /** * Describes the message agent.v1.DeletePermissionDenied. * Use `create(DeletePermissionDeniedSchema)` to create a new message. */ -export const DeletePermissionDeniedSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 191); +export const DeletePermissionDeniedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 191); /** * @generated from message agent.v1.DeleteFileBusy */ export type DeleteFileBusy = Message<"agent.v1.DeleteFileBusy"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; }; /** * Describes the message agent.v1.DeleteFileBusy. * Use `create(DeleteFileBusySchema)` to create a new message. */ -export const DeleteFileBusySchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 192); +export const DeleteFileBusySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 192); /** * @generated from message agent.v1.DeleteRejected */ export type DeleteRejected = Message<"agent.v1.DeleteRejected"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: string reason = 2; - */ - reason: string; + /** + * @generated from field: string reason = 2; + */ + reason: string; }; /** * Describes the message agent.v1.DeleteRejected. * Use `create(DeleteRejectedSchema)` to create a new message. */ -export const DeleteRejectedSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 193); +export const DeleteRejectedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 193); /** * @generated from message agent.v1.DeleteError */ export type DeleteError = Message<"agent.v1.DeleteError"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: string error = 2; - */ - error: string; + /** + * @generated from field: string error = 2; + */ + error: string; }; /** * Describes the message agent.v1.DeleteError. * Use `create(DeleteErrorSchema)` to create a new message. */ -export const DeleteErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 194); +export const DeleteErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 194); /** * @generated from message agent.v1.DeleteToolCall */ export type DeleteToolCall = Message<"agent.v1.DeleteToolCall"> & { - /** - * @generated from field: agent.v1.DeleteArgs args = 1; - */ - args?: DeleteArgs; + /** + * @generated from field: agent.v1.DeleteArgs args = 1; + */ + args?: DeleteArgs | undefined; - /** - * @generated from field: agent.v1.DeleteResult result = 2; - */ - result?: DeleteResult; + /** + * @generated from field: agent.v1.DeleteResult result = 2; + */ + result?: DeleteResult | undefined; }; /** * Describes the message agent.v1.DeleteToolCall. * Use `create(DeleteToolCallSchema)` to create a new message. */ -export const DeleteToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 195); +export const DeleteToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 195); /** * @generated from message agent.v1.DiagnosticsArgs */ export type DiagnosticsArgs = Message<"agent.v1.DiagnosticsArgs"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: string tool_call_id = 2; - */ - toolCallId: string; + /** + * @generated from field: string tool_call_id = 2; + */ + toolCallId: string; }; /** * Describes the message agent.v1.DiagnosticsArgs. * Use `create(DiagnosticsArgsSchema)` to create a new message. */ -export const DiagnosticsArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 196); +export const DiagnosticsArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 196); /** * @generated from message agent.v1.DiagnosticsResult */ export type DiagnosticsResult = Message<"agent.v1.DiagnosticsResult"> & { - /** - * @generated from oneof agent.v1.DiagnosticsResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.DiagnosticsSuccess success = 1; - */ - value: DiagnosticsSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.DiagnosticsError error = 2; - */ - value: DiagnosticsError; - case: "error"; - } - | { - /** - * @generated from field: agent.v1.DiagnosticsRejected rejected = 3; - */ - value: DiagnosticsRejected; - case: "rejected"; - } - | { - /** - * @generated from field: agent.v1.DiagnosticsFileNotFound file_not_found = 4; - */ - value: DiagnosticsFileNotFound; - case: "fileNotFound"; - } - | { - /** - * @generated from field: agent.v1.DiagnosticsPermissionDenied permission_denied = 5; - */ - value: DiagnosticsPermissionDenied; - case: "permissionDenied"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.DiagnosticsResult.result + */ + result: { + /** + * @generated from field: agent.v1.DiagnosticsSuccess success = 1; + */ + value: DiagnosticsSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.DiagnosticsError error = 2; + */ + value: DiagnosticsError; + case: "error"; + } | { + /** + * @generated from field: agent.v1.DiagnosticsRejected rejected = 3; + */ + value: DiagnosticsRejected; + case: "rejected"; + } | { + /** + * @generated from field: agent.v1.DiagnosticsFileNotFound file_not_found = 4; + */ + value: DiagnosticsFileNotFound; + case: "fileNotFound"; + } | { + /** + * @generated from field: agent.v1.DiagnosticsPermissionDenied permission_denied = 5; + */ + value: DiagnosticsPermissionDenied; + case: "permissionDenied"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.DiagnosticsResult. * Use `create(DiagnosticsResultSchema)` to create a new message. */ -export const DiagnosticsResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 197); +export const DiagnosticsResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 197); /** * @generated from message agent.v1.DiagnosticsSuccess */ export type DiagnosticsSuccess = Message<"agent.v1.DiagnosticsSuccess"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: repeated agent.v1.Diagnostic diagnostics = 2; - */ - diagnostics: Diagnostic[]; + /** + * @generated from field: repeated agent.v1.Diagnostic diagnostics = 2; + */ + diagnostics: Diagnostic[]; - /** - * @generated from field: int32 total_diagnostics = 3; - */ - totalDiagnostics: number; + /** + * @generated from field: int32 total_diagnostics = 3; + */ + totalDiagnostics: number; }; /** * Describes the message agent.v1.DiagnosticsSuccess. * Use `create(DiagnosticsSuccessSchema)` to create a new message. */ -export const DiagnosticsSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 198); +export const DiagnosticsSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 198); /** * @generated from message agent.v1.Diagnostic */ export type Diagnostic = Message<"agent.v1.Diagnostic"> & { - /** - * @generated from field: int32 severity = 1; - */ - severity: number; + /** + * @generated from field: int32 severity = 1; + */ + severity: number; - /** - * @generated from field: agent.v1.Range range = 2; - */ - range?: Range; + /** + * @generated from field: agent.v1.Range range = 2; + */ + range?: Range | undefined; - /** - * @generated from field: string message = 3; - */ - message: string; + /** + * @generated from field: string message = 3; + */ + message: string; - /** - * @generated from field: string source = 4; - */ - source: string; + /** + * @generated from field: string source = 4; + */ + source: string; - /** - * @generated from field: string code = 5; - */ - code: string; + /** + * @generated from field: string code = 5; + */ + code: string; - /** - * @generated from field: bool is_stale = 6; - */ - isStale: boolean; + /** + * @generated from field: bool is_stale = 6; + */ + isStale: boolean; }; /** * Describes the message agent.v1.Diagnostic. * Use `create(DiagnosticSchema)` to create a new message. */ -export const DiagnosticSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 199); +export const DiagnosticSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 199); /** * @generated from message agent.v1.DiagnosticsError */ export type DiagnosticsError = Message<"agent.v1.DiagnosticsError"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: string error = 2; - */ - error: string; + /** + * @generated from field: string error = 2; + */ + error: string; }; /** * Describes the message agent.v1.DiagnosticsError. * Use `create(DiagnosticsErrorSchema)` to create a new message. */ -export const DiagnosticsErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 200); +export const DiagnosticsErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 200); /** * @generated from message agent.v1.DiagnosticsRejected */ export type DiagnosticsRejected = Message<"agent.v1.DiagnosticsRejected"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: string reason = 2; - */ - reason: string; + /** + * @generated from field: string reason = 2; + */ + reason: string; }; /** * Describes the message agent.v1.DiagnosticsRejected. * Use `create(DiagnosticsRejectedSchema)` to create a new message. */ -export const DiagnosticsRejectedSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 201); +export const DiagnosticsRejectedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 201); /** * @generated from message agent.v1.DiagnosticsFileNotFound */ export type DiagnosticsFileNotFound = Message<"agent.v1.DiagnosticsFileNotFound"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; }; /** * Describes the message agent.v1.DiagnosticsFileNotFound. * Use `create(DiagnosticsFileNotFoundSchema)` to create a new message. */ -export const DiagnosticsFileNotFoundSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 202); +export const DiagnosticsFileNotFoundSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 202); /** * @generated from message agent.v1.DiagnosticsPermissionDenied */ export type DiagnosticsPermissionDenied = Message<"agent.v1.DiagnosticsPermissionDenied"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; }; /** * Describes the message agent.v1.DiagnosticsPermissionDenied. * Use `create(DiagnosticsPermissionDeniedSchema)` to create a new message. */ -export const DiagnosticsPermissionDeniedSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 203); +export const DiagnosticsPermissionDeniedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 203); /** * @generated from message agent.v1.EditArgs */ export type EditArgs = Message<"agent.v1.EditArgs"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: optional string stream_content = 6; - */ - streamContent?: string; + /** + * @generated from field: optional string stream_content = 6; + */ + streamContent?: string | undefined; }; /** * Describes the message agent.v1.EditArgs. * Use `create(EditArgsSchema)` to create a new message. */ -export const EditArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 204); +export const EditArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 204); /** * @generated from message agent.v1.EditResult */ export type EditResult = Message<"agent.v1.EditResult"> & { - /** - * @generated from oneof agent.v1.EditResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.EditSuccess success = 1; - */ - value: EditSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.EditFileNotFound file_not_found = 2; - */ - value: EditFileNotFound; - case: "fileNotFound"; - } - | { - /** - * @generated from field: agent.v1.EditReadPermissionDenied read_permission_denied = 3; - */ - value: EditReadPermissionDenied; - case: "readPermissionDenied"; - } - | { - /** - * @generated from field: agent.v1.EditWritePermissionDenied write_permission_denied = 4; - */ - value: EditWritePermissionDenied; - case: "writePermissionDenied"; - } - | { - /** - * @generated from field: agent.v1.EditRejected rejected = 6; - */ - value: EditRejected; - case: "rejected"; - } - | { - /** - * @generated from field: agent.v1.EditError error = 7; - */ - value: EditError; - case: "error"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.EditResult.result + */ + result: { + /** + * @generated from field: agent.v1.EditSuccess success = 1; + */ + value: EditSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.EditFileNotFound file_not_found = 2; + */ + value: EditFileNotFound; + case: "fileNotFound"; + } | { + /** + * @generated from field: agent.v1.EditReadPermissionDenied read_permission_denied = 3; + */ + value: EditReadPermissionDenied; + case: "readPermissionDenied"; + } | { + /** + * @generated from field: agent.v1.EditWritePermissionDenied write_permission_denied = 4; + */ + value: EditWritePermissionDenied; + case: "writePermissionDenied"; + } | { + /** + * @generated from field: agent.v1.EditRejected rejected = 6; + */ + value: EditRejected; + case: "rejected"; + } | { + /** + * @generated from field: agent.v1.EditError error = 7; + */ + value: EditError; + case: "error"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.EditResult. * Use `create(EditResultSchema)` to create a new message. */ -export const EditResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 205); +export const EditResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 205); /** * @generated from message agent.v1.EditSuccess */ export type EditSuccess = Message<"agent.v1.EditSuccess"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: optional int32 lines_added = 3; - */ - linesAdded?: number; + /** + * @generated from field: optional int32 lines_added = 3; + */ + linesAdded?: number | undefined; - /** - * @generated from field: optional int32 lines_removed = 4; - */ - linesRemoved?: number; + /** + * @generated from field: optional int32 lines_removed = 4; + */ + linesRemoved?: number | undefined; - /** - * Concatenated chunk diff strings separated by "\n...\n" - * - * @generated from field: optional string diff_string = 5; - */ - diffString?: string; + /** + * Concatenated chunk diff strings separated by "\n...\n" + * + * @generated from field: optional string diff_string = 5; + */ + diffString?: string | undefined; - /** - * undefined if file didn't exist before the edit - * - * @generated from field: optional string before_full_file_content = 6; - */ - beforeFullFileContent?: string; + /** + * undefined if file didn't exist before the edit + * + * @generated from field: optional string before_full_file_content = 6; + */ + beforeFullFileContent?: string | undefined; - /** - * @generated from field: string after_full_file_content = 7; - */ - afterFullFileContent: string; + /** + * @generated from field: string after_full_file_content = 7; + */ + afterFullFileContent: string; - /** - * Formatted message for display to model (resultForModel from EditTransformResult) - * - * @generated from field: optional string message = 8; - */ - message?: string; + /** + * Formatted message for display to model (resultForModel from EditTransformResult) + * + * @generated from field: optional string message = 8; + */ + message?: string | undefined; }; /** * Describes the message agent.v1.EditSuccess. * Use `create(EditSuccessSchema)` to create a new message. */ -export const EditSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 206); +export const EditSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 206); /** * @generated from message agent.v1.EditFileNotFound */ export type EditFileNotFound = Message<"agent.v1.EditFileNotFound"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; }; /** * Describes the message agent.v1.EditFileNotFound. * Use `create(EditFileNotFoundSchema)` to create a new message. */ -export const EditFileNotFoundSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 207); +export const EditFileNotFoundSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 207); /** * @generated from message agent.v1.EditReadPermissionDenied */ export type EditReadPermissionDenied = Message<"agent.v1.EditReadPermissionDenied"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; }; /** * Describes the message agent.v1.EditReadPermissionDenied. * Use `create(EditReadPermissionDeniedSchema)` to create a new message. */ -export const EditReadPermissionDeniedSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 208); +export const EditReadPermissionDeniedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 208); /** * @generated from message agent.v1.EditWritePermissionDenied */ export type EditWritePermissionDenied = Message<"agent.v1.EditWritePermissionDenied"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: string error = 2; - */ - error: string; + /** + * @generated from field: string error = 2; + */ + error: string; - /** - * @generated from field: bool is_readonly = 3; - */ - isReadonly: boolean; + /** + * @generated from field: bool is_readonly = 3; + */ + isReadonly: boolean; }; /** * Describes the message agent.v1.EditWritePermissionDenied. * Use `create(EditWritePermissionDeniedSchema)` to create a new message. */ -export const EditWritePermissionDeniedSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 209); +export const EditWritePermissionDeniedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 209); /** * @generated from message agent.v1.EditRejected */ export type EditRejected = Message<"agent.v1.EditRejected"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: string reason = 2; - */ - reason: string; + /** + * @generated from field: string reason = 2; + */ + reason: string; }; /** * Describes the message agent.v1.EditRejected. * Use `create(EditRejectedSchema)` to create a new message. */ -export const EditRejectedSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 210); +export const EditRejectedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 210); /** * @generated from message agent.v1.EditError */ export type EditError = Message<"agent.v1.EditError"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: string error = 2; - */ - error: string; + /** + * @generated from field: string error = 2; + */ + error: string; - /** - * @generated from field: optional string model_visible_error = 5; - */ - modelVisibleError?: string; + /** + * @generated from field: optional string model_visible_error = 5; + */ + modelVisibleError?: string | undefined; }; /** * Describes the message agent.v1.EditError. * Use `create(EditErrorSchema)` to create a new message. */ -export const EditErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 211); +export const EditErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 211); /** * @generated from message agent.v1.EditToolCall */ export type EditToolCall = Message<"agent.v1.EditToolCall"> & { - /** - * @generated from field: agent.v1.EditArgs args = 1; - */ - args?: EditArgs; + /** + * @generated from field: agent.v1.EditArgs args = 1; + */ + args?: EditArgs | undefined; - /** - * @generated from field: agent.v1.EditResult result = 2; - */ - result?: EditResult; + /** + * @generated from field: agent.v1.EditResult result = 2; + */ + result?: EditResult | undefined; }; /** * Describes the message agent.v1.EditToolCall. * Use `create(EditToolCallSchema)` to create a new message. */ -export const EditToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 212); +export const EditToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 212); /** * @generated from message agent.v1.EditToolCallDelta */ export type EditToolCallDelta = Message<"agent.v1.EditToolCallDelta"> & { - /** - * @generated from field: string stream_content_delta = 1; - */ - streamContentDelta: string; + /** + * @generated from field: string stream_content_delta = 1; + */ + streamContentDelta: string; }; /** * Describes the message agent.v1.EditToolCallDelta. * Use `create(EditToolCallDeltaSchema)` to create a new message. */ -export const EditToolCallDeltaSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 213); +export const EditToolCallDeltaSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 213); /** * @generated from message agent.v1.ExaFetchArgs */ export type ExaFetchArgs = Message<"agent.v1.ExaFetchArgs"> & { - /** - * @generated from field: repeated string ids = 1; - */ - ids: string[]; + /** + * @generated from field: repeated string ids = 1; + */ + ids: string[]; - /** - * @generated from field: string tool_call_id = 2; - */ - toolCallId: string; + /** + * @generated from field: string tool_call_id = 2; + */ + toolCallId: string; }; /** * Describes the message agent.v1.ExaFetchArgs. * Use `create(ExaFetchArgsSchema)` to create a new message. */ -export const ExaFetchArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 214); +export const ExaFetchArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 214); /** * @generated from message agent.v1.ExaFetchResult */ export type ExaFetchResult = Message<"agent.v1.ExaFetchResult"> & { - /** - * @generated from oneof agent.v1.ExaFetchResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.ExaFetchSuccess success = 1; - */ - value: ExaFetchSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.ExaFetchError error = 2; - */ - value: ExaFetchError; - case: "error"; - } - | { - /** - * @generated from field: agent.v1.ExaFetchRejected rejected = 3; - */ - value: ExaFetchRejected; - case: "rejected"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ExaFetchResult.result + */ + result: { + /** + * @generated from field: agent.v1.ExaFetchSuccess success = 1; + */ + value: ExaFetchSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.ExaFetchError error = 2; + */ + value: ExaFetchError; + case: "error"; + } | { + /** + * @generated from field: agent.v1.ExaFetchRejected rejected = 3; + */ + value: ExaFetchRejected; + case: "rejected"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ExaFetchResult. * Use `create(ExaFetchResultSchema)` to create a new message. */ -export const ExaFetchResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 215); +export const ExaFetchResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 215); /** * @generated from message agent.v1.ExaFetchSuccess */ export type ExaFetchSuccess = Message<"agent.v1.ExaFetchSuccess"> & { - /** - * @generated from field: repeated agent.v1.ExaFetchContent contents = 1; - */ - contents: ExaFetchContent[]; + /** + * @generated from field: repeated agent.v1.ExaFetchContent contents = 1; + */ + contents: ExaFetchContent[]; }; /** * Describes the message agent.v1.ExaFetchSuccess. * Use `create(ExaFetchSuccessSchema)` to create a new message. */ -export const ExaFetchSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 216); +export const ExaFetchSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 216); /** * @generated from message agent.v1.ExaFetchError */ export type ExaFetchError = Message<"agent.v1.ExaFetchError"> & { - /** - * @generated from field: string error = 1; - */ - error: string; + /** + * @generated from field: string error = 1; + */ + error: string; }; /** * Describes the message agent.v1.ExaFetchError. * Use `create(ExaFetchErrorSchema)` to create a new message. */ -export const ExaFetchErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 217); +export const ExaFetchErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 217); /** * @generated from message agent.v1.ExaFetchRejected */ export type ExaFetchRejected = Message<"agent.v1.ExaFetchRejected"> & { - /** - * @generated from field: string reason = 1; - */ - reason: string; + /** + * @generated from field: string reason = 1; + */ + reason: string; }; /** * Describes the message agent.v1.ExaFetchRejected. * Use `create(ExaFetchRejectedSchema)` to create a new message. */ -export const ExaFetchRejectedSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 218); +export const ExaFetchRejectedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 218); /** * @generated from message agent.v1.ExaFetchContent */ export type ExaFetchContent = Message<"agent.v1.ExaFetchContent"> & { - /** - * @generated from field: string title = 1; - */ - title: string; + /** + * @generated from field: string title = 1; + */ + title: string; - /** - * @generated from field: string url = 2; - */ - url: string; + /** + * @generated from field: string url = 2; + */ + url: string; - /** - * @generated from field: string text = 3; - */ - text: string; + /** + * @generated from field: string text = 3; + */ + text: string; - /** - * @generated from field: string published_date = 4; - */ - publishedDate: string; + /** + * @generated from field: string published_date = 4; + */ + publishedDate: string; }; /** * Describes the message agent.v1.ExaFetchContent. * Use `create(ExaFetchContentSchema)` to create a new message. */ -export const ExaFetchContentSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 219); +export const ExaFetchContentSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 219); /** * @generated from message agent.v1.ExaFetchToolCall */ export type ExaFetchToolCall = Message<"agent.v1.ExaFetchToolCall"> & { - /** - * @generated from field: agent.v1.ExaFetchArgs args = 1; - */ - args?: ExaFetchArgs; + /** + * @generated from field: agent.v1.ExaFetchArgs args = 1; + */ + args?: ExaFetchArgs | undefined; - /** - * @generated from field: agent.v1.ExaFetchResult result = 2; - */ - result?: ExaFetchResult; + /** + * @generated from field: agent.v1.ExaFetchResult result = 2; + */ + result?: ExaFetchResult | undefined; }; /** * Describes the message agent.v1.ExaFetchToolCall. * Use `create(ExaFetchToolCallSchema)` to create a new message. */ -export const ExaFetchToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 220); +export const ExaFetchToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 220); /** * @generated from message agent.v1.ExaFetchRequestQuery */ export type ExaFetchRequestQuery = Message<"agent.v1.ExaFetchRequestQuery"> & { - /** - * @generated from field: agent.v1.ExaFetchArgs args = 1; - */ - args?: ExaFetchArgs; + /** + * @generated from field: agent.v1.ExaFetchArgs args = 1; + */ + args?: ExaFetchArgs | undefined; }; /** * Describes the message agent.v1.ExaFetchRequestQuery. * Use `create(ExaFetchRequestQuerySchema)` to create a new message. */ -export const ExaFetchRequestQuerySchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 221); +export const ExaFetchRequestQuerySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 221); /** * @generated from message agent.v1.ExaFetchRequestResponse */ export type ExaFetchRequestResponse = Message<"agent.v1.ExaFetchRequestResponse"> & { - /** - * @generated from oneof agent.v1.ExaFetchRequestResponse.result - */ - result: - | { - /** - * @generated from field: agent.v1.ExaFetchRequestResponse_Approved approved = 1; - */ - value: ExaFetchRequestResponse_Approved; - case: "approved"; - } - | { - /** - * @generated from field: agent.v1.ExaFetchRequestResponse_Rejected rejected = 2; - */ - value: ExaFetchRequestResponse_Rejected; - case: "rejected"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ExaFetchRequestResponse.result + */ + result: { + /** + * @generated from field: agent.v1.ExaFetchRequestResponse_Approved approved = 1; + */ + value: ExaFetchRequestResponse_Approved; + case: "approved"; + } | { + /** + * @generated from field: agent.v1.ExaFetchRequestResponse_Rejected rejected = 2; + */ + value: ExaFetchRequestResponse_Rejected; + case: "rejected"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ExaFetchRequestResponse. * Use `create(ExaFetchRequestResponseSchema)` to create a new message. */ -export const ExaFetchRequestResponseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 222); +export const ExaFetchRequestResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 222); /** * @generated from message agent.v1.ExaFetchRequestResponse_Approved */ -export type ExaFetchRequestResponse_Approved = Message<"agent.v1.ExaFetchRequestResponse_Approved"> & {}; +export type ExaFetchRequestResponse_Approved = Message<"agent.v1.ExaFetchRequestResponse_Approved"> & { +}; /** * Describes the message agent.v1.ExaFetchRequestResponse_Approved. * Use `create(ExaFetchRequestResponse_ApprovedSchema)` to create a new message. */ -export const ExaFetchRequestResponse_ApprovedSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 223); +export const ExaFetchRequestResponse_ApprovedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 223); /** * @generated from message agent.v1.ExaFetchRequestResponse_Rejected */ export type ExaFetchRequestResponse_Rejected = Message<"agent.v1.ExaFetchRequestResponse_Rejected"> & { - /** - * @generated from field: string reason = 1; - */ - reason: string; + /** + * @generated from field: string reason = 1; + */ + reason: string; }; /** * Describes the message agent.v1.ExaFetchRequestResponse_Rejected. * Use `create(ExaFetchRequestResponse_RejectedSchema)` to create a new message. */ -export const ExaFetchRequestResponse_RejectedSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 224); +export const ExaFetchRequestResponse_RejectedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 224); /** * @generated from message agent.v1.ExaSearchArgs */ export type ExaSearchArgs = Message<"agent.v1.ExaSearchArgs"> & { - /** - * @generated from field: string query = 1; - */ - query: string; + /** + * @generated from field: string query = 1; + */ + query: string; - /** - * "auto", "neural", or "keyword" - * - * @generated from field: string type = 2; - */ - type: string; + /** + * "auto", "neural", or "keyword" + * + * @generated from field: string type = 2; + */ + type: string; - /** - * @generated from field: int32 num_results = 3; - */ - numResults: number; + /** + * @generated from field: int32 num_results = 3; + */ + numResults: number; - /** - * @generated from field: string tool_call_id = 4; - */ - toolCallId: string; + /** + * @generated from field: string tool_call_id = 4; + */ + toolCallId: string; }; /** * Describes the message agent.v1.ExaSearchArgs. * Use `create(ExaSearchArgsSchema)` to create a new message. */ -export const ExaSearchArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 225); +export const ExaSearchArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 225); /** * @generated from message agent.v1.ExaSearchResult */ export type ExaSearchResult = Message<"agent.v1.ExaSearchResult"> & { - /** - * @generated from oneof agent.v1.ExaSearchResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.ExaSearchSuccess success = 1; - */ - value: ExaSearchSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.ExaSearchError error = 2; - */ - value: ExaSearchError; - case: "error"; - } - | { - /** - * @generated from field: agent.v1.ExaSearchRejected rejected = 3; - */ - value: ExaSearchRejected; - case: "rejected"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ExaSearchResult.result + */ + result: { + /** + * @generated from field: agent.v1.ExaSearchSuccess success = 1; + */ + value: ExaSearchSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.ExaSearchError error = 2; + */ + value: ExaSearchError; + case: "error"; + } | { + /** + * @generated from field: agent.v1.ExaSearchRejected rejected = 3; + */ + value: ExaSearchRejected; + case: "rejected"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ExaSearchResult. * Use `create(ExaSearchResultSchema)` to create a new message. */ -export const ExaSearchResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 226); +export const ExaSearchResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 226); /** * @generated from message agent.v1.ExaSearchSuccess */ export type ExaSearchSuccess = Message<"agent.v1.ExaSearchSuccess"> & { - /** - * @generated from field: repeated agent.v1.ExaSearchReference references = 1; - */ - references: ExaSearchReference[]; + /** + * @generated from field: repeated agent.v1.ExaSearchReference references = 1; + */ + references: ExaSearchReference[]; }; /** * Describes the message agent.v1.ExaSearchSuccess. * Use `create(ExaSearchSuccessSchema)` to create a new message. */ -export const ExaSearchSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 227); +export const ExaSearchSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 227); /** * @generated from message agent.v1.ExaSearchError */ export type ExaSearchError = Message<"agent.v1.ExaSearchError"> & { - /** - * @generated from field: string error = 1; - */ - error: string; + /** + * @generated from field: string error = 1; + */ + error: string; }; /** * Describes the message agent.v1.ExaSearchError. * Use `create(ExaSearchErrorSchema)` to create a new message. */ -export const ExaSearchErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 228); +export const ExaSearchErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 228); /** * @generated from message agent.v1.ExaSearchRejected */ export type ExaSearchRejected = Message<"agent.v1.ExaSearchRejected"> & { - /** - * @generated from field: string reason = 1; - */ - reason: string; + /** + * @generated from field: string reason = 1; + */ + reason: string; }; /** * Describes the message agent.v1.ExaSearchRejected. * Use `create(ExaSearchRejectedSchema)` to create a new message. */ -export const ExaSearchRejectedSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 229); +export const ExaSearchRejectedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 229); /** * @generated from message agent.v1.ExaSearchReference */ export type ExaSearchReference = Message<"agent.v1.ExaSearchReference"> & { - /** - * @generated from field: string title = 1; - */ - title: string; + /** + * @generated from field: string title = 1; + */ + title: string; - /** - * @generated from field: string url = 2; - */ - url: string; + /** + * @generated from field: string url = 2; + */ + url: string; - /** - * @generated from field: string text = 3; - */ - text: string; + /** + * @generated from field: string text = 3; + */ + text: string; - /** - * @generated from field: string published_date = 4; - */ - publishedDate: string; + /** + * @generated from field: string published_date = 4; + */ + publishedDate: string; }; /** * Describes the message agent.v1.ExaSearchReference. * Use `create(ExaSearchReferenceSchema)` to create a new message. */ -export const ExaSearchReferenceSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 230); +export const ExaSearchReferenceSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 230); /** * @generated from message agent.v1.ExaSearchToolCall */ export type ExaSearchToolCall = Message<"agent.v1.ExaSearchToolCall"> & { - /** - * @generated from field: agent.v1.ExaSearchArgs args = 1; - */ - args?: ExaSearchArgs; + /** + * @generated from field: agent.v1.ExaSearchArgs args = 1; + */ + args?: ExaSearchArgs | undefined; - /** - * @generated from field: agent.v1.ExaSearchResult result = 2; - */ - result?: ExaSearchResult; + /** + * @generated from field: agent.v1.ExaSearchResult result = 2; + */ + result?: ExaSearchResult | undefined; }; /** * Describes the message agent.v1.ExaSearchToolCall. * Use `create(ExaSearchToolCallSchema)` to create a new message. */ -export const ExaSearchToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 231); +export const ExaSearchToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 231); /** * @generated from message agent.v1.ExaSearchRequestQuery */ export type ExaSearchRequestQuery = Message<"agent.v1.ExaSearchRequestQuery"> & { - /** - * @generated from field: agent.v1.ExaSearchArgs args = 1; - */ - args?: ExaSearchArgs; + /** + * @generated from field: agent.v1.ExaSearchArgs args = 1; + */ + args?: ExaSearchArgs | undefined; }; /** * Describes the message agent.v1.ExaSearchRequestQuery. * Use `create(ExaSearchRequestQuerySchema)` to create a new message. */ -export const ExaSearchRequestQuerySchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 232); +export const ExaSearchRequestQuerySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 232); /** * @generated from message agent.v1.ExaSearchRequestResponse */ export type ExaSearchRequestResponse = Message<"agent.v1.ExaSearchRequestResponse"> & { - /** - * @generated from oneof agent.v1.ExaSearchRequestResponse.result - */ - result: - | { - /** - * @generated from field: agent.v1.ExaSearchRequestResponse_Approved approved = 1; - */ - value: ExaSearchRequestResponse_Approved; - case: "approved"; - } - | { - /** - * @generated from field: agent.v1.ExaSearchRequestResponse_Rejected rejected = 2; - */ - value: ExaSearchRequestResponse_Rejected; - case: "rejected"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ExaSearchRequestResponse.result + */ + result: { + /** + * @generated from field: agent.v1.ExaSearchRequestResponse_Approved approved = 1; + */ + value: ExaSearchRequestResponse_Approved; + case: "approved"; + } | { + /** + * @generated from field: agent.v1.ExaSearchRequestResponse_Rejected rejected = 2; + */ + value: ExaSearchRequestResponse_Rejected; + case: "rejected"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ExaSearchRequestResponse. * Use `create(ExaSearchRequestResponseSchema)` to create a new message. */ -export const ExaSearchRequestResponseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 233); +export const ExaSearchRequestResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 233); /** * @generated from message agent.v1.ExaSearchRequestResponse_Approved */ -export type ExaSearchRequestResponse_Approved = Message<"agent.v1.ExaSearchRequestResponse_Approved"> & {}; +export type ExaSearchRequestResponse_Approved = Message<"agent.v1.ExaSearchRequestResponse_Approved"> & { +}; /** * Describes the message agent.v1.ExaSearchRequestResponse_Approved. * Use `create(ExaSearchRequestResponse_ApprovedSchema)` to create a new message. */ -export const ExaSearchRequestResponse_ApprovedSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 234); +export const ExaSearchRequestResponse_ApprovedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 234); /** * @generated from message agent.v1.ExaSearchRequestResponse_Rejected */ export type ExaSearchRequestResponse_Rejected = Message<"agent.v1.ExaSearchRequestResponse_Rejected"> & { - /** - * @generated from field: string reason = 1; - */ - reason: string; + /** + * @generated from field: string reason = 1; + */ + reason: string; }; /** * Describes the message agent.v1.ExaSearchRequestResponse_Rejected. * Use `create(ExaSearchRequestResponse_RejectedSchema)` to create a new message. */ -export const ExaSearchRequestResponse_RejectedSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 235); +export const ExaSearchRequestResponse_RejectedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 235); /** * @generated from message agent.v1.ExecClientStreamClose */ export type ExecClientStreamClose = Message<"agent.v1.ExecClientStreamClose"> & { - /** - * @generated from field: uint32 id = 1; - */ - id: number; + /** + * @generated from field: uint32 id = 1; + */ + id: number; }; /** * Describes the message agent.v1.ExecClientStreamClose. * Use `create(ExecClientStreamCloseSchema)` to create a new message. */ -export const ExecClientStreamCloseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 236); +export const ExecClientStreamCloseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 236); /** * @generated from message agent.v1.ExecClientThrow */ export type ExecClientThrow = Message<"agent.v1.ExecClientThrow"> & { - /** - * @generated from field: uint32 id = 1; - */ - id: number; + /** + * @generated from field: uint32 id = 1; + */ + id: number; - /** - * @generated from field: string error = 2; - */ - error: string; + /** + * @generated from field: string error = 2; + */ + error: string; - /** - * @generated from field: optional string stack_trace = 3; - */ - stackTrace?: string; + /** + * @generated from field: optional string stack_trace = 3; + */ + stackTrace?: string | undefined; + + /** + * @generated from field: optional string error_code = 4; + */ + errorCode?: string | undefined; }; /** * Describes the message agent.v1.ExecClientThrow. * Use `create(ExecClientThrowSchema)` to create a new message. */ -export const ExecClientThrowSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 237); +export const ExecClientThrowSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 237); /** * @generated from message agent.v1.ExecClientHeartbeat */ export type ExecClientHeartbeat = Message<"agent.v1.ExecClientHeartbeat"> & { - /** - * @generated from field: uint32 id = 1; - */ - id: number; + /** + * @generated from field: uint32 id = 1; + */ + id: number; }; /** * Describes the message agent.v1.ExecClientHeartbeat. * Use `create(ExecClientHeartbeatSchema)` to create a new message. */ -export const ExecClientHeartbeatSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 238); +export const ExecClientHeartbeatSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 238); /** * @generated from message agent.v1.ExecClientControlMessage */ export type ExecClientControlMessage = Message<"agent.v1.ExecClientControlMessage"> & { - /** - * @generated from oneof agent.v1.ExecClientControlMessage.message - */ - message: - | { - /** - * @generated from field: agent.v1.ExecClientStreamClose stream_close = 1; - */ - value: ExecClientStreamClose; - case: "streamClose"; - } - | { - /** - * @generated from field: agent.v1.ExecClientThrow throw = 2; - */ - value: ExecClientThrow; - case: "throw"; - } - | { - /** - * @generated from field: agent.v1.ExecClientHeartbeat heartbeat = 3; - */ - value: ExecClientHeartbeat; - case: "heartbeat"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ExecClientControlMessage.message + */ + message: { + /** + * @generated from field: agent.v1.ExecClientStreamClose stream_close = 1; + */ + value: ExecClientStreamClose; + case: "streamClose"; + } | { + /** + * @generated from field: agent.v1.ExecClientThrow throw = 2; + */ + value: ExecClientThrow; + case: "throw"; + } | { + /** + * @generated from field: agent.v1.ExecClientHeartbeat heartbeat = 3; + */ + value: ExecClientHeartbeat; + case: "heartbeat"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ExecClientControlMessage. * Use `create(ExecClientControlMessageSchema)` to create a new message. */ -export const ExecClientControlMessageSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 239); +export const ExecClientControlMessageSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 239); /** * Simplified span context for tracing exec calls @@ -6794,1415 +6802,1719 @@ export const ExecClientControlMessageSchema: GenMessage & { - /** - * Trace identifier (128-bit as hex string, same for all spans in a trace) - * - * @generated from field: string trace_id = 1; - */ - traceId: string; + /** + * Trace identifier (128-bit as hex string, same for all spans in a trace) + * + * @generated from field: string trace_id = 1; + */ + traceId: string; - /** - * Unique span identifier (64-bit as hex string) - * - * @generated from field: string span_id = 2; - */ - spanId: string; + /** + * Unique span identifier (64-bit as hex string) + * + * @generated from field: string span_id = 2; + */ + spanId: string; - /** - * Trace flags bit field following OTEL SPAN_FLAGS_* semantics - * - * @generated from field: optional uint32 trace_flags = 3; - */ - traceFlags?: number; + /** + * Trace flags bit field following OTEL SPAN_FLAGS_* semantics + * + * @generated from field: optional uint32 trace_flags = 3; + */ + traceFlags?: number | undefined; - /** - * W3C trace-state header string (optional) - * - * @generated from field: optional string trace_state = 4; - */ - traceState?: string; + /** + * W3C trace-state header string (optional) + * + * @generated from field: optional string trace_state = 4; + */ + traceState?: string | undefined; }; /** * Describes the message agent.v1.SpanContext. * Use `create(SpanContextSchema)` to create a new message. */ -export const SpanContextSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 240); +export const SpanContextSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 240); /** * Empty abort message for aborting running execs * * @generated from message agent.v1.AbortArgs */ -export type AbortArgs = Message<"agent.v1.AbortArgs"> & {}; +export type AbortArgs = Message<"agent.v1.AbortArgs"> & { +}; /** * Describes the message agent.v1.AbortArgs. * Use `create(AbortArgsSchema)` to create a new message. */ -export const AbortArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 241); +export const AbortArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 241); /** * @generated from message agent.v1.AbortResult */ -export type AbortResult = Message<"agent.v1.AbortResult"> & {}; +export type AbortResult = Message<"agent.v1.AbortResult"> & { +}; /** * Describes the message agent.v1.AbortResult. * Use `create(AbortResultSchema)` to create a new message. */ -export const AbortResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 242); +export const AbortResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 242); /** * @generated from message agent.v1.ExecServerMessage */ export type ExecServerMessage = Message<"agent.v1.ExecServerMessage"> & { - /** - * @generated from field: uint32 id = 1; - */ - id: number; + /** + * @generated from field: uint32 id = 1; + */ + id: number; - /** - * Optional exec ID for attachable executions - * - * @generated from field: string exec_id = 15; - */ - execId: string; + /** + * Optional exec ID for attachable executions + * + * @generated from field: string exec_id = 15; + */ + execId: string; - /** - * Optional parent span context for tracing - * - * @generated from field: optional agent.v1.SpanContext span_context = 19; - */ - spanContext?: SpanContext; + /** + * Optional parent span context for tracing + * + * @generated from field: optional agent.v1.SpanContext span_context = 19; + */ + spanContext?: SpanContext | undefined; - /** - * @generated from oneof agent.v1.ExecServerMessage.message - */ - message: - | { - /** - * @generated from field: agent.v1.ShellArgs shell_args = 2; - */ - value: ShellArgs; - case: "shellArgs"; - } - | { - /** - * @generated from field: agent.v1.WriteArgs write_args = 3; - */ - value: WriteArgs; - case: "writeArgs"; - } - | { - /** - * @generated from field: agent.v1.DeleteArgs delete_args = 4; - */ - value: DeleteArgs; - case: "deleteArgs"; - } - | { - /** - * @generated from field: agent.v1.GrepArgs grep_args = 5; - */ - value: GrepArgs; - case: "grepArgs"; - } - | { - /** - * @generated from field: agent.v1.ReadArgs read_args = 7; - */ - value: ReadArgs; - case: "readArgs"; - } - | { - /** - * @generated from field: agent.v1.LsArgs ls_args = 8; - */ - value: LsArgs; - case: "lsArgs"; - } - | { - /** - * @generated from field: agent.v1.DiagnosticsArgs diagnostics_args = 9; - */ - value: DiagnosticsArgs; - case: "diagnosticsArgs"; - } - | { - /** - * @generated from field: agent.v1.RequestContextArgs request_context_args = 10; - */ - value: RequestContextArgs; - case: "requestContextArgs"; - } - | { - /** - * @generated from field: agent.v1.McpArgs mcp_args = 11; - */ - value: McpArgs; - case: "mcpArgs"; - } - | { - /** - * @generated from field: agent.v1.ShellArgs shell_stream_args = 14; - */ - value: ShellArgs; - case: "shellStreamArgs"; - } - | { - /** - * @generated from field: agent.v1.BackgroundShellSpawnArgs background_shell_spawn_args = 16; - */ - value: BackgroundShellSpawnArgs; - case: "backgroundShellSpawnArgs"; - } - | { - /** - * @generated from field: agent.v1.ListMcpResourcesExecArgs list_mcp_resources_exec_args = 17; - */ - value: ListMcpResourcesExecArgs; - case: "listMcpResourcesExecArgs"; - } - | { - /** - * @generated from field: agent.v1.ReadMcpResourceExecArgs read_mcp_resource_exec_args = 18; - */ - value: ReadMcpResourceExecArgs; - case: "readMcpResourceExecArgs"; - } - | { - /** - * @generated from field: agent.v1.FetchArgs fetch_args = 20; - */ - value: FetchArgs; - case: "fetchArgs"; - } - | { - /** - * @generated from field: agent.v1.RecordScreenArgs record_screen_args = 21; - */ - value: RecordScreenArgs; - case: "recordScreenArgs"; - } - | { - /** - * @generated from field: agent.v1.ComputerUseArgs computer_use_args = 22; - */ - value: ComputerUseArgs; - case: "computerUseArgs"; - } - | { - /** - * @generated from field: agent.v1.WriteShellStdinArgs write_shell_stdin_args = 23; - */ - value: WriteShellStdinArgs; - case: "writeShellStdinArgs"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ExecServerMessage.message + */ + message: { + /** + * @generated from field: agent.v1.ShellArgs shell_args = 2; + */ + value: ShellArgs; + case: "shellArgs"; + } | { + /** + * @generated from field: agent.v1.WriteArgs write_args = 3; + */ + value: WriteArgs; + case: "writeArgs"; + } | { + /** + * @generated from field: agent.v1.DeleteArgs delete_args = 4; + */ + value: DeleteArgs; + case: "deleteArgs"; + } | { + /** + * @generated from field: agent.v1.GrepArgs grep_args = 5; + */ + value: GrepArgs; + case: "grepArgs"; + } | { + /** + * @generated from field: agent.v1.ReadArgs read_args = 7; + */ + value: ReadArgs; + case: "readArgs"; + } | { + /** + * @generated from field: agent.v1.LsArgs ls_args = 8; + */ + value: LsArgs; + case: "lsArgs"; + } | { + /** + * @generated from field: agent.v1.DiagnosticsArgs diagnostics_args = 9; + */ + value: DiagnosticsArgs; + case: "diagnosticsArgs"; + } | { + /** + * @generated from field: agent.v1.RequestContextArgs request_context_args = 10; + */ + value: RequestContextArgs; + case: "requestContextArgs"; + } | { + /** + * @generated from field: agent.v1.McpArgs mcp_args = 11; + */ + value: McpArgs; + case: "mcpArgs"; + } | { + /** + * @generated from field: agent.v1.ShellArgs shell_stream_args = 14; + */ + value: ShellArgs; + case: "shellStreamArgs"; + } | { + /** + * @generated from field: agent.v1.BackgroundShellSpawnArgs background_shell_spawn_args = 16; + */ + value: BackgroundShellSpawnArgs; + case: "backgroundShellSpawnArgs"; + } | { + /** + * @generated from field: agent.v1.ListMcpResourcesExecArgs list_mcp_resources_exec_args = 17; + */ + value: ListMcpResourcesExecArgs; + case: "listMcpResourcesExecArgs"; + } | { + /** + * @generated from field: agent.v1.ReadMcpResourceExecArgs read_mcp_resource_exec_args = 18; + */ + value: ReadMcpResourceExecArgs; + case: "readMcpResourceExecArgs"; + } | { + /** + * @generated from field: agent.v1.FetchArgs fetch_args = 20; + */ + value: FetchArgs; + case: "fetchArgs"; + } | { + /** + * @generated from field: agent.v1.RecordScreenArgs record_screen_args = 21; + */ + value: RecordScreenArgs; + case: "recordScreenArgs"; + } | { + /** + * @generated from field: agent.v1.ComputerUseArgs computer_use_args = 22; + */ + value: ComputerUseArgs; + case: "computerUseArgs"; + } | { + /** + * @generated from field: agent.v1.WriteShellStdinArgs write_shell_stdin_args = 23; + */ + value: WriteShellStdinArgs; + case: "writeShellStdinArgs"; + } | { + /** + * @generated from field: agent.v1.ReadArgs redacted_read_args = 29; + */ + value: ReadArgs; + case: "redactedReadArgs"; + } | { + /** + * @generated from field: agent.v1.McpStateExecArgs mcp_state_exec_args = 36; + */ + value: McpStateExecArgs; + case: "mcpStateExecArgs"; + } | { + /** + * @generated from field: agent.v1.ExecuteHookArgs execute_hook_args = 27; + */ + value: ExecuteHookArgs; + case: "executeHookArgs"; + } | { + /** + * @generated from field: agent.v1.SubagentArgs subagent_args = 28; + */ + value: SubagentArgs; + case: "subagentArgs"; + } | { + /** + * @generated from field: agent.v1.ForceBackgroundShellArgs force_background_shell_args = 30; + */ + value: ForceBackgroundShellArgs; + case: "forceBackgroundShellArgs"; + } | { + /** + * @generated from field: agent.v1.ForceBackgroundSubagentArgs force_background_subagent_args = 31; + */ + value: ForceBackgroundSubagentArgs; + case: "forceBackgroundSubagentArgs"; + } | { + /** + * @generated from field: agent.v1.SubagentAwaitArgs subagent_await_args = 37; + */ + value: SubagentAwaitArgs; + case: "subagentAwaitArgs"; + } | { + /** + * @generated from field: agent.v1.SmartModeClassifierArgs smart_mode_classifier_args = 38; + */ + value: SmartModeClassifierArgs; + case: "smartModeClassifierArgs"; + } | { + /** + * @generated from field: agent.v1.CanvasDiagnosticsArgs canvas_diagnostics_args = 40; + */ + value: CanvasDiagnosticsArgs; + case: "canvasDiagnosticsArgs"; + } | { + /** + * @generated from field: agent.v1.ShellAllowlistPrecheckArgs shell_allowlist_precheck_args = 41; + */ + value: ShellAllowlistPrecheckArgs; + case: "shellAllowlistPrecheckArgs"; + } | { + /** + * @generated from field: agent.v1.McpAllowlistPrecheckArgs mcp_allowlist_precheck_args = 42; + */ + value: McpAllowlistPrecheckArgs; + case: "mcpAllowlistPrecheckArgs"; + } | { + /** + * @generated from field: agent.v1.WebFetchAllowlistPrecheckArgs web_fetch_allowlist_precheck_args = 43; + */ + value: WebFetchAllowlistPrecheckArgs; + case: "webFetchAllowlistPrecheckArgs"; + } | { + /** + * @generated from field: agent.v1.GetDiffRequest git_diff_request = 44; + */ + value: GetDiffRequest; + case: "gitDiffRequest"; + } | { + /** + * @generated from field: agent.v1.PiReadExecArgs pi_read_args = 45; + */ + value: PiReadExecArgs; + case: "piReadArgs"; + } | { + /** + * @generated from field: agent.v1.PiBashExecArgs pi_bash_args = 46; + */ + value: PiBashExecArgs; + case: "piBashArgs"; + } | { + /** + * @generated from field: agent.v1.PiEditExecArgs pi_edit_args = 47; + */ + value: PiEditExecArgs; + case: "piEditArgs"; + } | { + /** + * @generated from field: agent.v1.PiWriteExecArgs pi_write_args = 48; + */ + value: PiWriteExecArgs; + case: "piWriteArgs"; + } | { + /** + * @generated from field: agent.v1.PiGrepExecArgs pi_grep_args = 49; + */ + value: PiGrepExecArgs; + case: "piGrepArgs"; + } | { + /** + * @generated from field: agent.v1.PiFindExecArgs pi_find_args = 50; + */ + value: PiFindExecArgs; + case: "piFindArgs"; + } | { + /** + * @generated from field: agent.v1.PiLsExecArgs pi_ls_args = 51; + */ + value: PiLsExecArgs; + case: "piLsArgs"; + } | { + /** + * @generated from field: agent.v1.ShellArgs mini_swe_agent_bash_args = 52; + */ + value: ShellArgs; + case: "miniSweAgentBashArgs"; + } | { + /** + * @generated from field: agent.v1.ConversationSearchArgs conversation_search_args = 53; + */ + value: ConversationSearchArgs; + case: "conversationSearchArgs"; + } | { + /** + * @generated from field: agent.v1.AgentStoreConflictArgs agent_store_conflict_args = 54; + */ + value: AgentStoreConflictArgs; + case: "agentStoreConflictArgs"; + } | { case: undefined; value?: undefined }; + + /** + * @generated from field: optional bool accept_hook_additional_contexts = 55; + */ + acceptHookAdditionalContexts?: boolean | undefined; }; /** * Describes the message agent.v1.ExecServerMessage. * Use `create(ExecServerMessageSchema)` to create a new message. */ -export const ExecServerMessageSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 243); +export const ExecServerMessageSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 243); /** * @generated from message agent.v1.ExecClientMessage */ export type ExecClientMessage = Message<"agent.v1.ExecClientMessage"> & { - /** - * @generated from field: uint32 id = 1; - */ - id: number; + /** + * @generated from field: uint32 id = 1; + */ + id: number; - /** - * Optional exec ID for attachable executions - * - * @generated from field: string exec_id = 15; - */ - execId: string; + /** + * Optional exec ID for attachable executions + * + * @generated from field: string exec_id = 15; + */ + execId: string; - /** - * @generated from oneof agent.v1.ExecClientMessage.message - */ - message: - | { - /** - * @generated from field: agent.v1.ShellResult shell_result = 2; - */ - value: ShellResult; - case: "shellResult"; - } - | { - /** - * @generated from field: agent.v1.WriteResult write_result = 3; - */ - value: WriteResult; - case: "writeResult"; - } - | { - /** - * @generated from field: agent.v1.DeleteResult delete_result = 4; - */ - value: DeleteResult; - case: "deleteResult"; - } - | { - /** - * @generated from field: agent.v1.GrepResult grep_result = 5; - */ - value: GrepResult; - case: "grepResult"; - } - | { - /** - * @generated from field: agent.v1.ReadResult read_result = 7; - */ - value: ReadResult; - case: "readResult"; - } - | { - /** - * @generated from field: agent.v1.LsResult ls_result = 8; - */ - value: LsResult; - case: "lsResult"; - } - | { - /** - * @generated from field: agent.v1.DiagnosticsResult diagnostics_result = 9; - */ - value: DiagnosticsResult; - case: "diagnosticsResult"; - } - | { - /** - * @generated from field: agent.v1.RequestContextResult request_context_result = 10; - */ - value: RequestContextResult; - case: "requestContextResult"; - } - | { - /** - * @generated from field: agent.v1.McpResult mcp_result = 11; - */ - value: McpResult; - case: "mcpResult"; - } - | { - /** - * @generated from field: agent.v1.ShellStream shell_stream = 14; - */ - value: ShellStream; - case: "shellStream"; - } - | { - /** - * @generated from field: agent.v1.BackgroundShellSpawnResult background_shell_spawn_result = 16; - */ - value: BackgroundShellSpawnResult; - case: "backgroundShellSpawnResult"; - } - | { - /** - * @generated from field: agent.v1.ListMcpResourcesExecResult list_mcp_resources_exec_result = 17; - */ - value: ListMcpResourcesExecResult; - case: "listMcpResourcesExecResult"; - } - | { - /** - * @generated from field: agent.v1.ReadMcpResourceExecResult read_mcp_resource_exec_result = 18; - */ - value: ReadMcpResourceExecResult; - case: "readMcpResourceExecResult"; - } - | { - /** - * @generated from field: agent.v1.FetchResult fetch_result = 20; - */ - value: FetchResult; - case: "fetchResult"; - } - | { - /** - * @generated from field: agent.v1.RecordScreenResult record_screen_result = 21; - */ - value: RecordScreenResult; - case: "recordScreenResult"; - } - | { - /** - * @generated from field: agent.v1.ComputerUseResult computer_use_result = 22; - */ - value: ComputerUseResult; - case: "computerUseResult"; - } - | { - /** - * @generated from field: agent.v1.WriteShellStdinResult write_shell_stdin_result = 23; - */ - value: WriteShellStdinResult; - case: "writeShellStdinResult"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ExecClientMessage.message + */ + message: { + /** + * @generated from field: agent.v1.ShellResult shell_result = 2; + */ + value: ShellResult; + case: "shellResult"; + } | { + /** + * @generated from field: agent.v1.WriteResult write_result = 3; + */ + value: WriteResult; + case: "writeResult"; + } | { + /** + * @generated from field: agent.v1.DeleteResult delete_result = 4; + */ + value: DeleteResult; + case: "deleteResult"; + } | { + /** + * @generated from field: agent.v1.GrepResult grep_result = 5; + */ + value: GrepResult; + case: "grepResult"; + } | { + /** + * @generated from field: agent.v1.ReadResult read_result = 7; + */ + value: ReadResult; + case: "readResult"; + } | { + /** + * @generated from field: agent.v1.LsResult ls_result = 8; + */ + value: LsResult; + case: "lsResult"; + } | { + /** + * @generated from field: agent.v1.DiagnosticsResult diagnostics_result = 9; + */ + value: DiagnosticsResult; + case: "diagnosticsResult"; + } | { + /** + * @generated from field: agent.v1.RequestContextResult request_context_result = 10; + */ + value: RequestContextResult; + case: "requestContextResult"; + } | { + /** + * @generated from field: agent.v1.McpResult mcp_result = 11; + */ + value: McpResult; + case: "mcpResult"; + } | { + /** + * @generated from field: agent.v1.ShellStream shell_stream = 14; + */ + value: ShellStream; + case: "shellStream"; + } | { + /** + * @generated from field: agent.v1.BackgroundShellSpawnResult background_shell_spawn_result = 16; + */ + value: BackgroundShellSpawnResult; + case: "backgroundShellSpawnResult"; + } | { + /** + * @generated from field: agent.v1.ListMcpResourcesExecResult list_mcp_resources_exec_result = 17; + */ + value: ListMcpResourcesExecResult; + case: "listMcpResourcesExecResult"; + } | { + /** + * @generated from field: agent.v1.ReadMcpResourceExecResult read_mcp_resource_exec_result = 18; + */ + value: ReadMcpResourceExecResult; + case: "readMcpResourceExecResult"; + } | { + /** + * @generated from field: agent.v1.FetchResult fetch_result = 20; + */ + value: FetchResult; + case: "fetchResult"; + } | { + /** + * @generated from field: agent.v1.RecordScreenResult record_screen_result = 21; + */ + value: RecordScreenResult; + case: "recordScreenResult"; + } | { + /** + * @generated from field: agent.v1.ComputerUseResult computer_use_result = 22; + */ + value: ComputerUseResult; + case: "computerUseResult"; + } | { + /** + * @generated from field: agent.v1.WriteShellStdinResult write_shell_stdin_result = 23; + */ + value: WriteShellStdinResult; + case: "writeShellStdinResult"; + } | { + /** + * @generated from field: agent.v1.ReadResult redacted_read_result = 29; + */ + value: ReadResult; + case: "redactedReadResult"; + } | { + /** + * @generated from field: agent.v1.McpStateExecResult mcp_state_exec_result = 36; + */ + value: McpStateExecResult; + case: "mcpStateExecResult"; + } | { + /** + * @generated from field: agent.v1.ExecuteHookResult execute_hook_result = 27; + */ + value: ExecuteHookResult; + case: "executeHookResult"; + } | { + /** + * @generated from field: agent.v1.SubagentResult subagent_result = 28; + */ + value: SubagentResult; + case: "subagentResult"; + } | { + /** + * @generated from field: agent.v1.ForceBackgroundShellResult force_background_shell_result = 30; + */ + value: ForceBackgroundShellResult; + case: "forceBackgroundShellResult"; + } | { + /** + * @generated from field: agent.v1.ForceBackgroundSubagentResult force_background_subagent_result = 31; + */ + value: ForceBackgroundSubagentResult; + case: "forceBackgroundSubagentResult"; + } | { + /** + * @generated from field: agent.v1.SubagentAwaitResult subagent_await_result = 37; + */ + value: SubagentAwaitResult; + case: "subagentAwaitResult"; + } | { + /** + * @generated from field: agent.v1.SmartModeClassifierResult smart_mode_classifier_result = 38; + */ + value: SmartModeClassifierResult; + case: "smartModeClassifierResult"; + } | { + /** + * @generated from field: agent.v1.CanvasDiagnosticsResult canvas_diagnostics_result = 40; + */ + value: CanvasDiagnosticsResult; + case: "canvasDiagnosticsResult"; + } | { + /** + * @generated from field: agent.v1.ShellAllowlistPrecheckResult shell_allowlist_precheck_result = 41; + */ + value: ShellAllowlistPrecheckResult; + case: "shellAllowlistPrecheckResult"; + } | { + /** + * @generated from field: agent.v1.McpAllowlistPrecheckResult mcp_allowlist_precheck_result = 42; + */ + value: McpAllowlistPrecheckResult; + case: "mcpAllowlistPrecheckResult"; + } | { + /** + * @generated from field: agent.v1.WebFetchAllowlistPrecheckResult web_fetch_allowlist_precheck_result = 43; + */ + value: WebFetchAllowlistPrecheckResult; + case: "webFetchAllowlistPrecheckResult"; + } | { + /** + * @generated from field: agent.v1.GetDiffResponse git_diff_response = 44; + */ + value: GetDiffResponse; + case: "gitDiffResponse"; + } | { + /** + * @generated from field: agent.v1.PiReadExecResult pi_read_result = 46; + */ + value: PiReadExecResult; + case: "piReadResult"; + } | { + /** + * @generated from field: agent.v1.PiBashExecResult pi_bash_result = 47; + */ + value: PiBashExecResult; + case: "piBashResult"; + } | { + /** + * @generated from field: agent.v1.PiEditExecResult pi_edit_result = 48; + */ + value: PiEditExecResult; + case: "piEditResult"; + } | { + /** + * @generated from field: agent.v1.PiWriteExecResult pi_write_result = 49; + */ + value: PiWriteExecResult; + case: "piWriteResult"; + } | { + /** + * @generated from field: agent.v1.PiGrepExecResult pi_grep_result = 50; + */ + value: PiGrepExecResult; + case: "piGrepResult"; + } | { + /** + * @generated from field: agent.v1.PiFindExecResult pi_find_result = 51; + */ + value: PiFindExecResult; + case: "piFindResult"; + } | { + /** + * @generated from field: agent.v1.PiLsExecResult pi_ls_result = 52; + */ + value: PiLsExecResult; + case: "piLsResult"; + } | { + /** + * @generated from field: agent.v1.ConversationSearchResult conversation_search_result = 53; + */ + value: ConversationSearchResult; + case: "conversationSearchResult"; + } | { + /** + * @generated from field: agent.v1.AgentStoreConflictResult agent_store_conflict_result = 54; + */ + value: AgentStoreConflictResult; + case: "agentStoreConflictResult"; + } | { + /** + * @generated from field: agent.v1.ShellResult mini_swe_agent_bash_result = 55; + */ + value: ShellResult; + case: "miniSweAgentBashResult"; + } | { case: undefined; value?: undefined }; + + /** + * @generated from field: optional int32 local_execution_time_ms = 39; + */ + localExecutionTimeMs?: number | undefined; + + /** + * @generated from field: repeated agent.v1.HookAdditionalContext hook_additional_contexts = 45; + */ + hookAdditionalContexts: HookAdditionalContext[]; }; /** * Describes the message agent.v1.ExecClientMessage. * Use `create(ExecClientMessageSchema)` to create a new message. */ -export const ExecClientMessageSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 244); +export const ExecClientMessageSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 244); /** * @generated from message agent.v1.FetchArgs */ export type FetchArgs = Message<"agent.v1.FetchArgs"> & { - /** - * @generated from field: string url = 1; - */ - url: string; + /** + * @generated from field: string url = 1; + */ + url: string; - /** - * @generated from field: string tool_call_id = 2; - */ - toolCallId: string; + /** + * @generated from field: string tool_call_id = 2; + */ + toolCallId: string; }; /** * Describes the message agent.v1.FetchArgs. * Use `create(FetchArgsSchema)` to create a new message. */ -export const FetchArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 245); +export const FetchArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 245); /** * @generated from message agent.v1.FetchResult */ export type FetchResult = Message<"agent.v1.FetchResult"> & { - /** - * @generated from oneof agent.v1.FetchResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.FetchSuccess success = 1; - */ - value: FetchSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.FetchError error = 2; - */ - value: FetchError; - case: "error"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.FetchResult.result + */ + result: { + /** + * @generated from field: agent.v1.FetchSuccess success = 1; + */ + value: FetchSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.FetchError error = 2; + */ + value: FetchError; + case: "error"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.FetchResult. * Use `create(FetchResultSchema)` to create a new message. */ -export const FetchResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 246); +export const FetchResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 246); /** * @generated from message agent.v1.FetchSuccess */ export type FetchSuccess = Message<"agent.v1.FetchSuccess"> & { - /** - * @generated from field: string url = 1; - */ - url: string; + /** + * @generated from field: string url = 1; + */ + url: string; - /** - * @generated from field: string content = 2; - */ - content: string; + /** + * @generated from field: string content = 2; + */ + content: string; - /** - * @generated from field: int32 status_code = 3; - */ - statusCode: number; + /** + * @generated from field: int32 status_code = 3; + */ + statusCode: number; - /** - * @generated from field: string content_type = 4; - */ - contentType: string; + /** + * @generated from field: string content_type = 4; + */ + contentType: string; }; /** * Describes the message agent.v1.FetchSuccess. * Use `create(FetchSuccessSchema)` to create a new message. */ -export const FetchSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 247); +export const FetchSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 247); /** * @generated from message agent.v1.FetchError */ export type FetchError = Message<"agent.v1.FetchError"> & { - /** - * @generated from field: string url = 1; - */ - url: string; + /** + * @generated from field: string url = 1; + */ + url: string; - /** - * @generated from field: string error = 2; - */ - error: string; + /** + * @generated from field: string error = 2; + */ + error: string; }; /** * Describes the message agent.v1.FetchError. * Use `create(FetchErrorSchema)` to create a new message. */ -export const FetchErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 248); +export const FetchErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 248); /** * @generated from message agent.v1.GenerateImageArgs */ export type GenerateImageArgs = Message<"agent.v1.GenerateImageArgs"> & { - /** - * @generated from field: string description = 1; - */ - description: string; + /** + * @generated from field: string description = 1; + */ + description: string; - /** - * @generated from field: optional string file_path = 2; - */ - filePath?: string; + /** + * @generated from field: optional string file_path = 2; + */ + filePath?: string | undefined; - /** - * Optional paths to reference images to use as input for image-to-image generation - * - * @generated from field: repeated string reference_image_paths = 5; - */ - referenceImagePaths: string[]; + /** + * Optional paths to reference images to use as input for image-to-image generation + * + * @generated from field: repeated string reference_image_paths = 5; + */ + referenceImagePaths: string[]; }; /** * Describes the message agent.v1.GenerateImageArgs. * Use `create(GenerateImageArgsSchema)` to create a new message. */ -export const GenerateImageArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 249); +export const GenerateImageArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 249); /** * @generated from message agent.v1.GenerateImageResult */ export type GenerateImageResult = Message<"agent.v1.GenerateImageResult"> & { - /** - * @generated from oneof agent.v1.GenerateImageResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.GenerateImageSuccess success = 1; - */ - value: GenerateImageSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.GenerateImageError error = 2; - */ - value: GenerateImageError; - case: "error"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.GenerateImageResult.result + */ + result: { + /** + * @generated from field: agent.v1.GenerateImageSuccess success = 1; + */ + value: GenerateImageSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.GenerateImageError error = 2; + */ + value: GenerateImageError; + case: "error"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.GenerateImageResult. * Use `create(GenerateImageResultSchema)` to create a new message. */ -export const GenerateImageResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 250); +export const GenerateImageResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 250); /** * @generated from message agent.v1.GenerateImageSuccess */ export type GenerateImageSuccess = Message<"agent.v1.GenerateImageSuccess"> & { - /** - * Actual file path where the image was saved (e.g., /path/to/project/assets/image.png) - * - * @generated from field: string file_path = 1; - */ - filePath: string; + /** + * Actual file path where the image was saved (e.g., /path/to/project/assets/image.png) + * + * @generated from field: string file_path = 1; + */ + filePath: string; - /** - * Base64-encoded image data - * - * @generated from field: string image_data = 2; - */ - imageData: string; + /** + * Base64-encoded image data + * + * @generated from field: string image_data = 2; + */ + imageData: string; }; /** * Describes the message agent.v1.GenerateImageSuccess. * Use `create(GenerateImageSuccessSchema)` to create a new message. */ -export const GenerateImageSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 251); +export const GenerateImageSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 251); /** * @generated from message agent.v1.GenerateImageError */ export type GenerateImageError = Message<"agent.v1.GenerateImageError"> & { - /** - * @generated from field: string error = 1; - */ - error: string; + /** + * @generated from field: string error = 1; + */ + error: string; }; /** * Describes the message agent.v1.GenerateImageError. * Use `create(GenerateImageErrorSchema)` to create a new message. */ -export const GenerateImageErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 252); +export const GenerateImageErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 252); /** * @generated from message agent.v1.GenerateImageToolCall */ export type GenerateImageToolCall = Message<"agent.v1.GenerateImageToolCall"> & { - /** - * @generated from field: agent.v1.GenerateImageArgs args = 1; - */ - args?: GenerateImageArgs; + /** + * @generated from field: agent.v1.GenerateImageArgs args = 1; + */ + args?: GenerateImageArgs | undefined; - /** - * @generated from field: agent.v1.GenerateImageResult result = 2; - */ - result?: GenerateImageResult; + /** + * @generated from field: agent.v1.GenerateImageResult result = 2; + */ + result?: GenerateImageResult | undefined; }; /** * Describes the message agent.v1.GenerateImageToolCall. * Use `create(GenerateImageToolCallSchema)` to create a new message. */ -export const GenerateImageToolCallSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 253); +export const GenerateImageToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 253); /** * @generated from message agent.v1.GrepArgs */ export type GrepArgs = Message<"agent.v1.GrepArgs"> & { - /** - * @generated from field: string pattern = 1; - */ - pattern: string; + /** + * @generated from field: string pattern = 1; + */ + pattern: string; - /** - * @generated from field: optional string path = 2; - */ - path?: string; + /** + * @generated from field: optional string path = 2; + */ + path?: string | undefined; - /** - * @generated from field: optional string glob = 3; - */ - glob?: string; + /** + * @generated from field: optional string glob = 3; + */ + glob?: string | undefined; - /** - * "content", "files_with_matches", "count" - * - * @generated from field: optional string output_mode = 4; - */ - outputMode?: string; + /** + * "content", "files_with_matches", "count" + * + * @generated from field: optional string output_mode = 4; + */ + outputMode?: string | undefined; - /** - * @generated from field: optional int32 context_before = 5; - */ - contextBefore?: number; + /** + * @generated from field: optional int32 context_before = 5; + */ + contextBefore?: number | undefined; - /** - * @generated from field: optional int32 context_after = 6; - */ - contextAfter?: number; + /** + * @generated from field: optional int32 context_after = 6; + */ + contextAfter?: number | undefined; - /** - * @generated from field: optional int32 context = 7; - */ - context?: number; + /** + * @generated from field: optional int32 context = 7; + */ + context?: number | undefined; - /** - * @generated from field: optional bool case_insensitive = 8; - */ - caseInsensitive?: boolean; + /** + * @generated from field: optional bool case_insensitive = 8; + */ + caseInsensitive?: boolean | undefined; - /** - * --type - * - * @generated from field: optional string type = 9; - */ - type?: string; + /** + * --type + * + * @generated from field: optional string type = 9; + */ + type?: string | undefined; - /** - * | head -N - * - * @generated from field: optional int32 head_limit = 10; - */ - headLimit?: number; + /** + * | head -N + * + * @generated from field: optional int32 head_limit = 10; + */ + headLimit?: number | undefined; - /** - * -U --multiline-dotall - * - * @generated from field: optional bool multiline = 11; - */ - multiline?: boolean; + /** + * -U --multiline-dotall + * + * @generated from field: optional bool multiline = 11; + */ + multiline?: boolean | undefined; - /** - * --sort: "none", "path", "modified", "accessed", "created" - * - * @generated from field: optional string sort = 12; - */ - sort?: string; + /** + * --sort: "none", "path", "modified", "accessed", "created" + * + * @generated from field: optional string sort = 12; + */ + sort?: string | undefined; - /** - * if false, use --sortr for reverse sort - * - * @generated from field: optional bool sort_ascending = 13; - */ - sortAscending?: boolean; + /** + * if false, use --sortr for reverse sort + * + * @generated from field: optional bool sort_ascending = 13; + */ + sortAscending?: boolean | undefined; - /** - * @generated from field: string tool_call_id = 14; - */ - toolCallId: string; + /** + * @generated from field: string tool_call_id = 14; + */ + toolCallId: string; - /** - * @generated from field: optional agent.v1.SandboxPolicy sandbox_policy = 15; - */ - sandboxPolicy?: SandboxPolicy; + /** + * @generated from field: optional agent.v1.SandboxPolicy sandbox_policy = 15; + */ + sandboxPolicy?: SandboxPolicy | undefined; + + /** + * @generated from field: optional int32 offset = 16; + */ + offset?: number | undefined; }; /** * Describes the message agent.v1.GrepArgs. * Use `create(GrepArgsSchema)` to create a new message. */ -export const GrepArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 254); +export const GrepArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 254); /** * @generated from message agent.v1.GrepResult */ export type GrepResult = Message<"agent.v1.GrepResult"> & { - /** - * @generated from oneof agent.v1.GrepResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.GrepSuccess success = 1; - */ - value: GrepSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.GrepError error = 2; - */ - value: GrepError; - case: "error"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.GrepResult.result + */ + result: { + /** + * @generated from field: agent.v1.GrepSuccess success = 1; + */ + value: GrepSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.GrepError error = 2; + */ + value: GrepError; + case: "error"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.GrepResult. * Use `create(GrepResultSchema)` to create a new message. */ -export const GrepResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 255); +export const GrepResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 255); /** * @generated from message agent.v1.GrepError */ export type GrepError = Message<"agent.v1.GrepError"> & { - /** - * @generated from field: string error = 1; - */ - error: string; + /** + * @generated from field: string error = 1; + */ + error: string; }; /** * Describes the message agent.v1.GrepError. * Use `create(GrepErrorSchema)` to create a new message. */ -export const GrepErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 256); +export const GrepErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 256); /** * @generated from message agent.v1.GrepSuccess */ export type GrepSuccess = Message<"agent.v1.GrepSuccess"> & { - /** - * @generated from field: string pattern = 1; - */ - pattern: string; + /** + * @generated from field: string pattern = 1; + */ + pattern: string; - /** - * @generated from field: string path = 2; - */ - path: string; + /** + * @generated from field: string path = 2; + */ + path: string; - /** - * "content", "files_with_matches", or "count" - * - * @generated from field: string output_mode = 3; - */ - outputMode: string; + /** + * "content", "files_with_matches", or "count" + * + * @generated from field: string output_mode = 3; + */ + outputMode: string; - /** - * @generated from field: map workspace_results = 4; - */ - workspaceResults: { [key: string]: GrepUnionResult }; + /** + * @generated from field: map workspace_results = 4; + */ + workspaceResults: { [key: string]: GrepUnionResult }; - /** - * @generated from field: optional agent.v1.GrepUnionResult active_editor_result = 5; - */ - activeEditorResult?: GrepUnionResult; + /** + * @generated from field: optional agent.v1.GrepUnionResult active_editor_result = 5; + */ + activeEditorResult?: GrepUnionResult | undefined; }; /** * Describes the message agent.v1.GrepSuccess. * Use `create(GrepSuccessSchema)` to create a new message. */ -export const GrepSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 257); +export const GrepSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 257); /** * @generated from message agent.v1.GrepUnionResult */ export type GrepUnionResult = Message<"agent.v1.GrepUnionResult"> & { - /** - * @generated from oneof agent.v1.GrepUnionResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.GrepCountResult count = 1; - */ - value: GrepCountResult; - case: "count"; - } - | { - /** - * @generated from field: agent.v1.GrepFilesResult files = 2; - */ - value: GrepFilesResult; - case: "files"; - } - | { - /** - * @generated from field: agent.v1.GrepContentResult content = 3; - */ - value: GrepContentResult; - case: "content"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.GrepUnionResult.result + */ + result: { + /** + * @generated from field: agent.v1.GrepCountResult count = 1; + */ + value: GrepCountResult; + case: "count"; + } | { + /** + * @generated from field: agent.v1.GrepFilesResult files = 2; + */ + value: GrepFilesResult; + case: "files"; + } | { + /** + * @generated from field: agent.v1.GrepContentResult content = 3; + */ + value: GrepContentResult; + case: "content"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.GrepUnionResult. * Use `create(GrepUnionResultSchema)` to create a new message. */ -export const GrepUnionResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 258); +export const GrepUnionResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 258); /** * @generated from message agent.v1.GrepCountResult */ export type GrepCountResult = Message<"agent.v1.GrepCountResult"> & { - /** - * ordered by relevance - * - * @generated from field: repeated agent.v1.GrepFileCount counts = 1; - */ - counts: GrepFileCount[]; + /** + * ordered by relevance + * + * @generated from field: repeated agent.v1.GrepFileCount counts = 1; + */ + counts: GrepFileCount[]; - /** - * The total count of files that the client found from the ripgrep call This is a lower bound if we truncated the output from the ripgrep call itself, but is accurate if client_truncated is true (but may be more than the number of files returned to the server) - * - * @generated from field: int32 total_files = 2; - */ - totalFiles: number; + /** + * The total count of files that the client found from the ripgrep call This is a lower bound if we truncated the output from the ripgrep call itself, but is accurate if client_truncated is true (but may be more than the number of files returned to the server) + * + * @generated from field: int32 total_files = 2; + */ + totalFiles: number; - /** - * The total count of matches that the client found from the ripgrep call This is a lower bound if we truncated the output from the ripgrep call itself, but is accurate if client_truncated is true (but may be more than the number of matches returned to the server) - * - * @generated from field: int32 total_matches = 3; - */ - totalMatches: number; + /** + * The total count of matches that the client found from the ripgrep call This is a lower bound if we truncated the output from the ripgrep call itself, but is accurate if client_truncated is true (but may be more than the number of matches returned to the server) + * + * @generated from field: int32 total_matches = 3; + */ + totalMatches: number; - /** - * true if the client truncated the output sent to the server - * - * @generated from field: bool client_truncated = 4; - */ - clientTruncated: boolean; + /** + * true if the client truncated the output sent to the server + * + * @generated from field: bool client_truncated = 4; + */ + clientTruncated: boolean; - /** - * true if we truncated the output from the ripgrep call itself - * - * @generated from field: bool ripgrep_truncated = 5; - */ - ripgrepTruncated: boolean; + /** + * true if we truncated the output from the ripgrep call itself + * + * @generated from field: bool ripgrep_truncated = 5; + */ + ripgrepTruncated: boolean; + + /** + * @generated from field: optional int32 head_limit_applied = 6; + */ + headLimitApplied?: number | undefined; + + /** + * @generated from field: optional int32 offset_applied = 7; + */ + offsetApplied?: number | undefined; }; /** * Describes the message agent.v1.GrepCountResult. * Use `create(GrepCountResultSchema)` to create a new message. */ -export const GrepCountResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 259); +export const GrepCountResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 259); /** * @generated from message agent.v1.GrepFileCount */ export type GrepFileCount = Message<"agent.v1.GrepFileCount"> & { - /** - * @generated from field: string file = 1; - */ - file: string; + /** + * @generated from field: string file = 1; + */ + file: string; - /** - * @generated from field: int32 count = 2; - */ - count: number; + /** + * @generated from field: int32 count = 2; + */ + count: number; }; /** * Describes the message agent.v1.GrepFileCount. * Use `create(GrepFileCountSchema)` to create a new message. */ -export const GrepFileCountSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 260); +export const GrepFileCountSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 260); /** * @generated from message agent.v1.GrepFilesResult */ export type GrepFilesResult = Message<"agent.v1.GrepFilesResult"> & { - /** - * ordered by relevance - * - * @generated from field: repeated string files = 1; - */ - files: string[]; + /** + * ordered by relevance + * + * @generated from field: repeated string files = 1; + */ + files: string[]; - /** - * The total count of files that the client found from the ripgrep call This is a lower bound if we truncated the output from the ripgrep call itself, but is accurate if client_truncated is true (but may be more than the number of files returned to the server) - * - * @generated from field: int32 total_files = 2; - */ - totalFiles: number; + /** + * The total count of files that the client found from the ripgrep call This is a lower bound if we truncated the output from the ripgrep call itself, but is accurate if client_truncated is true (but may be more than the number of files returned to the server) + * + * @generated from field: int32 total_files = 2; + */ + totalFiles: number; - /** - * true if the client truncated the output sent to the server - * - * @generated from field: bool client_truncated = 3; - */ - clientTruncated: boolean; + /** + * true if the client truncated the output sent to the server + * + * @generated from field: bool client_truncated = 3; + */ + clientTruncated: boolean; - /** - * true if we truncated the output from the ripgrep call itself - * - * @generated from field: bool ripgrep_truncated = 4; - */ - ripgrepTruncated: boolean; + /** + * true if we truncated the output from the ripgrep call itself + * + * @generated from field: bool ripgrep_truncated = 4; + */ + ripgrepTruncated: boolean; + + /** + * @generated from field: optional int32 head_limit_applied = 5; + */ + headLimitApplied?: number | undefined; + + /** + * @generated from field: optional int32 offset_applied = 6; + */ + offsetApplied?: number | undefined; }; /** * Describes the message agent.v1.GrepFilesResult. * Use `create(GrepFilesResultSchema)` to create a new message. */ -export const GrepFilesResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 261); +export const GrepFilesResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 261); /** * @generated from message agent.v1.GrepContentResult */ export type GrepContentResult = Message<"agent.v1.GrepContentResult"> & { - /** - * ordered by relevance - * - * @generated from field: repeated agent.v1.GrepFileMatch matches = 1; - */ - matches: GrepFileMatch[]; + /** + * ordered by relevance + * + * @generated from field: repeated agent.v1.GrepFileMatch matches = 1; + */ + matches: GrepFileMatch[]; - /** - * The total count of lines that the client found from the ripgrep call This is a lower bound if we truncated the output from the ripgrep call itself, but is accurate if client_truncated is true (but may be more than the number of lines returned to the server) - * - * @generated from field: int32 total_lines = 2; - */ - totalLines: number; + /** + * The total count of lines that the client found from the ripgrep call This is a lower bound if we truncated the output from the ripgrep call itself, but is accurate if client_truncated is true (but may be more than the number of lines returned to the server) + * + * @generated from field: int32 total_lines = 2; + */ + totalLines: number; - /** - * The total count of matches that the client found from the ripgrep call This is a lower bound if we truncated the output from the ripgrep call itself, but is accurate if client_truncated is true (but may be more than the number of matches returned to the server) - * - * @generated from field: int32 total_matched_lines = 3; - */ - totalMatchedLines: number; + /** + * The total count of matches that the client found from the ripgrep call This is a lower bound if we truncated the output from the ripgrep call itself, but is accurate if client_truncated is true (but may be more than the number of matches returned to the server) + * + * @generated from field: int32 total_matched_lines = 3; + */ + totalMatchedLines: number; - /** - * true if the client truncated the output sent to the server - * - * @generated from field: bool client_truncated = 4; - */ - clientTruncated: boolean; + /** + * true if the client truncated the output sent to the server + * + * @generated from field: bool client_truncated = 4; + */ + clientTruncated: boolean; - /** - * true if we truncated the output from the ripgrep call itself - * - * @generated from field: bool ripgrep_truncated = 5; - */ - ripgrepTruncated: boolean; + /** + * true if we truncated the output from the ripgrep call itself + * + * @generated from field: bool ripgrep_truncated = 5; + */ + ripgrepTruncated: boolean; + + /** + * @generated from field: optional int32 head_limit_applied = 6; + */ + headLimitApplied?: number | undefined; + + /** + * @generated from field: optional int32 offset_applied = 7; + */ + offsetApplied?: number | undefined; }; /** * Describes the message agent.v1.GrepContentResult. * Use `create(GrepContentResultSchema)` to create a new message. */ -export const GrepContentResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 262); +export const GrepContentResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 262); /** * @generated from message agent.v1.GrepFileMatch */ export type GrepFileMatch = Message<"agent.v1.GrepFileMatch"> & { - /** - * @generated from field: string file = 1; - */ - file: string; + /** + * @generated from field: string file = 1; + */ + file: string; - /** - * @generated from field: repeated agent.v1.GrepContentMatch matches = 2; - */ - matches: GrepContentMatch[]; + /** + * @generated from field: repeated agent.v1.GrepContentMatch matches = 2; + */ + matches: GrepContentMatch[]; }; /** * Describes the message agent.v1.GrepFileMatch. * Use `create(GrepFileMatchSchema)` to create a new message. */ -export const GrepFileMatchSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 263); +export const GrepFileMatchSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 263); /** * @generated from message agent.v1.GrepContentMatch */ export type GrepContentMatch = Message<"agent.v1.GrepContentMatch"> & { - /** - * @generated from field: int32 line_number = 1; - */ - lineNumber: number; + /** + * @generated from field: int32 line_number = 1; + */ + lineNumber: number; - /** - * @generated from field: string content = 2; - */ - content: string; + /** + * @generated from field: string content = 2; + */ + content: string; - /** - * @generated from field: bool content_truncated = 3; - */ - contentTruncated: boolean; + /** + * @generated from field: bool content_truncated = 3; + */ + contentTruncated: boolean; - /** - * true for context lines (-A/B/C) - * - * @generated from field: bool is_context_line = 4; - */ - isContextLine: boolean; + /** + * true for context lines (-A/B/C) + * + * @generated from field: bool is_context_line = 4; + */ + isContextLine: boolean; }; /** * Describes the message agent.v1.GrepContentMatch. * Use `create(GrepContentMatchSchema)` to create a new message. */ -export const GrepContentMatchSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 264); +export const GrepContentMatchSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 264); /** * @generated from message agent.v1.GrepStream */ export type GrepStream = Message<"agent.v1.GrepStream"> & { - /** - * @generated from field: string pattern = 1; - */ - pattern: string; + /** + * @generated from field: string pattern = 1; + */ + pattern: string; }; /** * Describes the message agent.v1.GrepStream. * Use `create(GrepStreamSchema)` to create a new message. */ -export const GrepStreamSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 265); +export const GrepStreamSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 265); /** * @generated from message agent.v1.GrepToolCall */ export type GrepToolCall = Message<"agent.v1.GrepToolCall"> & { - /** - * @generated from field: agent.v1.GrepArgs args = 1; - */ - args?: GrepArgs; + /** + * @generated from field: agent.v1.GrepArgs args = 1; + */ + args?: GrepArgs | undefined; - /** - * @generated from field: agent.v1.GrepResult result = 2; - */ - result?: GrepResult; + /** + * @generated from field: agent.v1.GrepResult result = 2; + */ + result?: GrepResult | undefined; }; /** * Describes the message agent.v1.GrepToolCall. * Use `create(GrepToolCallSchema)` to create a new message. */ -export const GrepToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 266); +export const GrepToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 266); /** * @generated from message agent.v1.GetBlobArgs */ export type GetBlobArgs = Message<"agent.v1.GetBlobArgs"> & { - /** - * @generated from field: bytes blob_id = 1; - */ - blobId: Uint8Array; + /** + * @generated from field: bytes blob_id = 1; + */ + blobId: Uint8Array; }; /** * Describes the message agent.v1.GetBlobArgs. * Use `create(GetBlobArgsSchema)` to create a new message. */ -export const GetBlobArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 267); +export const GetBlobArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 267); /** * @generated from message agent.v1.GetBlobResult */ export type GetBlobResult = Message<"agent.v1.GetBlobResult"> & { - /** - * @generated from field: optional bytes blob_data = 1; - */ - blobData?: Uint8Array; + /** + * @generated from field: optional bytes blob_data = 1; + */ + blobData?: Uint8Array | undefined; }; /** * Describes the message agent.v1.GetBlobResult. * Use `create(GetBlobResultSchema)` to create a new message. */ -export const GetBlobResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 268); +export const GetBlobResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 268); /** * @generated from message agent.v1.SetBlobArgs */ export type SetBlobArgs = Message<"agent.v1.SetBlobArgs"> & { - /** - * @generated from field: bytes blob_id = 1; - */ - blobId: Uint8Array; + /** + * @generated from field: bytes blob_id = 1; + */ + blobId: Uint8Array; - /** - * @generated from field: bytes blob_data = 2; - */ - blobData: Uint8Array; + /** + * @generated from field: bytes blob_data = 2; + */ + blobData: Uint8Array; }; /** * Describes the message agent.v1.SetBlobArgs. * Use `create(SetBlobArgsSchema)` to create a new message. */ -export const SetBlobArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 269); +export const SetBlobArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 269); /** * @generated from message agent.v1.SetBlobResult */ export type SetBlobResult = Message<"agent.v1.SetBlobResult"> & { - /** - * @generated from field: optional agent.v1.Error error = 1; - */ - error?: Error; + /** + * @generated from field: optional agent.v1.Error error = 1; + */ + error?: Error | undefined; }; /** * Describes the message agent.v1.SetBlobResult. * Use `create(SetBlobResultSchema)` to create a new message. */ -export const SetBlobResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 270); +export const SetBlobResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 270); /** * @generated from message agent.v1.KvServerMessage */ export type KvServerMessage = Message<"agent.v1.KvServerMessage"> & { - /** - * @generated from field: uint32 id = 1; - */ - id: number; + /** + * @generated from field: uint32 id = 1; + */ + id: number; - /** - * Span context for distributed tracing - * - * @generated from field: optional agent.v1.SpanContext span_context = 4; - */ - spanContext?: SpanContext; + /** + * Span context for distributed tracing + * + * @generated from field: optional agent.v1.SpanContext span_context = 4; + */ + spanContext?: SpanContext | undefined; - /** - * @generated from oneof agent.v1.KvServerMessage.message - */ - message: - | { - /** - * @generated from field: agent.v1.GetBlobArgs get_blob_args = 2; - */ - value: GetBlobArgs; - case: "getBlobArgs"; - } - | { - /** - * @generated from field: agent.v1.SetBlobArgs set_blob_args = 3; - */ - value: SetBlobArgs; - case: "setBlobArgs"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.KvServerMessage.message + */ + message: { + /** + * @generated from field: agent.v1.GetBlobArgs get_blob_args = 2; + */ + value: GetBlobArgs; + case: "getBlobArgs"; + } | { + /** + * @generated from field: agent.v1.SetBlobArgs set_blob_args = 3; + */ + value: SetBlobArgs; + case: "setBlobArgs"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.KvServerMessage. * Use `create(KvServerMessageSchema)` to create a new message. */ -export const KvServerMessageSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 271); +export const KvServerMessageSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 271); /** * @generated from message agent.v1.KvClientMessage */ export type KvClientMessage = Message<"agent.v1.KvClientMessage"> & { - /** - * @generated from field: uint32 id = 1; - */ - id: number; + /** + * @generated from field: uint32 id = 1; + */ + id: number; - /** - * @generated from oneof agent.v1.KvClientMessage.message - */ - message: - | { - /** - * @generated from field: agent.v1.GetBlobResult get_blob_result = 2; - */ - value: GetBlobResult; - case: "getBlobResult"; - } - | { - /** - * @generated from field: agent.v1.SetBlobResult set_blob_result = 3; - */ - value: SetBlobResult; - case: "setBlobResult"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.KvClientMessage.message + */ + message: { + /** + * @generated from field: agent.v1.GetBlobResult get_blob_result = 2; + */ + value: GetBlobResult; + case: "getBlobResult"; + } | { + /** + * @generated from field: agent.v1.SetBlobResult set_blob_result = 3; + */ + value: SetBlobResult; + case: "setBlobResult"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.KvClientMessage. * Use `create(KvClientMessageSchema)` to create a new message. */ -export const KvClientMessageSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 272); +export const KvClientMessageSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 272); /** * @generated from message agent.v1.LsArgs */ export type LsArgs = Message<"agent.v1.LsArgs"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: repeated string ignore = 2; - */ - ignore: string[]; + /** + * @generated from field: repeated string ignore = 2; + */ + ignore: string[]; - /** - * @generated from field: string tool_call_id = 3; - */ - toolCallId: string; + /** + * @generated from field: string tool_call_id = 3; + */ + toolCallId: string; - /** - * @generated from field: optional agent.v1.SandboxPolicy sandbox_policy = 4; - */ - sandboxPolicy?: SandboxPolicy; + /** + * @generated from field: optional agent.v1.SandboxPolicy sandbox_policy = 4; + */ + sandboxPolicy?: SandboxPolicy | undefined; - /** - * defaults to 5000ms - * - * @generated from field: optional uint32 timeout_ms = 5; - */ - timeoutMs?: number; + /** + * defaults to 5000ms + * + * @generated from field: optional uint32 timeout_ms = 5; + */ + timeoutMs?: number | undefined; }; /** * Describes the message agent.v1.LsArgs. * Use `create(LsArgsSchema)` to create a new message. */ -export const LsArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 273); +export const LsArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 273); /** * @generated from message agent.v1.LsResult */ export type LsResult = Message<"agent.v1.LsResult"> & { - /** - * @generated from oneof agent.v1.LsResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.LsSuccess success = 1; - */ - value: LsSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.LsError error = 2; - */ - value: LsError; - case: "error"; - } - | { - /** - * @generated from field: agent.v1.LsRejected rejected = 3; - */ - value: LsRejected; - case: "rejected"; - } - | { - /** - * @generated from field: agent.v1.LsTimeout timeout = 4; - */ - value: LsTimeout; - case: "timeout"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.LsResult.result + */ + result: { + /** + * @generated from field: agent.v1.LsSuccess success = 1; + */ + value: LsSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.LsError error = 2; + */ + value: LsError; + case: "error"; + } | { + /** + * @generated from field: agent.v1.LsRejected rejected = 3; + */ + value: LsRejected; + case: "rejected"; + } | { + /** + * @generated from field: agent.v1.LsTimeout timeout = 4; + */ + value: LsTimeout; + case: "timeout"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.LsResult. * Use `create(LsResultSchema)` to create a new message. */ -export const LsResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 274); +export const LsResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 274); /** * @generated from message agent.v1.LsSuccess */ export type LsSuccess = Message<"agent.v1.LsSuccess"> & { - /** - * @generated from field: agent.v1.LsDirectoryTreeNode directory_tree_root = 1; - */ - directoryTreeRoot?: LsDirectoryTreeNode; + /** + * @generated from field: agent.v1.LsDirectoryTreeNode directory_tree_root = 1; + */ + directoryTreeRoot?: LsDirectoryTreeNode | undefined; }; /** * Describes the message agent.v1.LsSuccess. * Use `create(LsSuccessSchema)` to create a new message. */ -export const LsSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 275); +export const LsSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 275); /** * @generated from message agent.v1.LsDirectoryTreeNode */ export type LsDirectoryTreeNode = Message<"agent.v1.LsDirectoryTreeNode"> & { - /** - * @generated from field: string abs_path = 1; - */ - absPath: string; + /** + * @generated from field: string abs_path = 1; + */ + absPath: string; - /** - * @generated from field: repeated agent.v1.LsDirectoryTreeNode children_dirs = 2; - */ - childrenDirs: LsDirectoryTreeNode[]; + /** + * @generated from field: repeated agent.v1.LsDirectoryTreeNode children_dirs = 2; + */ + childrenDirs: LsDirectoryTreeNode[]; - /** - * @generated from field: repeated agent.v1.LsDirectoryTreeNode_File children_files = 3; - */ - childrenFiles: LsDirectoryTreeNode_File[]; + /** + * @generated from field: repeated agent.v1.LsDirectoryTreeNode_File children_files = 3; + */ + childrenFiles: LsDirectoryTreeNode_File[]; - /** - * Proto doesn't allow repeated fields to be optional, so in case of empty children arrays, this fields indicates if it happens: `true` - because directory really doesn't have any children `false` - because we stopped traversal before getting to its children - * - * @generated from field: bool children_were_processed = 4; - */ - childrenWereProcessed: boolean; + /** + * Proto doesn't allow repeated fields to be optional, so in case of empty children arrays, this fields indicates if it happens: `true` - because directory really doesn't have any children `false` - because we stopped traversal before getting to its children + * + * @generated from field: bool children_were_processed = 4; + */ + childrenWereProcessed: boolean; - /** - * Count of extensions in the full sub-tree - * - * @generated from field: map full_subtree_extension_counts = 5; - */ - fullSubtreeExtensionCounts: { [key: string]: number }; + /** + * Count of extensions in the full sub-tree + * + * @generated from field: map full_subtree_extension_counts = 5; + */ + fullSubtreeExtensionCounts: { [key: string]: number }; - /** - * @generated from field: int32 num_files = 6; - */ - numFiles: number; + /** + * @generated from field: int32 num_files = 6; + */ + numFiles: number; }; /** * Describes the message agent.v1.LsDirectoryTreeNode. * Use `create(LsDirectoryTreeNodeSchema)` to create a new message. */ -export const LsDirectoryTreeNodeSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 276); +export const LsDirectoryTreeNodeSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 276); /** * @generated from message agent.v1.LsDirectoryTreeNode_File */ export type LsDirectoryTreeNode_File = Message<"agent.v1.LsDirectoryTreeNode_File"> & { - /** - * @generated from field: string name = 1; - */ - name: string; + /** + * @generated from field: string name = 1; + */ + name: string; - /** - * @generated from field: optional agent.v1.TerminalMetadata terminal_metadata = 2; - */ - terminalMetadata?: TerminalMetadata; + /** + * @generated from field: optional agent.v1.TerminalMetadata terminal_metadata = 2; + */ + terminalMetadata?: TerminalMetadata | undefined; }; /** * Describes the message agent.v1.LsDirectoryTreeNode_File. * Use `create(LsDirectoryTreeNode_FileSchema)` to create a new message. */ -export const LsDirectoryTreeNode_FileSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 277); +export const LsDirectoryTreeNode_FileSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 277); /** * @generated from message agent.v1.LsError */ export type LsError = Message<"agent.v1.LsError"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: string error = 2; - */ - error: string; + /** + * @generated from field: string error = 2; + */ + error: string; }; /** * Describes the message agent.v1.LsError. * Use `create(LsErrorSchema)` to create a new message. */ -export const LsErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 278); +export const LsErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 278); /** * @generated from message agent.v1.LsRejected */ export type LsRejected = Message<"agent.v1.LsRejected"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: string reason = 2; - */ - reason: string; + /** + * @generated from field: string reason = 2; + */ + reason: string; }; /** * Describes the message agent.v1.LsRejected. * Use `create(LsRejectedSchema)` to create a new message. */ -export const LsRejectedSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 279); +export const LsRejectedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 279); /** * Returned when ls operation timed out. Contains partial results gathered before timeout. @@ -8210,211 +8522,242 @@ export const LsRejectedSchema: GenMessage = /*@__PURE__*/ messageDes * @generated from message agent.v1.LsTimeout */ export type LsTimeout = Message<"agent.v1.LsTimeout"> & { - /** - * @generated from field: agent.v1.LsDirectoryTreeNode directory_tree_root = 1; - */ - directoryTreeRoot?: LsDirectoryTreeNode; + /** + * @generated from field: agent.v1.LsDirectoryTreeNode directory_tree_root = 1; + */ + directoryTreeRoot?: LsDirectoryTreeNode | undefined; }; /** * Describes the message agent.v1.LsTimeout. * Use `create(LsTimeoutSchema)` to create a new message. */ -export const LsTimeoutSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 280); +export const LsTimeoutSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 280); /** * @generated from message agent.v1.TerminalMetadata */ export type TerminalMetadata = Message<"agent.v1.TerminalMetadata"> & { - /** - * @generated from field: optional string cwd = 1; - */ - cwd?: string; + /** + * @generated from field: optional string cwd = 1; + */ + cwd?: string | undefined; - /** - * @generated from field: repeated agent.v1.TerminalMetadata_Command last_commands = 2; - */ - lastCommands: TerminalMetadata_Command[]; + /** + * @generated from field: repeated agent.v1.TerminalMetadata_Command last_commands = 2; + */ + lastCommands: TerminalMetadata_Command[]; - /** - * @generated from field: optional int64 last_modified_ms = 3; - */ - lastModifiedMs?: bigint; + /** + * @generated from field: optional int64 last_modified_ms = 3; + */ + lastModifiedMs?: bigint | undefined; - /** - * @generated from field: optional agent.v1.TerminalMetadata_Command current_command = 4; - */ - currentCommand?: TerminalMetadata_Command; + /** + * @generated from field: optional agent.v1.TerminalMetadata_Command current_command = 4; + */ + currentCommand?: TerminalMetadata_Command | undefined; }; /** * Describes the message agent.v1.TerminalMetadata. * Use `create(TerminalMetadataSchema)` to create a new message. */ -export const TerminalMetadataSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 281); +export const TerminalMetadataSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 281); /** * @generated from message agent.v1.TerminalMetadata_Command */ export type TerminalMetadata_Command = Message<"agent.v1.TerminalMetadata_Command"> & { - /** - * @generated from field: string command = 1; - */ - command: string; + /** + * @generated from field: string command = 1; + */ + command: string; - /** - * @generated from field: optional int32 exit_code = 2; - */ - exitCode?: number; + /** + * @generated from field: optional int32 exit_code = 2; + */ + exitCode?: number | undefined; - /** - * @generated from field: optional int64 timestamp_ms = 3; - */ - timestampMs?: bigint; + /** + * @generated from field: optional int64 timestamp_ms = 3; + */ + timestampMs?: bigint | undefined; - /** - * @generated from field: optional int64 duration_ms = 4; - */ - durationMs?: bigint; + /** + * @generated from field: optional int64 duration_ms = 4; + */ + durationMs?: bigint | undefined; }; /** * Describes the message agent.v1.TerminalMetadata_Command. * Use `create(TerminalMetadata_CommandSchema)` to create a new message. */ -export const TerminalMetadata_CommandSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 282); +export const TerminalMetadata_CommandSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 282); /** * @generated from message agent.v1.LsToolCall */ export type LsToolCall = Message<"agent.v1.LsToolCall"> & { - /** - * @generated from field: agent.v1.LsArgs args = 1; - */ - args?: LsArgs; + /** + * @generated from field: agent.v1.LsArgs args = 1; + */ + args?: LsArgs | undefined; - /** - * @generated from field: agent.v1.LsResult result = 2; - */ - result?: LsResult; + /** + * @generated from field: agent.v1.LsResult result = 2; + */ + result?: LsResult | undefined; }; /** * Describes the message agent.v1.LsToolCall. * Use `create(LsToolCallSchema)` to create a new message. */ -export const LsToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 283); +export const LsToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 283); /** * @generated from message agent.v1.McpArgs */ export type McpArgs = Message<"agent.v1.McpArgs"> & { - /** - * @generated from field: string name = 1; - */ - name: string; + /** + * @generated from field: string name = 1; + */ + name: string; - /** - * @generated from field: map args = 2; - */ - args: { [key: string]: Uint8Array }; + /** + * @generated from field: map args = 2; + */ + args: { [key: string]: Uint8Array }; - /** - * @generated from field: string tool_call_id = 3; - */ - toolCallId: string; + /** + * @generated from field: string tool_call_id = 3; + */ + toolCallId: string; - /** - * @generated from field: string provider_identifier = 4; - */ - providerIdentifier: string; + /** + * @generated from field: string provider_identifier = 4; + */ + providerIdentifier: string; - /** - * @generated from field: string tool_name = 5; - */ - toolName: string; + /** + * @generated from field: string tool_name = 5; + */ + toolName: string; + + /** + * @generated from field: optional agent.v1.SmartModeApproval smart_mode_approval = 6; + */ + smartModeApproval?: SmartModeApproval | undefined; + + /** + * @generated from field: bool smart_mode_approval_only = 7; + */ + smartModeApprovalOnly: boolean; + + /** + * @generated from field: bool skip_approval = 8; + */ + skipApproval: boolean; + + /** + * @generated from field: string server_identifier = 9; + */ + serverIdentifier: string; }; /** * Describes the message agent.v1.McpArgs. * Use `create(McpArgsSchema)` to create a new message. */ -export const McpArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 284); +export const McpArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 284); /** * @generated from message agent.v1.McpResult */ export type McpResult = Message<"agent.v1.McpResult"> & { - /** - * @generated from oneof agent.v1.McpResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.McpSuccess success = 1; - */ - value: McpSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.McpError error = 2; - */ - value: McpError; - case: "error"; - } - | { - /** - * @generated from field: agent.v1.McpRejected rejected = 3; - */ - value: McpRejected; - case: "rejected"; - } - | { - /** - * @generated from field: agent.v1.McpPermissionDenied permission_denied = 4; - */ - value: McpPermissionDenied; - case: "permissionDenied"; - } - | { - /** - * @generated from field: agent.v1.McpToolNotFound tool_not_found = 5; - */ - value: McpToolNotFound; - case: "toolNotFound"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.McpResult.result + */ + result: { + /** + * @generated from field: agent.v1.McpSuccess success = 1; + */ + value: McpSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.McpError error = 2; + */ + value: McpError; + case: "error"; + } | { + /** + * @generated from field: agent.v1.McpRejected rejected = 3; + */ + value: McpRejected; + case: "rejected"; + } | { + /** + * @generated from field: agent.v1.McpPermissionDenied permission_denied = 4; + */ + value: McpPermissionDenied; + case: "permissionDenied"; + } | { + /** + * @generated from field: agent.v1.McpToolNotFound tool_not_found = 5; + */ + value: McpToolNotFound; + case: "toolNotFound"; + } | { + /** + * @generated from field: agent.v1.McpServerNotFound server_not_found = 6; + */ + value: McpServerNotFound; + case: "serverNotFound"; + } | { + /** + * @generated from field: agent.v1.McpApproved approved = 7; + */ + value: McpApproved; + case: "approved"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.McpResult. * Use `create(McpResultSchema)` to create a new message. */ -export const McpResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 285); +export const McpResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 285); /** * @generated from message agent.v1.McpToolNotFound */ export type McpToolNotFound = Message<"agent.v1.McpToolNotFound"> & { - /** - * @generated from field: string name = 1; - */ - name: string; + /** + * @generated from field: string name = 1; + */ + name: string; - /** - * @generated from field: repeated string available_tools = 2; - */ - availableTools: string[]; + /** + * @generated from field: repeated string available_tools = 2; + */ + availableTools: string[]; }; /** * Describes the message agent.v1.McpToolNotFound. * Use `create(McpToolNotFoundSchema)` to create a new message. */ -export const McpToolNotFoundSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 286); +export const McpToolNotFoundSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 286); /** * Text content item @@ -8422,24 +8765,25 @@ export const McpToolNotFoundSchema: GenMessage = /*@__PURE__*/ * @generated from message agent.v1.McpTextContent */ export type McpTextContent = Message<"agent.v1.McpTextContent"> & { - /** - * @generated from field: string text = 1; - */ - text: string; + /** + * @generated from field: string text = 1; + */ + text: string; - /** - * Optional file location for large outputs - * - * @generated from field: optional agent.v1.OutputLocation output_location = 2; - */ - outputLocation?: OutputLocation; + /** + * Optional file location for large outputs + * + * @generated from field: optional agent.v1.OutputLocation output_location = 2; + */ + outputLocation?: OutputLocation | undefined; }; /** * Describes the message agent.v1.McpTextContent. * Use `create(McpTextContentSchema)` to create a new message. */ -export const McpTextContentSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 287); +export const McpTextContentSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 287); /** * Image content item @@ -8447,26 +8791,27 @@ export const McpTextContentSchema: GenMessage = /*@__PURE__*/ me * @generated from message agent.v1.McpImageContent */ export type McpImageContent = Message<"agent.v1.McpImageContent"> & { - /** - * Raw bytes of the image. In JSON, this will be base64-encoded. - * - * @generated from field: bytes data = 1; - */ - data: Uint8Array; + /** + * Raw bytes of the image. In JSON, this will be base64-encoded. + * + * @generated from field: bytes data = 1; + */ + data: Uint8Array; - /** - * Optional MIME type, e.g. "image/png" - * - * @generated from field: string mime_type = 2; - */ - mimeType: string; + /** + * Optional MIME type, e.g. "image/png" + * + * @generated from field: string mime_type = 2; + */ + mimeType: string; }; /** * Describes the message agent.v1.McpImageContent. * Use `create(McpImageContentSchema)` to create a new message. */ -export const McpImageContentSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 288); +export const McpImageContentSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 288); /** * A single tool result content item: either text or image @@ -8474,34 +8819,30 @@ export const McpImageContentSchema: GenMessage = /*@__PURE__*/ * @generated from message agent.v1.McpToolResultContentItem */ export type McpToolResultContentItem = Message<"agent.v1.McpToolResultContentItem"> & { - /** - * @generated from oneof agent.v1.McpToolResultContentItem.content - */ - content: - | { - /** - * @generated from field: agent.v1.McpTextContent text = 1; - */ - value: McpTextContent; - case: "text"; - } - | { - /** - * @generated from field: agent.v1.McpImageContent image = 2; - */ - value: McpImageContent; - case: "image"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.McpToolResultContentItem.content + */ + content: { + /** + * @generated from field: agent.v1.McpTextContent text = 1; + */ + value: McpTextContent; + case: "text"; + } | { + /** + * @generated from field: agent.v1.McpImageContent image = 2; + */ + value: McpImageContent; + case: "image"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.McpToolResultContentItem. * Use `create(McpToolResultContentItemSchema)` to create a new message. */ -export const McpToolResultContentItemSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 289); +export const McpToolResultContentItemSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 289); /** * Equivalent to the requested McpToolResult TypeScript type @@ -8509,80 +8850,84 @@ export const McpToolResultContentItemSchema: GenMessage & { - /** - * @generated from field: repeated agent.v1.McpToolResultContentItem content = 1; - */ - content: McpToolResultContentItem[]; + /** + * @generated from field: repeated agent.v1.McpToolResultContentItem content = 1; + */ + content: McpToolResultContentItem[]; - /** - * @generated from field: bool is_error = 2; - */ - isError: boolean; + /** + * @generated from field: bool is_error = 2; + */ + isError: boolean; }; /** * Describes the message agent.v1.McpSuccess. * Use `create(McpSuccessSchema)` to create a new message. */ -export const McpSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 290); +export const McpSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 290); /** * @generated from message agent.v1.McpError */ export type McpError = Message<"agent.v1.McpError"> & { - /** - * @generated from field: string error = 1; - */ - error: string; + /** + * @generated from field: string error = 1; + */ + error: string; }; /** * Describes the message agent.v1.McpError. * Use `create(McpErrorSchema)` to create a new message. */ -export const McpErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 291); +export const McpErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 291); /** * @generated from message agent.v1.McpRejected */ export type McpRejected = Message<"agent.v1.McpRejected"> & { - /** - * @generated from field: string reason = 1; - */ - reason: string; + /** + * @generated from field: string reason = 1; + */ + reason: string; - /** - * @generated from field: bool is_readonly = 2; - */ - isReadonly: boolean; + /** + * @generated from field: bool is_readonly = 2; + */ + isReadonly: boolean; }; /** * Describes the message agent.v1.McpRejected. * Use `create(McpRejectedSchema)` to create a new message. */ -export const McpRejectedSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 292); +export const McpRejectedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 292); /** * @generated from message agent.v1.McpPermissionDenied */ export type McpPermissionDenied = Message<"agent.v1.McpPermissionDenied"> & { - /** - * @generated from field: string error = 1; - */ - error: string; + /** + * @generated from field: string error = 1; + */ + error: string; - /** - * @generated from field: bool is_readonly = 2; - */ - isReadonly: boolean; + /** + * @generated from field: bool is_readonly = 2; + */ + isReadonly: boolean; }; /** * Describes the message agent.v1.McpPermissionDenied. * Use `create(McpPermissionDeniedSchema)` to create a new message. */ -export const McpPermissionDeniedSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 293); +export const McpPermissionDeniedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 293); /** * List MCP resources exec args @@ -8590,21 +8935,20 @@ export const McpPermissionDeniedSchema: GenMessage = /*@__P * @generated from message agent.v1.ListMcpResourcesExecArgs */ export type ListMcpResourcesExecArgs = Message<"agent.v1.ListMcpResourcesExecArgs"> & { - /** - * Optional server name to filter resources by - * - * @generated from field: optional string server = 1; - */ - server?: string; + /** + * Optional server name to filter resources by + * + * @generated from field: optional string server = 1; + */ + server?: string | undefined; }; /** * Describes the message agent.v1.ListMcpResourcesExecArgs. * Use `create(ListMcpResourcesExecArgsSchema)` to create a new message. */ -export const ListMcpResourcesExecArgsSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 294); +export const ListMcpResourcesExecArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 294); /** * List MCP resources exec result @@ -8612,142 +8956,133 @@ export const ListMcpResourcesExecArgsSchema: GenMessage & { - /** - * @generated from oneof agent.v1.ListMcpResourcesExecResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.ListMcpResourcesSuccess success = 1; - */ - value: ListMcpResourcesSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.ListMcpResourcesError error = 2; - */ - value: ListMcpResourcesError; - case: "error"; - } - | { - /** - * @generated from field: agent.v1.ListMcpResourcesRejected rejected = 3; - */ - value: ListMcpResourcesRejected; - case: "rejected"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ListMcpResourcesExecResult.result + */ + result: { + /** + * @generated from field: agent.v1.ListMcpResourcesSuccess success = 1; + */ + value: ListMcpResourcesSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.ListMcpResourcesError error = 2; + */ + value: ListMcpResourcesError; + case: "error"; + } | { + /** + * @generated from field: agent.v1.ListMcpResourcesRejected rejected = 3; + */ + value: ListMcpResourcesRejected; + case: "rejected"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ListMcpResourcesExecResult. * Use `create(ListMcpResourcesExecResultSchema)` to create a new message. */ -export const ListMcpResourcesExecResultSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 295); +export const ListMcpResourcesExecResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 295); /** * @generated from message agent.v1.ListMcpResourcesExecResult_McpResource */ export type ListMcpResourcesExecResult_McpResource = Message<"agent.v1.ListMcpResourcesExecResult_McpResource"> & { - /** - * @generated from field: string uri = 1; - */ - uri: string; + /** + * @generated from field: string uri = 1; + */ + uri: string; - /** - * @generated from field: optional string name = 2; - */ - name?: string; + /** + * @generated from field: optional string name = 2; + */ + name?: string | undefined; - /** - * @generated from field: optional string description = 3; - */ - description?: string; + /** + * @generated from field: optional string description = 3; + */ + description?: string | undefined; - /** - * @generated from field: optional string mime_type = 4; - */ - mimeType?: string; + /** + * @generated from field: optional string mime_type = 4; + */ + mimeType?: string | undefined; - /** - * Server name that provides this resource - * - * @generated from field: string server = 5; - */ - server: string; + /** + * Server name that provides this resource + * + * @generated from field: string server = 5; + */ + server: string; - /** - * Additional metadata - * - * @generated from field: map annotations = 6; - */ - annotations: { [key: string]: string }; + /** + * Additional metadata + * + * @generated from field: map annotations = 6; + */ + annotations: { [key: string]: string }; }; /** * Describes the message agent.v1.ListMcpResourcesExecResult_McpResource. * Use `create(ListMcpResourcesExecResult_McpResourceSchema)` to create a new message. */ -export const ListMcpResourcesExecResult_McpResourceSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 296); +export const ListMcpResourcesExecResult_McpResourceSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 296); /** * @generated from message agent.v1.ListMcpResourcesSuccess */ export type ListMcpResourcesSuccess = Message<"agent.v1.ListMcpResourcesSuccess"> & { - /** - * @generated from field: repeated agent.v1.ListMcpResourcesExecResult_McpResource resources = 1; - */ - resources: ListMcpResourcesExecResult_McpResource[]; + /** + * @generated from field: repeated agent.v1.ListMcpResourcesExecResult_McpResource resources = 1; + */ + resources: ListMcpResourcesExecResult_McpResource[]; }; /** * Describes the message agent.v1.ListMcpResourcesSuccess. * Use `create(ListMcpResourcesSuccessSchema)` to create a new message. */ -export const ListMcpResourcesSuccessSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 297); +export const ListMcpResourcesSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 297); /** * @generated from message agent.v1.ListMcpResourcesError */ export type ListMcpResourcesError = Message<"agent.v1.ListMcpResourcesError"> & { - /** - * @generated from field: string error = 1; - */ - error: string; + /** + * @generated from field: string error = 1; + */ + error: string; }; /** * Describes the message agent.v1.ListMcpResourcesError. * Use `create(ListMcpResourcesErrorSchema)` to create a new message. */ -export const ListMcpResourcesErrorSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 298); +export const ListMcpResourcesErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 298); /** * @generated from message agent.v1.ListMcpResourcesRejected */ export type ListMcpResourcesRejected = Message<"agent.v1.ListMcpResourcesRejected"> & { - /** - * @generated from field: string reason = 1; - */ - reason: string; + /** + * @generated from field: string reason = 1; + */ + reason: string; }; /** * Describes the message agent.v1.ListMcpResourcesRejected. * Use `create(ListMcpResourcesRejectedSchema)` to create a new message. */ -export const ListMcpResourcesRejectedSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 299); +export const ListMcpResourcesRejectedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 299); /** * Read MCP resource exec args @@ -8755,35 +9090,44 @@ export const ListMcpResourcesRejectedSchema: GenMessage & { - /** - * Required server name - * - * @generated from field: string server = 1; - */ - server: string; + /** + * Required server name + * + * @generated from field: string server = 1; + */ + server: string; - /** - * Required resource URI - * - * @generated from field: string uri = 2; - */ - uri: string; + /** + * Required resource URI + * + * @generated from field: string uri = 2; + */ + uri: string; - /** - * Optional: when set, the resource will be downloaded to this path relative to the workspace, and the content will not be returned to the model. - * - * @generated from field: optional string download_path = 3; - */ - downloadPath?: string; + /** + * Optional: when set, the resource will be downloaded to this path relative to the workspace, and the content will not be returned to the model. + * + * @generated from field: optional string download_path = 3; + */ + downloadPath?: string | undefined; + + /** + * @generated from field: string tool_call_id = 4; + */ + toolCallId: string; + + /** + * @generated from field: optional agent.v1.SmartModeApproval smart_mode_approval = 5; + */ + smartModeApproval?: SmartModeApproval | undefined; }; /** * Describes the message agent.v1.ReadMcpResourceExecArgs. * Use `create(ReadMcpResourceExecArgsSchema)` to create a new message. */ -export const ReadMcpResourceExecArgsSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 300); +export const ReadMcpResourceExecArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 300); /** * Read MCP resource exec result @@ -8791,229 +9135,230 @@ export const ReadMcpResourceExecArgsSchema: GenMessage * @generated from message agent.v1.ReadMcpResourceExecResult */ export type ReadMcpResourceExecResult = Message<"agent.v1.ReadMcpResourceExecResult"> & { - /** - * @generated from oneof agent.v1.ReadMcpResourceExecResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.ReadMcpResourceSuccess success = 1; - */ - value: ReadMcpResourceSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.ReadMcpResourceError error = 2; - */ - value: ReadMcpResourceError; - case: "error"; - } - | { - /** - * @generated from field: agent.v1.ReadMcpResourceRejected rejected = 3; - */ - value: ReadMcpResourceRejected; - case: "rejected"; - } - | { - /** - * @generated from field: agent.v1.ReadMcpResourceNotFound not_found = 4; - */ - value: ReadMcpResourceNotFound; - case: "notFound"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ReadMcpResourceExecResult.result + */ + result: { + /** + * @generated from field: agent.v1.ReadMcpResourceSuccess success = 1; + */ + value: ReadMcpResourceSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.ReadMcpResourceError error = 2; + */ + value: ReadMcpResourceError; + case: "error"; + } | { + /** + * @generated from field: agent.v1.ReadMcpResourceRejected rejected = 3; + */ + value: ReadMcpResourceRejected; + case: "rejected"; + } | { + /** + * @generated from field: agent.v1.ReadMcpResourceNotFound not_found = 4; + */ + value: ReadMcpResourceNotFound; + case: "notFound"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ReadMcpResourceExecResult. * Use `create(ReadMcpResourceExecResultSchema)` to create a new message. */ -export const ReadMcpResourceExecResultSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 301); +export const ReadMcpResourceExecResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 301); /** * @generated from message agent.v1.ReadMcpResourceSuccess */ export type ReadMcpResourceSuccess = Message<"agent.v1.ReadMcpResourceSuccess"> & { - /** - * @generated from field: string uri = 1; - */ - uri: string; + /** + * @generated from field: string uri = 1; + */ + uri: string; - /** - * @generated from field: optional string name = 2; - */ - name?: string; + /** + * @generated from field: optional string name = 2; + */ + name?: string | undefined; - /** - * @generated from field: optional string description = 3; - */ - description?: string; + /** + * @generated from field: optional string description = 3; + */ + description?: string | undefined; - /** - * @generated from field: optional string mime_type = 4; - */ - mimeType?: string; + /** + * @generated from field: optional string mime_type = 4; + */ + mimeType?: string | undefined; - /** - * Additional metadata - * - * @generated from field: map annotations = 7; - */ - annotations: { [key: string]: string }; + /** + * Additional metadata + * + * @generated from field: map annotations = 7; + */ + annotations: { [key: string]: string }; - /** - * If set, resource was downloaded to this path - * - * @generated from field: optional string download_path = 8; - */ - downloadPath?: string; + /** + * If set, resource was downloaded to this path + * + * @generated from field: optional string download_path = 8; + */ + downloadPath?: string | undefined; - /** - * @generated from oneof agent.v1.ReadMcpResourceSuccess.content - */ - content: - | { - /** - * @generated from field: string text = 5; - */ - value: string; - case: "text"; - } - | { - /** - * @generated from field: bytes blob = 6; - */ - value: Uint8Array; - case: "blob"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ReadMcpResourceSuccess.content + */ + content: { + /** + * @generated from field: string text = 5; + */ + value: string; + case: "text"; + } | { + /** + * @generated from field: bytes blob = 6; + */ + value: Uint8Array; + case: "blob"; + } | { case: undefined; value?: undefined }; + + /** + * @generated from field: optional agent.v1.OutputLocation output_location = 9; + */ + outputLocation?: OutputLocation | undefined; }; /** * Describes the message agent.v1.ReadMcpResourceSuccess. * Use `create(ReadMcpResourceSuccessSchema)` to create a new message. */ -export const ReadMcpResourceSuccessSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 302); +export const ReadMcpResourceSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 302); /** * @generated from message agent.v1.ReadMcpResourceError */ export type ReadMcpResourceError = Message<"agent.v1.ReadMcpResourceError"> & { - /** - * @generated from field: string uri = 1; - */ - uri: string; + /** + * @generated from field: string uri = 1; + */ + uri: string; - /** - * @generated from field: string error = 2; - */ - error: string; + /** + * @generated from field: string error = 2; + */ + error: string; }; /** * Describes the message agent.v1.ReadMcpResourceError. * Use `create(ReadMcpResourceErrorSchema)` to create a new message. */ -export const ReadMcpResourceErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 303); +export const ReadMcpResourceErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 303); /** * @generated from message agent.v1.ReadMcpResourceRejected */ export type ReadMcpResourceRejected = Message<"agent.v1.ReadMcpResourceRejected"> & { - /** - * @generated from field: string uri = 1; - */ - uri: string; + /** + * @generated from field: string uri = 1; + */ + uri: string; - /** - * @generated from field: string reason = 2; - */ - reason: string; + /** + * @generated from field: string reason = 2; + */ + reason: string; }; /** * Describes the message agent.v1.ReadMcpResourceRejected. * Use `create(ReadMcpResourceRejectedSchema)` to create a new message. */ -export const ReadMcpResourceRejectedSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 304); +export const ReadMcpResourceRejectedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 304); /** * @generated from message agent.v1.ReadMcpResourceNotFound */ export type ReadMcpResourceNotFound = Message<"agent.v1.ReadMcpResourceNotFound"> & { - /** - * @generated from field: string uri = 1; - */ - uri: string; + /** + * @generated from field: string uri = 1; + */ + uri: string; }; /** * Describes the message agent.v1.ReadMcpResourceNotFound. * Use `create(ReadMcpResourceNotFoundSchema)` to create a new message. */ -export const ReadMcpResourceNotFoundSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 305); +export const ReadMcpResourceNotFoundSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 305); /** * @generated from message agent.v1.McpToolDefinition */ export type McpToolDefinition = Message<"agent.v1.McpToolDefinition"> & { - /** - * @generated from field: string name = 1; - */ - name: string; + /** + * @generated from field: string name = 1; + */ + name: string; - /** - * @generated from field: string provider_identifier = 4; - */ - providerIdentifier: string; + /** + * @generated from field: string provider_identifier = 4; + */ + providerIdentifier: string; - /** - * @generated from field: string tool_name = 5; - */ - toolName: string; + /** + * @generated from field: string tool_name = 5; + */ + toolName: string; - /** - * @generated from field: string description = 2; - */ - description: string; + /** + * @generated from field: string description = 2; + */ + description: string; - /** - * @generated from field: bytes input_schema = 3; - */ - inputSchema: Uint8Array; + /** + * @generated from field: bytes input_schema = 3; + */ + inputSchema: Uint8Array; + + /** + * @generated from field: optional string input_schema_json = 6; + */ + inputSchemaJson?: string | undefined; }; /** * Describes the message agent.v1.McpToolDefinition. * Use `create(McpToolDefinitionSchema)` to create a new message. */ -export const McpToolDefinitionSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 306); +export const McpToolDefinitionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 306); /** * @generated from message agent.v1.McpTools */ export type McpTools = Message<"agent.v1.McpTools"> & { - /** - * @generated from field: repeated agent.v1.McpToolDefinition mcp_tools = 1; - */ - mcpTools: McpToolDefinition[]; + /** + * @generated from field: repeated agent.v1.McpToolDefinition mcp_tools = 1; + */ + mcpTools: McpToolDefinition[]; }; /** * Describes the message agent.v1.McpTools. * Use `create(McpToolsSchema)` to create a new message. */ -export const McpToolsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 307); +export const McpToolsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 307); /** * Represents MCP-provided instructions from a specific server @@ -9021,775 +9366,828 @@ export const McpToolsSchema: GenMessage = /*@__PURE__*/ messageDesc(fi * @generated from message agent.v1.McpInstructions */ export type McpInstructions = Message<"agent.v1.McpInstructions"> & { - /** - * @generated from field: string server_name = 1; - */ - serverName: string; + /** + * @generated from field: string server_name = 1; + */ + serverName: string; - /** - * @generated from field: string instructions = 2; - */ - instructions: string; + /** + * @generated from field: string instructions = 2; + */ + instructions: string; + + /** + * @generated from field: string server_identifier = 3; + */ + serverIdentifier: string; }; /** * Describes the message agent.v1.McpInstructions. * Use `create(McpInstructionsSchema)` to create a new message. */ -export const McpInstructionsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 308); +export const McpInstructionsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 308); /** * @generated from message agent.v1.McpDescriptor */ export type McpDescriptor = Message<"agent.v1.McpDescriptor"> & { - /** - * Display name of the MCP server associated with this folder. - * - * @generated from field: string server_name = 1; - */ - serverName: string; + /** + * Display name of the MCP server associated with this folder. + * + * @generated from field: string server_name = 1; + */ + serverName: string; - /** - * @generated from field: string server_identifier = 2; - */ - serverIdentifier: string; + /** + * @generated from field: string server_identifier = 2; + */ + serverIdentifier: string; - /** - * Absolute folder path where MCP tool descriptor JSON files are stored. - * - * @generated from field: optional string folder_path = 3; - */ - folderPath?: string; + /** + * Absolute folder path where MCP tool descriptor JSON files are stored. + * + * @generated from field: optional string folder_path = 3; + */ + folderPath?: string | undefined; - /** - * @generated from field: optional string server_use_instructions = 4; - */ - serverUseInstructions?: string; + /** + * @generated from field: optional string server_use_instructions = 4; + */ + serverUseInstructions?: string | undefined; - /** - * @generated from field: repeated agent.v1.McpToolDescriptor tools = 5; - */ - tools: McpToolDescriptor[]; + /** + * @generated from field: repeated agent.v1.McpToolDescriptor tools = 5; + */ + tools: McpToolDescriptor[]; + + /** + * @generated from field: optional string plugin = 7; + */ + plugin?: string | undefined; + + /** + * @generated from field: optional string marketplace = 8; + */ + marketplace?: string | undefined; + + /** + * @generated from field: optional string plugin_db_id = 9; + */ + pluginDbId?: string | undefined; + + /** + * @generated from field: optional string marketplace_id = 10; + */ + marketplaceId?: string | undefined; }; /** * Describes the message agent.v1.McpDescriptor. * Use `create(McpDescriptorSchema)` to create a new message. */ -export const McpDescriptorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 309); +export const McpDescriptorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 309); /** * @generated from message agent.v1.McpToolDescriptor */ export type McpToolDescriptor = Message<"agent.v1.McpToolDescriptor"> & { - /** - * @generated from field: string tool_name = 1; - */ - toolName: string; + /** + * @generated from field: string tool_name = 1; + */ + toolName: string; - /** - * @generated from field: optional string definition_path = 2; - */ - definitionPath?: string; + /** + * @generated from field: optional string definition_path = 2; + */ + definitionPath?: string | undefined; + + /** + * @generated from field: optional string description = 3; + */ + description?: string | undefined; + + /** + * @generated from field: optional string input_schema_json = 5; + */ + inputSchemaJson?: string | undefined; }; /** * Describes the message agent.v1.McpToolDescriptor. * Use `create(McpToolDescriptorSchema)` to create a new message. */ -export const McpToolDescriptorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 310); +export const McpToolDescriptorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 310); /** * @generated from message agent.v1.McpFileSystemOptions */ export type McpFileSystemOptions = Message<"agent.v1.McpFileSystemOptions"> & { - /** - * @generated from field: bool enabled = 1; - */ - enabled: boolean; + /** + * @generated from field: bool enabled = 1; + */ + enabled: boolean; - /** - * @generated from field: string workspace_project_dir = 2; - */ - workspaceProjectDir: string; + /** + * @generated from field: string workspace_project_dir = 2; + */ + workspaceProjectDir: string; - /** - * @generated from field: repeated agent.v1.McpDescriptor mcp_descriptors = 3; - */ - mcpDescriptors: McpDescriptor[]; + /** + * @generated from field: repeated agent.v1.McpDescriptor mcp_descriptors = 3; + */ + mcpDescriptors: McpDescriptor[]; }; /** * Describes the message agent.v1.McpFileSystemOptions. * Use `create(McpFileSystemOptionsSchema)` to create a new message. */ -export const McpFileSystemOptionsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 311); +export const McpFileSystemOptionsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 311); /** * @generated from message agent.v1.ReadArgs */ export type ReadArgs = Message<"agent.v1.ReadArgs"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: string tool_call_id = 2; - */ - toolCallId: string; + /** + * @generated from field: string tool_call_id = 2; + */ + toolCallId: string; + + /** + * @generated from field: optional int32 offset = 4; + */ + offset?: number | undefined; + + /** + * @generated from field: optional uint32 limit = 5; + */ + limit?: number | undefined; + + /** + * @generated from field: optional string encoding_hint = 6; + */ + encodingHint?: string | undefined; }; /** * Describes the message agent.v1.ReadArgs. * Use `create(ReadArgsSchema)` to create a new message. */ -export const ReadArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 312); +export const ReadArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 312); /** * @generated from message agent.v1.ReadResult */ export type ReadResult = Message<"agent.v1.ReadResult"> & { - /** - * @generated from oneof agent.v1.ReadResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.ReadSuccess success = 1; - */ - value: ReadSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.ReadError error = 2; - */ - value: ReadError; - case: "error"; - } - | { - /** - * @generated from field: agent.v1.ReadRejected rejected = 3; - */ - value: ReadRejected; - case: "rejected"; - } - | { - /** - * @generated from field: agent.v1.ReadFileNotFound file_not_found = 4; - */ - value: ReadFileNotFound; - case: "fileNotFound"; - } - | { - /** - * @generated from field: agent.v1.ReadPermissionDenied permission_denied = 5; - */ - value: ReadPermissionDenied; - case: "permissionDenied"; - } - | { - /** - * @generated from field: agent.v1.ReadInvalidFile invalid_file = 6; - */ - value: ReadInvalidFile; - case: "invalidFile"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ReadResult.result + */ + result: { + /** + * @generated from field: agent.v1.ReadSuccess success = 1; + */ + value: ReadSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.ReadError error = 2; + */ + value: ReadError; + case: "error"; + } | { + /** + * @generated from field: agent.v1.ReadRejected rejected = 3; + */ + value: ReadRejected; + case: "rejected"; + } | { + /** + * @generated from field: agent.v1.ReadFileNotFound file_not_found = 4; + */ + value: ReadFileNotFound; + case: "fileNotFound"; + } | { + /** + * @generated from field: agent.v1.ReadPermissionDenied permission_denied = 5; + */ + value: ReadPermissionDenied; + case: "permissionDenied"; + } | { + /** + * @generated from field: agent.v1.ReadInvalidFile invalid_file = 6; + */ + value: ReadInvalidFile; + case: "invalidFile"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ReadResult. * Use `create(ReadResultSchema)` to create a new message. */ -export const ReadResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 313); +export const ReadResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 313); /** * @generated from message agent.v1.ReadSuccess */ export type ReadSuccess = Message<"agent.v1.ReadSuccess"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: int32 total_lines = 3; - */ - totalLines: number; + /** + * @generated from field: int32 total_lines = 3; + */ + totalLines: number; - /** - * @generated from field: int64 file_size = 4; - */ - fileSize: bigint; + /** + * @generated from field: int64 file_size = 4; + */ + fileSize: bigint; - /** - * true if the content was truncated due to size limits - * - * @generated from field: bool truncated = 6; - */ - truncated: boolean; + /** + * true if the content was truncated due to size limits + * + * @generated from field: bool truncated = 6; + */ + truncated: boolean; - /** - * Returns blob ID if the output was stored in the blob store. If provided, the output is stored separately from the rest of the tool result, and since it's already in the blob store, it need not be sent back to the client -- reducing bandwidth. - * - * @generated from field: optional bytes output_blob_id = 7; - */ - outputBlobId?: Uint8Array; + /** + * Returns blob ID if the output was stored in the blob store. If provided, the output is stored separately from the rest of the tool result, and since it's already in the blob store, it need not be sent back to the client -- reducing bandwidth. + * + * @generated from field: optional bytes output_blob_id = 7; + */ + outputBlobId?: Uint8Array | undefined; - /** - * @generated from oneof agent.v1.ReadSuccess.output - */ - output: - | { - /** - * @generated from field: string content = 2; - */ - value: string; - case: "content"; - } - | { - /** - * @generated from field: bytes data = 5; - */ - value: Uint8Array; - case: "data"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ReadSuccess.output + */ + output: { + /** + * @generated from field: string content = 2; + */ + value: string; + case: "content"; + } | { + /** + * @generated from field: bytes data = 5; + */ + value: Uint8Array; + case: "data"; + } | { case: undefined; value?: undefined }; + + /** + * @generated from field: bool range_applied = 8; + */ + rangeApplied: boolean; }; /** * Describes the message agent.v1.ReadSuccess. * Use `create(ReadSuccessSchema)` to create a new message. */ -export const ReadSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 314); +export const ReadSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 314); /** * @generated from message agent.v1.ReadError */ export type ReadError = Message<"agent.v1.ReadError"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: string error = 2; - */ - error: string; + /** + * @generated from field: string error = 2; + */ + error: string; }; /** * Describes the message agent.v1.ReadError. * Use `create(ReadErrorSchema)` to create a new message. */ -export const ReadErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 315); +export const ReadErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 315); /** * @generated from message agent.v1.ReadRejected */ export type ReadRejected = Message<"agent.v1.ReadRejected"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: string reason = 2; - */ - reason: string; + /** + * @generated from field: string reason = 2; + */ + reason: string; }; /** * Describes the message agent.v1.ReadRejected. * Use `create(ReadRejectedSchema)` to create a new message. */ -export const ReadRejectedSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 316); +export const ReadRejectedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 316); /** * @generated from message agent.v1.ReadFileNotFound */ export type ReadFileNotFound = Message<"agent.v1.ReadFileNotFound"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; }; /** * Describes the message agent.v1.ReadFileNotFound. * Use `create(ReadFileNotFoundSchema)` to create a new message. */ -export const ReadFileNotFoundSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 317); +export const ReadFileNotFoundSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 317); /** * @generated from message agent.v1.ReadPermissionDenied */ export type ReadPermissionDenied = Message<"agent.v1.ReadPermissionDenied"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; }; /** * Describes the message agent.v1.ReadPermissionDenied. * Use `create(ReadPermissionDeniedSchema)` to create a new message. */ -export const ReadPermissionDeniedSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 318); +export const ReadPermissionDeniedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 318); /** * @generated from message agent.v1.ReadInvalidFile */ export type ReadInvalidFile = Message<"agent.v1.ReadInvalidFile"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * e.g., "Path is a directory, not a file" - * - * @generated from field: string reason = 2; - */ - reason: string; + /** + * e.g., "Path is a directory, not a file" + * + * @generated from field: string reason = 2; + */ + reason: string; }; /** * Describes the message agent.v1.ReadInvalidFile. * Use `create(ReadInvalidFileSchema)` to create a new message. */ -export const ReadInvalidFileSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 319); +export const ReadInvalidFileSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 319); /** * @generated from message agent.v1.ReadToolCall */ export type ReadToolCall = Message<"agent.v1.ReadToolCall"> & { - /** - * @generated from field: agent.v1.ReadToolArgs args = 1; - */ - args?: ReadToolArgs; + /** + * @generated from field: agent.v1.ReadToolArgs args = 1; + */ + args?: ReadToolArgs | undefined; - /** - * @generated from field: agent.v1.ReadToolResult result = 2; - */ - result?: ReadToolResult; + /** + * @generated from field: agent.v1.ReadToolResult result = 2; + */ + result?: ReadToolResult | undefined; }; /** * Describes the message agent.v1.ReadToolCall. * Use `create(ReadToolCallSchema)` to create a new message. */ -export const ReadToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 320); +export const ReadToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 320); /** * @generated from message agent.v1.ReadToolArgs */ export type ReadToolArgs = Message<"agent.v1.ReadToolArgs"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: optional int32 offset = 2; - */ - offset?: number; + /** + * @generated from field: optional int32 offset = 2; + */ + offset?: number | undefined; - /** - * @generated from field: optional int32 limit = 3; - */ - limit?: number; + /** + * @generated from field: optional int32 limit = 3; + */ + limit?: number | undefined; }; /** * Describes the message agent.v1.ReadToolArgs. * Use `create(ReadToolArgsSchema)` to create a new message. */ -export const ReadToolArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 321); +export const ReadToolArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 321); /** * @generated from message agent.v1.ReadToolResult */ export type ReadToolResult = Message<"agent.v1.ReadToolResult"> & { - /** - * @generated from oneof agent.v1.ReadToolResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.ReadToolSuccess success = 1; - */ - value: ReadToolSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.ReadToolError error = 2; - */ - value: ReadToolError; - case: "error"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ReadToolResult.result + */ + result: { + /** + * @generated from field: agent.v1.ReadToolSuccess success = 1; + */ + value: ReadToolSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.ReadToolError error = 2; + */ + value: ReadToolError; + case: "error"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ReadToolResult. * Use `create(ReadToolResultSchema)` to create a new message. */ -export const ReadToolResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 322); +export const ReadToolResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 322); /** * @generated from message agent.v1.ReadRange */ export type ReadRange = Message<"agent.v1.ReadRange"> & { - /** - * @generated from field: uint32 start_line = 1; - */ - startLine: number; + /** + * @generated from field: uint32 start_line = 1; + */ + startLine: number; - /** - * @generated from field: uint32 end_line = 2; - */ - endLine: number; + /** + * @generated from field: uint32 end_line = 2; + */ + endLine: number; }; /** * Describes the message agent.v1.ReadRange. * Use `create(ReadRangeSchema)` to create a new message. */ -export const ReadRangeSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 323); +export const ReadRangeSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 323); /** * @generated from message agent.v1.ReadToolSuccess */ export type ReadToolSuccess = Message<"agent.v1.ReadToolSuccess"> & { - /** - * @generated from field: bool is_empty = 2; - */ - isEmpty: boolean; + /** + * @generated from field: bool is_empty = 2; + */ + isEmpty: boolean; - /** - * @generated from field: bool exceeded_limit = 3; - */ - exceededLimit: boolean; + /** + * @generated from field: bool exceeded_limit = 3; + */ + exceededLimit: boolean; - /** - * @generated from field: uint32 total_lines = 4; - */ - totalLines: number; + /** + * @generated from field: uint32 total_lines = 4; + */ + totalLines: number; - /** - * @generated from field: uint32 file_size = 5; - */ - fileSize: number; + /** + * @generated from field: uint32 file_size = 5; + */ + fileSize: number; - /** - * @generated from field: string path = 7; - */ - path: string; + /** + * @generated from field: string path = 7; + */ + path: string; - /** - * @generated from field: optional agent.v1.ReadRange read_range = 8; - */ - readRange?: ReadRange; + /** + * @generated from field: optional agent.v1.ReadRange read_range = 8; + */ + readRange?: ReadRange | undefined; - /** - * @generated from oneof agent.v1.ReadToolSuccess.output - */ - output: - | { - /** - * @generated from field: string content = 1; - */ - value: string; - case: "content"; - } - | { - /** - * @generated from field: bytes data = 6; - */ - value: Uint8Array; - case: "data"; - } - | { - /** - * @generated from field: bytes data_blob_id = 9; - */ - value: Uint8Array; - case: "dataBlobId"; - } - | { - /** - * @generated from field: bytes content_blob_id = 10; - */ - value: Uint8Array; - case: "contentBlobId"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ReadToolSuccess.output + */ + output: { + /** + * @generated from field: string content = 1; + */ + value: string; + case: "content"; + } | { + /** + * @generated from field: bytes data = 6; + */ + value: Uint8Array; + case: "data"; + } | { + /** + * @generated from field: bytes data_blob_id = 9; + */ + value: Uint8Array; + case: "dataBlobId"; + } | { + /** + * @generated from field: bytes content_blob_id = 10; + */ + value: Uint8Array; + case: "contentBlobId"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ReadToolSuccess. * Use `create(ReadToolSuccessSchema)` to create a new message. */ -export const ReadToolSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 324); +export const ReadToolSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 324); /** * @generated from message agent.v1.ReadToolError */ export type ReadToolError = Message<"agent.v1.ReadToolError"> & { - /** - * @generated from field: string error_message = 1; - */ - errorMessage: string; + /** + * @generated from field: string error_message = 1; + */ + errorMessage: string; }; /** * Describes the message agent.v1.ReadToolError. * Use `create(ReadToolErrorSchema)` to create a new message. */ -export const ReadToolErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 325); +export const ReadToolErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 325); /** * @generated from message agent.v1.RecordScreenArgs */ export type RecordScreenArgs = Message<"agent.v1.RecordScreenArgs"> & { - /** - * @generated from field: int32 mode = 1; - */ - mode: number; + /** + * @generated from field: int32 mode = 1; + */ + mode: number; - /** - * @generated from field: string tool_call_id = 2; - */ - toolCallId: string; + /** + * @generated from field: string tool_call_id = 2; + */ + toolCallId: string; - /** - * Custom filename for SAVE_RECORDING mode - * - * @generated from field: optional string save_as_filename = 3; - */ - saveAsFilename?: string; + /** + * Custom filename for SAVE_RECORDING mode + * + * @generated from field: optional string save_as_filename = 3; + */ + saveAsFilename?: string | undefined; }; /** * Describes the message agent.v1.RecordScreenArgs. * Use `create(RecordScreenArgsSchema)` to create a new message. */ -export const RecordScreenArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 326); +export const RecordScreenArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 326); /** * @generated from message agent.v1.RecordScreenResult */ export type RecordScreenResult = Message<"agent.v1.RecordScreenResult"> & { - /** - * @generated from oneof agent.v1.RecordScreenResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.RecordScreenStartSuccess start_success = 1; - */ - value: RecordScreenStartSuccess; - case: "startSuccess"; - } - | { - /** - * @generated from field: agent.v1.RecordScreenSaveSuccess save_success = 2; - */ - value: RecordScreenSaveSuccess; - case: "saveSuccess"; - } - | { - /** - * @generated from field: agent.v1.RecordScreenDiscardSuccess discard_success = 3; - */ - value: RecordScreenDiscardSuccess; - case: "discardSuccess"; - } - | { - /** - * @generated from field: agent.v1.RecordScreenFailure failure = 4; - */ - value: RecordScreenFailure; - case: "failure"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.RecordScreenResult.result + */ + result: { + /** + * @generated from field: agent.v1.RecordScreenStartSuccess start_success = 1; + */ + value: RecordScreenStartSuccess; + case: "startSuccess"; + } | { + /** + * @generated from field: agent.v1.RecordScreenSaveSuccess save_success = 2; + */ + value: RecordScreenSaveSuccess; + case: "saveSuccess"; + } | { + /** + * @generated from field: agent.v1.RecordScreenDiscardSuccess discard_success = 3; + */ + value: RecordScreenDiscardSuccess; + case: "discardSuccess"; + } | { + /** + * @generated from field: agent.v1.RecordScreenFailure failure = 4; + */ + value: RecordScreenFailure; + case: "failure"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.RecordScreenResult. * Use `create(RecordScreenResultSchema)` to create a new message. */ -export const RecordScreenResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 327); +export const RecordScreenResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 327); /** * @generated from message agent.v1.RecordScreenStartSuccess */ export type RecordScreenStartSuccess = Message<"agent.v1.RecordScreenStartSuccess"> & { - /** - * True if a prior recording was cancelled, false otherwise - * - * @generated from field: bool was_prior_recording_cancelled = 1; - */ - wasPriorRecordingCancelled: boolean; + /** + * True if a prior recording was cancelled, false otherwise + * + * @generated from field: bool was_prior_recording_cancelled = 1; + */ + wasPriorRecordingCancelled: boolean; - /** - * True if save_as_filename arg was passed to start tool and ignored - * - * @generated from field: bool was_save_as_filename_ignored = 2; - */ - wasSaveAsFilenameIgnored: boolean; + /** + * True if save_as_filename arg was passed to start tool and ignored + * + * @generated from field: bool was_save_as_filename_ignored = 2; + */ + wasSaveAsFilenameIgnored: boolean; }; /** * Describes the message agent.v1.RecordScreenStartSuccess. * Use `create(RecordScreenStartSuccessSchema)` to create a new message. */ -export const RecordScreenStartSuccessSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 328); +export const RecordScreenStartSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 328); /** * @generated from message agent.v1.RecordScreenSaveSuccess */ export type RecordScreenSaveSuccess = Message<"agent.v1.RecordScreenSaveSuccess"> & { - /** - * Path to the saved recording file - * - * @generated from field: string path = 1; - */ - path: string; + /** + * Path to the saved recording file + * + * @generated from field: string path = 1; + */ + path: string; - /** - * Duration of the recording in milliseconds - * - * @generated from field: int64 recording_duration_ms = 2; - */ - recordingDurationMs: bigint; + /** + * Duration of the recording in milliseconds + * + * @generated from field: int64 recording_duration_ms = 2; + */ + recordingDurationMs: bigint; - /** - * Set if save_as_filename was invalid and default path was used instead - * - * @generated from field: optional int32 requested_file_path_rejected_reason = 3; - */ - requestedFilePathRejectedReason?: number; + /** + * Set if save_as_filename was invalid and default path was used instead + * + * @generated from field: optional int32 requested_file_path_rejected_reason = 3; + */ + requestedFilePathRejectedReason?: number | undefined; }; /** * Describes the message agent.v1.RecordScreenSaveSuccess. * Use `create(RecordScreenSaveSuccessSchema)` to create a new message. */ -export const RecordScreenSaveSuccessSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 329); +export const RecordScreenSaveSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 329); /** * Empty message - recording discarded successfully * * @generated from message agent.v1.RecordScreenDiscardSuccess */ -export type RecordScreenDiscardSuccess = Message<"agent.v1.RecordScreenDiscardSuccess"> & {}; +export type RecordScreenDiscardSuccess = Message<"agent.v1.RecordScreenDiscardSuccess"> & { +}; /** * Describes the message agent.v1.RecordScreenDiscardSuccess. * Use `create(RecordScreenDiscardSuccessSchema)` to create a new message. */ -export const RecordScreenDiscardSuccessSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 330); +export const RecordScreenDiscardSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 330); /** * @generated from message agent.v1.RecordScreenFailure */ export type RecordScreenFailure = Message<"agent.v1.RecordScreenFailure"> & { - /** - * Error message - * - * @generated from field: string error = 1; - */ - error: string; + /** + * Error message + * + * @generated from field: string error = 1; + */ + error: string; }; /** * Describes the message agent.v1.RecordScreenFailure. * Use `create(RecordScreenFailureSchema)` to create a new message. */ -export const RecordScreenFailureSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 331); +export const RecordScreenFailureSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 331); /** * @generated from message agent.v1.CursorPackagePrompt */ export type CursorPackagePrompt = Message<"agent.v1.CursorPackagePrompt"> & { - /** - * @generated from field: string name = 1; - */ - name: string; + /** + * @generated from field: string name = 1; + */ + name: string; - /** - * @generated from field: string file_path = 2; - */ - filePath: string; + /** + * @generated from field: string file_path = 2; + */ + filePath: string; }; /** * Describes the message agent.v1.CursorPackagePrompt. * Use `create(CursorPackagePromptSchema)` to create a new message. */ -export const CursorPackagePromptSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 332); +export const CursorPackagePromptSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 332); /** * @generated from message agent.v1.CursorPackage */ export type CursorPackage = Message<"agent.v1.CursorPackage"> & { - /** - * @generated from field: string name = 1; - */ - name: string; + /** + * @generated from field: string name = 1; + */ + name: string; - /** - * @generated from field: string description = 2; - */ - description: string; + /** + * @generated from field: string description = 2; + */ + description: string; - /** - * @generated from field: string folder_path = 3; - */ - folderPath: string; + /** + * @generated from field: string folder_path = 3; + */ + folderPath: string; - /** - * @generated from field: bool enabled = 4; - */ - enabled: boolean; + /** + * @generated from field: bool enabled = 4; + */ + enabled: boolean; - /** - * @generated from field: optional string parse_error = 5; - */ - parseError?: string; + /** + * @generated from field: optional string parse_error = 5; + */ + parseError?: string | undefined; - /** - * @generated from field: repeated agent.v1.CursorPackagePrompt prompts = 6; - */ - prompts: CursorPackagePrompt[]; + /** + * @generated from field: repeated agent.v1.CursorPackagePrompt prompts = 6; + */ + prompts: CursorPackagePrompt[]; - /** - * @generated from field: string readme_file_path = 7; - */ - readmeFilePath: string; + /** + * @generated from field: string readme_file_path = 7; + */ + readmeFilePath: string; - /** - * @generated from field: int32 package_type = 8; - */ - packageType: number; + /** + * @generated from field: int32 package_type = 8; + */ + packageType: number; }; /** * Describes the message agent.v1.CursorPackage. * Use `create(CursorPackageSchema)` to create a new message. */ -export const CursorPackageSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 333); +export const CursorPackageSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 333); /** * TODO: you should be able to override / configure this list in your .vscode settings not exactly sure what that should look like... but i guess you should be able to specify an override URL because we use URLs for identifying repos and maybe you should be able to specify additional buckets too... like in the jane street case: i guess jane street should have some default buckets @@ -9797,187 +10195,203 @@ export const CursorPackageSchema: GenMessage = /*@__PURE__*/ mess * @generated from message agent.v1.RepositoryIndexingInfo */ export type RepositoryIndexingInfo = Message<"agent.v1.RepositoryIndexingInfo"> & { - /** - * the relative path in the current workspace this is useful for locating the repo and identifying what repo a given file is in this should be unique for different repositories (I think) - * - * @generated from field: string relative_workspace_path = 1; - */ - relativeWorkspacePath: string; + /** + * the relative path in the current workspace this is useful for locating the repo and identifying what repo a given file is in this should be unique for different repositories (I think) + * + * @generated from field: string relative_workspace_path = 1; + */ + relativeWorkspacePath: string; - /** - * a git repo may have multiple remotes at the server we choose the remote (either origin, or the one we have embedded, or something else) invariant: len(remote_urls) == len(remote_names) - * - * @generated from field: repeated string remote_urls = 2; - */ - remoteUrls: string[]; + /** + * a git repo may have multiple remotes at the server we choose the remote (either origin, or the one we have embedded, or something else) invariant: len(remote_urls) == len(remote_names) + * + * @generated from field: repeated string remote_urls = 2; + */ + remoteUrls: string[]; - /** - * @generated from field: repeated string remote_names = 3; - */ - remoteNames: string[]; + /** + * @generated from field: repeated string remote_names = 3; + */ + remoteNames: string[]; - /** - * @generated from field: string repo_name = 4; - */ - repoName: string; + /** + * @generated from field: string repo_name = 4; + */ + repoName: string; - /** - * @generated from field: string repo_owner = 5; - */ - repoOwner: string; + /** + * @generated from field: string repo_owner = 5; + */ + repoOwner: string; - /** - * @generated from field: bool is_tracked = 6; - */ - isTracked: boolean; + /** + * @generated from field: bool is_tracked = 6; + */ + isTracked: boolean; - /** - * If this is local - * - * @generated from field: bool is_local = 7; - */ - isLocal: boolean; + /** + * If this is local + * + * @generated from field: bool is_local = 7; + */ + isLocal: boolean; - /** - * the orthogonal transform seed if sent from the client! if the client sends up the transform seed then we use that for the orthogonal transform instead of the value stored in the database - * - * @generated from field: optional double orthogonal_transform_seed = 8; - */ - orthogonalTransformSeed?: number; + /** + * the orthogonal transform seed if sent from the client! if the client sends up the transform seed then we use that for the orthogonal transform instead of the value stored in the database + * + * @generated from field: optional double orthogonal_transform_seed = 8; + */ + orthogonalTransformSeed?: number | undefined; - /** - * The encrypted workspace uri for the repository. - * - * @generated from field: string workspace_uri = 9; - */ - workspaceUri: string; + /** + * The encrypted workspace uri for the repository. + * + * @generated from field: string workspace_uri = 9; + */ + workspaceUri: string; - /** - * The encryption key for partial paths - * - * @generated from field: string path_encryption_key = 10; - */ - pathEncryptionKey: string; + /** + * The encryption key for partial paths + * + * @generated from field: string path_encryption_key = 10; + */ + pathEncryptionKey: string; }; /** * Describes the message agent.v1.RepositoryIndexingInfo. * Use `create(RepositoryIndexingInfoSchema)` to create a new message. */ -export const RepositoryIndexingInfoSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 334); +export const RepositoryIndexingInfoSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 334); /** * @generated from message agent.v1.RequestContextArgs */ export type RequestContextArgs = Message<"agent.v1.RequestContextArgs"> & { - /** - * @generated from field: optional string notes_session_id = 2; - */ - notesSessionId?: string; + /** + * @generated from field: optional string notes_session_id = 2; + */ + notesSessionId?: string | undefined; - /** - * @generated from field: optional string workspace_id = 3; - */ - workspaceId?: string; + /** + * @generated from field: optional string workspace_id = 3; + */ + workspaceId?: string | undefined; + + /** + * @generated from field: optional string read_only_pinned_tree_sha = 4; + */ + readOnlyPinnedTreeSha?: string | undefined; + + /** + * @generated from field: optional string read_only_plugin_cache_root = 5; + */ + readOnlyPluginCacheRoot?: string | undefined; + + /** + * @generated from field: optional bool use_cached = 7; + */ + useCached?: boolean | undefined; }; /** * Describes the message agent.v1.RequestContextArgs. * Use `create(RequestContextArgsSchema)` to create a new message. */ -export const RequestContextArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 335); +export const RequestContextArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 335); /** * @generated from message agent.v1.RequestContextResult */ export type RequestContextResult = Message<"agent.v1.RequestContextResult"> & { - /** - * @generated from oneof agent.v1.RequestContextResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.RequestContextSuccess success = 1; - */ - value: RequestContextSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.RequestContextError error = 2; - */ - value: RequestContextError; - case: "error"; - } - | { - /** - * @generated from field: agent.v1.RequestContextRejected rejected = 3; - */ - value: RequestContextRejected; - case: "rejected"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.RequestContextResult.result + */ + result: { + /** + * @generated from field: agent.v1.RequestContextSuccess success = 1; + */ + value: RequestContextSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.RequestContextError error = 2; + */ + value: RequestContextError; + case: "error"; + } | { + /** + * @generated from field: agent.v1.RequestContextRejected rejected = 3; + */ + value: RequestContextRejected; + case: "rejected"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.RequestContextResult. * Use `create(RequestContextResultSchema)` to create a new message. */ -export const RequestContextResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 336); +export const RequestContextResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 336); /** * @generated from message agent.v1.RequestContextSuccess */ export type RequestContextSuccess = Message<"agent.v1.RequestContextSuccess"> & { - /** - * @generated from field: agent.v1.RequestContext request_context = 1; - */ - requestContext?: RequestContext; + /** + * @generated from field: agent.v1.RequestContext request_context = 1; + */ + requestContext?: RequestContext | undefined; + + /** + * @generated from field: optional bool served_from_disk_cache = 2; + */ + servedFromDiskCache?: boolean | undefined; }; /** * Describes the message agent.v1.RequestContextSuccess. * Use `create(RequestContextSuccessSchema)` to create a new message. */ -export const RequestContextSuccessSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 337); +export const RequestContextSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 337); /** * @generated from message agent.v1.RequestContextError */ export type RequestContextError = Message<"agent.v1.RequestContextError"> & { - /** - * @generated from field: string error = 1; - */ - error: string; + /** + * @generated from field: string error = 1; + */ + error: string; }; /** * Describes the message agent.v1.RequestContextError. * Use `create(RequestContextErrorSchema)` to create a new message. */ -export const RequestContextErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 338); +export const RequestContextErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 338); /** * @generated from message agent.v1.RequestContextRejected */ export type RequestContextRejected = Message<"agent.v1.RequestContextRejected"> & { - /** - * @generated from field: string reason = 1; - */ - reason: string; + /** + * @generated from field: string reason = 1; + */ + reason: string; }; /** * Describes the message agent.v1.RequestContextRejected. * Use `create(RequestContextRejectedSchema)` to create a new message. */ -export const RequestContextRejectedSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 339); +export const RequestContextRejectedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 339); /** * same as SelectedImage, but with the data field is the full image data @@ -9985,63 +10399,65 @@ export const RequestContextRejectedSchema: GenMessage = * @generated from message agent.v1.ImageProto */ export type ImageProto = Message<"agent.v1.ImageProto"> & { - /** - * @generated from field: bytes data = 1; - */ - data: Uint8Array; + /** + * @generated from field: bytes data = 1; + */ + data: Uint8Array; - /** - * @generated from field: string uuid = 2; - */ - uuid: string; + /** + * @generated from field: string uuid = 2; + */ + uuid: string; - /** - * @generated from field: string path = 3; - */ - path: string; + /** + * @generated from field: string path = 3; + */ + path: string; - /** - * @generated from field: agent.v1.ImageProto_Dimension dimension = 4; - */ - dimension?: ImageProto_Dimension; + /** + * @generated from field: agent.v1.ImageProto_Dimension dimension = 4; + */ + dimension?: ImageProto_Dimension | undefined; - /** - * @generated from field: optional string task_specific_description = 6; - */ - taskSpecificDescription?: string; + /** + * @generated from field: optional string task_specific_description = 6; + */ + taskSpecificDescription?: string | undefined; - /** - * @generated from field: string mime_type = 7; - */ - mimeType: string; + /** + * @generated from field: string mime_type = 7; + */ + mimeType: string; }; /** * Describes the message agent.v1.ImageProto. * Use `create(ImageProtoSchema)` to create a new message. */ -export const ImageProtoSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 340); +export const ImageProtoSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 340); /** * @generated from message agent.v1.ImageProto_Dimension */ export type ImageProto_Dimension = Message<"agent.v1.ImageProto_Dimension"> & { - /** - * @generated from field: int32 width = 1; - */ - width: number; + /** + * @generated from field: int32 width = 1; + */ + width: number; - /** - * @generated from field: int32 height = 2; - */ - height: number; + /** + * @generated from field: int32 height = 2; + */ + height: number; }; /** * Describes the message agent.v1.ImageProto_Dimension. * Use `create(ImageProto_DimensionSchema)` to create a new message. */ -export const ImageProto_DimensionSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 341); +export const ImageProto_DimensionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 341); /** * Git repository information for a workspace @@ -10049,32 +10465,33 @@ export const ImageProto_DimensionSchema: GenMessage = /*@_ * @generated from message agent.v1.GitRepoInfo */ export type GitRepoInfo = Message<"agent.v1.GitRepoInfo"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: string status = 2; - */ - status: string; + /** + * @generated from field: string status = 2; + */ + status: string; - /** - * @generated from field: string branch_name = 3; - */ - branchName: string; + /** + * @generated from field: string branch_name = 3; + */ + branchName: string; - /** - * @generated from field: optional string remote_url = 4; - */ - remoteUrl?: string; + /** + * @generated from field: optional string remote_url = 4; + */ + remoteUrl?: string | undefined; }; /** * Describes the message agent.v1.GitRepoInfo. * Use `create(GitRepoInfoSchema)` to create a new message. */ -export const GitRepoInfoSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 342); +export const GitRepoInfoSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 342); /** * Environment details for system prompt/context @@ -10082,364 +10499,367 @@ export const GitRepoInfoSchema: GenMessage = /*@__PURE__*/ messageD * @generated from message agent.v1.RequestContextEnv */ export type RequestContextEnv = Message<"agent.v1.RequestContextEnv"> & { - /** - * @generated from field: string os_version = 1; - */ - osVersion: string; + /** + * @generated from field: string os_version = 1; + */ + osVersion: string; - /** - * @generated from field: repeated string workspace_paths = 2; - */ - workspacePaths: string[]; + /** + * @generated from field: repeated string workspace_paths = 2; + */ + workspacePaths: string[]; - /** - * @generated from field: string shell = 3; - */ - shell: string; + /** + * @generated from field: string shell = 3; + */ + shell: string; - /** - * @generated from field: bool sandbox_enabled = 5; - */ - sandboxEnabled: boolean; + /** + * @generated from field: bool sandbox_enabled = 5; + */ + sandboxEnabled: boolean; - /** - * @generated from field: string terminals_folder = 7; - */ - terminalsFolder: string; + /** + * @generated from field: string terminals_folder = 7; + */ + terminalsFolder: string; - /** - * @generated from field: string agent_shared_notes_folder = 8; - */ - agentSharedNotesFolder: string; + /** + * @generated from field: string agent_shared_notes_folder = 8; + */ + agentSharedNotesFolder: string; - /** - * @generated from field: string agent_conversation_notes_folder = 9; - */ - agentConversationNotesFolder: string; + /** + * @generated from field: string agent_conversation_notes_folder = 9; + */ + agentConversationNotesFolder: string; - /** - * @generated from field: string time_zone = 10; - */ - timeZone: string; + /** + * @generated from field: string time_zone = 10; + */ + timeZone: string; - /** - * Project-specific folder for storing artifacts, computed client-side as ~/.cursor/projects/{slug}/ - * - * @generated from field: string project_folder = 11; - */ - projectFolder: string; + /** + * Project-specific folder for storing artifacts, computed client-side as ~/.cursor/projects/{slug}/ + * + * @generated from field: string project_folder = 11; + */ + projectFolder: string; - /** - * Folder where agent conversation transcripts are stored - * - * @generated from field: string agent_transcripts_folder = 12; - */ - agentTranscriptsFolder: string; + /** + * Folder where agent conversation transcripts are stored + * + * @generated from field: string agent_transcripts_folder = 12; + */ + agentTranscriptsFolder: string; }; /** * Describes the message agent.v1.RequestContextEnv. * Use `create(RequestContextEnvSchema)` to create a new message. */ -export const RequestContextEnvSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 343); +export const RequestContextEnvSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 343); /** * @generated from message agent.v1.DebugModeConfig */ export type DebugModeConfig = Message<"agent.v1.DebugModeConfig"> & { - /** - * @generated from field: string log_path = 1; - */ - logPath: string; + /** + * @generated from field: string log_path = 1; + */ + logPath: string; - /** - * @generated from field: string server_endpoint = 2; - */ - serverEndpoint: string; + /** + * @generated from field: string server_endpoint = 2; + */ + serverEndpoint: string; }; /** * Describes the message agent.v1.DebugModeConfig. * Use `create(DebugModeConfigSchema)` to create a new message. */ -export const DebugModeConfigSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 344); +export const DebugModeConfigSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 344); /** * @generated from message agent.v1.SkillDescriptor */ export type SkillDescriptor = Message<"agent.v1.SkillDescriptor"> & { - /** - * @generated from field: string name = 1; - */ - name: string; + /** + * @generated from field: string name = 1; + */ + name: string; - /** - * @generated from field: string description = 2; - */ - description: string; + /** + * @generated from field: string description = 2; + */ + description: string; - /** - * @generated from field: string folder_path = 3; - */ - folderPath: string; + /** + * @generated from field: string folder_path = 3; + */ + folderPath: string; - /** - * @generated from field: bool enabled = 4; - */ - enabled: boolean; + /** + * @generated from field: bool enabled = 4; + */ + enabled: boolean; - /** - * @generated from field: optional string parse_error = 5; - */ - parseError?: string; + /** + * @generated from field: optional string parse_error = 5; + */ + parseError?: string | undefined; - /** - * @generated from field: string readme_file_path = 6; - */ - readmeFilePath: string; + /** + * @generated from field: string readme_file_path = 6; + */ + readmeFilePath: string; - /** - * @generated from field: int32 package_type = 7; - */ - packageType: number; + /** + * @generated from field: int32 package_type = 7; + */ + packageType: number; }; /** * Describes the message agent.v1.SkillDescriptor. * Use `create(SkillDescriptorSchema)` to create a new message. */ -export const SkillDescriptorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 345); +export const SkillDescriptorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 345); /** * @generated from message agent.v1.SkillOptions */ export type SkillOptions = Message<"agent.v1.SkillOptions"> & { - /** - * @generated from field: repeated agent.v1.SkillDescriptor skill_descriptors = 1; - */ - skillDescriptors: SkillDescriptor[]; + /** + * @generated from field: repeated agent.v1.SkillDescriptor skill_descriptors = 1; + */ + skillDescriptors: SkillDescriptor[]; }; /** * Describes the message agent.v1.SkillOptions. * Use `create(SkillOptionsSchema)` to create a new message. */ -export const SkillOptionsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 346); +export const SkillOptionsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 346); /** * @generated from message agent.v1.RequestContext */ export type RequestContext = Message<"agent.v1.RequestContext"> & { - /** - * All rules, categorized by the embedded type - * - * @generated from field: repeated agent.v1.CursorRule rules = 2; - */ - rules: CursorRule[]; + /** + * All rules, categorized by the embedded type + * + * @generated from field: repeated agent.v1.CursorRule rules = 2; + */ + rules: CursorRule[]; - /** - * @generated from field: agent.v1.RequestContextEnv env = 4; - */ - env?: RequestContextEnv; + /** + * @generated from field: agent.v1.RequestContextEnv env = 4; + */ + env?: RequestContextEnv | undefined; - /** - * @generated from field: repeated agent.v1.RepositoryIndexingInfo repository_info = 6; - */ - repositoryInfo: RepositoryIndexingInfo[]; + /** + * @generated from field: repeated agent.v1.RepositoryIndexingInfo repository_info = 6; + */ + repositoryInfo: RepositoryIndexingInfo[]; - /** - * @generated from field: repeated agent.v1.McpToolDefinition tools = 7; - */ - tools: McpToolDefinition[]; + /** + * @generated from field: repeated agent.v1.McpToolDefinition tools = 7; + */ + tools: McpToolDefinition[]; - /** - * @generated from field: optional string conversation_notes_listing = 8; - */ - conversationNotesListing?: string; + /** + * @generated from field: optional string conversation_notes_listing = 8; + */ + conversationNotesListing?: string | undefined; - /** - * @generated from field: optional string shared_notes_listing = 9; - */ - sharedNotesListing?: string; + /** + * @generated from field: optional string shared_notes_listing = 9; + */ + sharedNotesListing?: string | undefined; - /** - * @generated from field: repeated agent.v1.GitRepoInfo git_repos = 11; - */ - gitRepos: GitRepoInfo[]; + /** + * @generated from field: repeated agent.v1.GitRepoInfo git_repos = 11; + */ + gitRepos: GitRepoInfo[]; - /** - * @generated from field: repeated agent.v1.LsDirectoryTreeNode project_layouts = 13; - */ - projectLayouts: LsDirectoryTreeNode[]; + /** + * @generated from field: repeated agent.v1.LsDirectoryTreeNode project_layouts = 13; + */ + projectLayouts: LsDirectoryTreeNode[]; - /** - * @generated from field: repeated agent.v1.McpInstructions mcp_instructions = 14; - */ - mcpInstructions: McpInstructions[]; + /** + * @generated from field: repeated agent.v1.McpInstructions mcp_instructions = 14; + */ + mcpInstructions: McpInstructions[]; - /** - * @generated from field: optional agent.v1.DebugModeConfig debug_mode_config = 15; - */ - debugModeConfig?: DebugModeConfig; + /** + * @generated from field: optional agent.v1.DebugModeConfig debug_mode_config = 15; + */ + debugModeConfig?: DebugModeConfig | undefined; - /** - * @generated from field: optional string cloud_rule = 16; - */ - cloudRule?: string; + /** + * @generated from field: optional string cloud_rule = 16; + */ + cloudRule?: string | undefined; - /** - * @generated from field: optional bool web_search_enabled = 17; - */ - webSearchEnabled?: boolean; + /** + * @generated from field: optional bool web_search_enabled = 17; + */ + webSearchEnabled?: boolean | undefined; - /** - * @generated from field: optional agent.v1.SkillOptions skill_options = 18; - */ - skillOptions?: SkillOptions; + /** + * @generated from field: optional agent.v1.SkillOptions skill_options = 18; + */ + skillOptions?: SkillOptions | undefined; - /** - * @generated from field: optional bool repository_info_should_query_prod = 19; - */ - repositoryInfoShouldQueryProd?: boolean; + /** + * @generated from field: optional bool repository_info_should_query_prod = 19; + */ + repositoryInfoShouldQueryProd?: boolean | undefined; - /** - * @generated from field: map file_contents = 20; - */ - fileContents: { [key: string]: string }; + /** + * @generated from field: map file_contents = 20; + */ + fileContents: { [key: string]: string }; - /** - * Content of the user-intent/index.md file summarizing past conversations - * - * @generated from field: optional string user_intent_summary = 21; - */ - userIntentSummary?: string; + /** + * Content of the user-intent/index.md file summarizing past conversations + * + * @generated from field: optional string user_intent_summary = 21; + */ + userIntentSummary?: string | undefined; - /** - * Local custom subagent definitions loaded from workspace configuration - * - * @generated from field: repeated agent.v1.CustomSubagent custom_subagents = 22; - */ - customSubagents: CustomSubagent[]; + /** + * Local custom subagent definitions loaded from workspace configuration + * + * @generated from field: repeated agent.v1.CustomSubagent custom_subagents = 22; + */ + customSubagents: CustomSubagent[]; - /** - * MCP file system options for agent MCP tool descriptor access - * - * @generated from field: optional agent.v1.McpFileSystemOptions mcp_file_system_options = 23; - */ - mcpFileSystemOptions?: McpFileSystemOptions; + /** + * MCP file system options for agent MCP tool descriptor access + * + * @generated from field: optional agent.v1.McpFileSystemOptions mcp_file_system_options = 23; + */ + mcpFileSystemOptions?: McpFileSystemOptions | undefined; }; /** * Describes the message agent.v1.RequestContext. * Use `create(RequestContextSchema)` to create a new message. */ -export const RequestContextSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 347); +export const RequestContextSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 347); /** * @generated from message agent.v1.SandboxPolicy */ export type SandboxPolicy = Message<"agent.v1.SandboxPolicy"> & { - /** - * @generated from field: int32 type = 1; - */ - type: number; + /** + * @generated from field: int32 type = 1; + */ + type: number; - /** - * @generated from field: optional bool network_access = 2; - */ - networkAccess?: boolean; + /** + * @generated from field: optional bool network_access = 2; + */ + networkAccess?: boolean | undefined; - /** - * @generated from field: repeated string additional_readwrite_paths = 3; - */ - additionalReadwritePaths: string[]; + /** + * @generated from field: repeated string additional_readwrite_paths = 3; + */ + additionalReadwritePaths: string[]; - /** - * @generated from field: repeated string additional_readonly_paths = 4; - */ - additionalReadonlyPaths: string[]; + /** + * @generated from field: repeated string additional_readonly_paths = 4; + */ + additionalReadonlyPaths: string[]; - /** - * @generated from field: optional string debug_output_dir = 5; - */ - debugOutputDir?: string; + /** + * @generated from field: optional string debug_output_dir = 5; + */ + debugOutputDir?: string | undefined; - /** - * @generated from field: optional bool block_git_writes = 6; - */ - blockGitWrites?: boolean; + /** + * @generated from field: optional bool block_git_writes = 6; + */ + blockGitWrites?: boolean | undefined; - /** - * If true, excludes default tmp paths (/tmp/, /private/tmp/, /var/folders/) from the sandbox writable paths. Useful for testing readonly behavior. - * - * @generated from field: optional bool disable_tmp_write = 7; - */ - disableTmpWrite?: boolean; + /** + * If true, excludes default tmp paths (/tmp/, /private/tmp/, /var/folders/) from the sandbox writable paths. Useful for testing readonly behavior. + * + * @generated from field: optional bool disable_tmp_write = 7; + */ + disableTmpWrite?: boolean | undefined; }; /** * Describes the message agent.v1.SandboxPolicy. * Use `create(SandboxPolicySchema)` to create a new message. */ -export const SandboxPolicySchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 348); +export const SandboxPolicySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 348); /** * @generated from message agent.v1.SelectedImage */ export type SelectedImage = Message<"agent.v1.SelectedImage"> & { - /** - * @generated from field: string uuid = 2; - */ - uuid: string; + /** + * @generated from field: string uuid = 2; + */ + uuid: string; - /** - * @generated from field: string path = 3; - */ - path: string; + /** + * @generated from field: string path = 3; + */ + path: string; - /** - * @generated from field: agent.v1.SelectedImage_Dimension dimension = 4; - */ - dimension?: SelectedImage_Dimension; + /** + * @generated from field: agent.v1.SelectedImage_Dimension dimension = 4; + */ + dimension?: SelectedImage_Dimension | undefined; - /** - * @generated from field: string mime_type = 7; - */ - mimeType: string; + /** + * @generated from field: string mime_type = 7; + */ + mimeType: string; - /** - * @generated from oneof agent.v1.SelectedImage.data_or_blob_id - */ - dataOrBlobId: - | { - /** - * @generated from field: bytes blob_id = 1; - */ - value: Uint8Array; - case: "blobId"; - } - | { - /** - * @generated from field: bytes data = 8; - */ - value: Uint8Array; - case: "data"; - } - | { - /** - * @generated from field: agent.v1.SelectedImage_BlobIdWithData blob_id_with_data = 9; - */ - value: SelectedImage_BlobIdWithData; - case: "blobIdWithData"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.SelectedImage.data_or_blob_id + */ + dataOrBlobId: { + /** + * @generated from field: bytes blob_id = 1; + */ + value: Uint8Array; + case: "blobId"; + } | { + /** + * @generated from field: bytes data = 8; + */ + value: Uint8Array; + case: "data"; + } | { + /** + * @generated from field: agent.v1.SelectedImage_BlobIdWithData blob_id_with_data = 9; + */ + value: SelectedImage_BlobIdWithData; + case: "blobIdWithData"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.SelectedImage. * Use `create(SelectedImageSchema)` to create a new message. */ -export const SelectedImageSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 349); +export const SelectedImageSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 349); /** * Contains both blob_id and data together, for when the client has both and wants to populate the server-side cache without re-uploading @@ -10447,47 +10867,45 @@ export const SelectedImageSchema: GenMessage = /*@__PURE__*/ mess * @generated from message agent.v1.SelectedImage_BlobIdWithData */ export type SelectedImage_BlobIdWithData = Message<"agent.v1.SelectedImage_BlobIdWithData"> & { - /** - * @generated from field: bytes blob_id = 1; - */ - blobId: Uint8Array; + /** + * @generated from field: bytes blob_id = 1; + */ + blobId: Uint8Array; - /** - * @generated from field: bytes data = 2; - */ - data: Uint8Array; + /** + * @generated from field: bytes data = 2; + */ + data: Uint8Array; }; /** * Describes the message agent.v1.SelectedImage_BlobIdWithData. * Use `create(SelectedImage_BlobIdWithDataSchema)` to create a new message. */ -export const SelectedImage_BlobIdWithDataSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 350); +export const SelectedImage_BlobIdWithDataSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 350); /** * @generated from message agent.v1.SelectedImage_Dimension */ export type SelectedImage_Dimension = Message<"agent.v1.SelectedImage_Dimension"> & { - /** - * @generated from field: int32 width = 1; - */ - width: number; + /** + * @generated from field: int32 width = 1; + */ + width: number; - /** - * @generated from field: int32 height = 2; - */ - height: number; + /** + * @generated from field: int32 height = 2; + */ + height: number; }; /** * Describes the message agent.v1.SelectedImage_Dimension. * Use `create(SelectedImage_DimensionSchema)` to create a new message. */ -export const SelectedImage_DimensionSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 351); +export const SelectedImage_DimensionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 351); /** * Extra context entry that can be stored inline or as a blob reference @@ -10495,32 +10913,30 @@ export const SelectedImage_DimensionSchema: GenMessage * @generated from message agent.v1.ExtraContextEntry */ export type ExtraContextEntry = Message<"agent.v1.ExtraContextEntry"> & { - /** - * @generated from oneof agent.v1.ExtraContextEntry.data_or_blob_id - */ - dataOrBlobId: - | { - /** - * @generated from field: string data = 1; - */ - value: string; - case: "data"; - } - | { - /** - * @generated from field: bytes blob_id = 2; - */ - value: Uint8Array; - case: "blobId"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ExtraContextEntry.data_or_blob_id + */ + dataOrBlobId: { + /** + * @generated from field: string data = 1; + */ + value: string; + case: "data"; + } | { + /** + * @generated from field: bytes blob_id = 2; + */ + value: Uint8Array; + case: "blobId"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ExtraContextEntry. * Use `create(ExtraContextEntrySchema)` to create a new message. */ -export const ExtraContextEntrySchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 352); +export const ExtraContextEntrySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 352); /** * A selected file from the UI @@ -10528,29 +10944,30 @@ export const ExtraContextEntrySchema: GenMessage = /*@__PURE_ * @generated from message agent.v1.SelectedFile */ export type SelectedFile = Message<"agent.v1.SelectedFile"> & { - /** - * @generated from field: string content = 1; - */ - content: string; + /** + * @generated from field: string content = 1; + */ + content: string; - /** - * This is the full path - * - * @generated from field: string path = 2; - */ - path: string; + /** + * This is the full path + * + * @generated from field: string path = 2; + */ + path: string; - /** - * @generated from field: optional string relative_path = 3; - */ - relativePath?: string; + /** + * @generated from field: optional string relative_path = 3; + */ + relativePath?: string | undefined; }; /** * Describes the message agent.v1.SelectedFile. * Use `create(SelectedFileSchema)` to create a new message. */ -export const SelectedFileSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 353); +export const SelectedFileSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 353); /** * A selected code selection from the UI @@ -10558,36 +10975,35 @@ export const SelectedFileSchema: GenMessage = /*@__PURE__*/ messag * @generated from message agent.v1.SelectedCodeSelection */ export type SelectedCodeSelection = Message<"agent.v1.SelectedCodeSelection"> & { - /** - * @generated from field: string content = 1; - */ - content: string; + /** + * @generated from field: string content = 1; + */ + content: string; - /** - * This is the full path - * - * @generated from field: string path = 2; - */ - path: string; + /** + * This is the full path + * + * @generated from field: string path = 2; + */ + path: string; - /** - * @generated from field: optional string relative_path = 3; - */ - relativePath?: string; + /** + * @generated from field: optional string relative_path = 3; + */ + relativePath?: string | undefined; - /** - * @generated from field: agent.v1.Range range = 4; - */ - range?: Range; + /** + * @generated from field: agent.v1.Range range = 4; + */ + range?: Range | undefined; }; /** * Describes the message agent.v1.SelectedCodeSelection. * Use `create(SelectedCodeSelectionSchema)` to create a new message. */ -export const SelectedCodeSelectionSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 354); +export const SelectedCodeSelectionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 354); /** * A selected terminal from the UI @@ -10595,27 +11011,28 @@ export const SelectedCodeSelectionSchema: GenMessage = * @generated from message agent.v1.SelectedTerminal */ export type SelectedTerminal = Message<"agent.v1.SelectedTerminal"> & { - /** - * @generated from field: string content = 1; - */ - content: string; + /** + * @generated from field: string content = 1; + */ + content: string; - /** - * @generated from field: optional string title = 2; - */ - title?: string; + /** + * @generated from field: optional string title = 2; + */ + title?: string | undefined; - /** - * @generated from field: optional string path = 3; - */ - path?: string; + /** + * @generated from field: optional string path = 3; + */ + path?: string | undefined; }; /** * Describes the message agent.v1.SelectedTerminal. * Use `create(SelectedTerminalSchema)` to create a new message. */ -export const SelectedTerminalSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 355); +export const SelectedTerminalSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 355); /** * A selected terminal selection from the UI @@ -10623,34 +11040,33 @@ export const SelectedTerminalSchema: GenMessage = /*@__PURE__* * @generated from message agent.v1.SelectedTerminalSelection */ export type SelectedTerminalSelection = Message<"agent.v1.SelectedTerminalSelection"> & { - /** - * @generated from field: string content = 1; - */ - content: string; + /** + * @generated from field: string content = 1; + */ + content: string; - /** - * @generated from field: optional string title = 2; - */ - title?: string; + /** + * @generated from field: optional string title = 2; + */ + title?: string | undefined; - /** - * @generated from field: optional string path = 3; - */ - path?: string; + /** + * @generated from field: optional string path = 3; + */ + path?: string | undefined; - /** - * @generated from field: agent.v1.Range range = 4; - */ - range?: Range; + /** + * @generated from field: agent.v1.Range range = 4; + */ + range?: Range | undefined; }; /** * Describes the message agent.v1.SelectedTerminalSelection. * Use `create(SelectedTerminalSelectionSchema)` to create a new message. */ -export const SelectedTerminalSelectionSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 356); +export const SelectedTerminalSelectionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 356); /** * A selected folder from the UI @@ -10658,29 +11074,30 @@ export const SelectedTerminalSelectionSchema: GenMessage & { - /** - * This is the full path - * - * @generated from field: string path = 1; - */ - path: string; + /** + * This is the full path + * + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: optional string relative_path = 2; - */ - relativePath?: string; + /** + * @generated from field: optional string relative_path = 2; + */ + relativePath?: string | undefined; - /** - * @generated from field: agent.v1.LsDirectoryTreeNode directory_tree = 3; - */ - directoryTree?: LsDirectoryTreeNode; + /** + * @generated from field: agent.v1.LsDirectoryTreeNode directory_tree = 3; + */ + directoryTree?: LsDirectoryTreeNode | undefined; }; /** * Describes the message agent.v1.SelectedFolder. * Use `create(SelectedFolderSchema)` to create a new message. */ -export const SelectedFolderSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 357); +export const SelectedFolderSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 357); /** * An external link manually attached by the user @@ -10688,39 +11105,40 @@ export const SelectedFolderSchema: GenMessage = /*@__PURE__*/ me * @generated from message agent.v1.SelectedExternalLink */ export type SelectedExternalLink = Message<"agent.v1.SelectedExternalLink"> & { - /** - * @generated from field: string url = 1; - */ - url: string; + /** + * @generated from field: string url = 1; + */ + url: string; - /** - * @generated from field: string uuid = 2; - */ - uuid: string; + /** + * @generated from field: string uuid = 2; + */ + uuid: string; - /** - * For local PDF files Base64-encoded PDF content - * - * @generated from field: optional string pdf_content = 3; - */ - pdfContent?: string; + /** + * For local PDF files Base64-encoded PDF content + * + * @generated from field: optional string pdf_content = 3; + */ + pdfContent?: string | undefined; - /** - * @generated from field: optional bool is_pdf = 4; - */ - isPdf?: boolean; + /** + * @generated from field: optional bool is_pdf = 4; + */ + isPdf?: boolean | undefined; - /** - * @generated from field: optional string filename = 5; - */ - filename?: string; + /** + * @generated from field: optional string filename = 5; + */ + filename?: string | undefined; }; /** * Describes the message agent.v1.SelectedExternalLink. * Use `create(SelectedExternalLinkSchema)` to create a new message. */ -export const SelectedExternalLinkSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 358); +export const SelectedExternalLinkSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 358); /** * A cursor rule manually attached by the user @@ -10728,17 +11146,18 @@ export const SelectedExternalLinkSchema: GenMessage = /*@_ * @generated from message agent.v1.SelectedCursorRule */ export type SelectedCursorRule = Message<"agent.v1.SelectedCursorRule"> & { - /** - * @generated from field: agent.v1.CursorRule rule = 1; - */ - rule?: CursorRule; + /** + * @generated from field: agent.v1.CursorRule rule = 1; + */ + rule?: CursorRule | undefined; }; /** * Describes the message agent.v1.SelectedCursorRule. * Use `create(SelectedCursorRuleSchema)` to create a new message. */ -export const SelectedCursorRuleSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 359); +export const SelectedCursorRuleSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 359); /** * Git diff (uncommitted changes in working tree) @@ -10746,19 +11165,20 @@ export const SelectedCursorRuleSchema: GenMessage = /*@__PUR * @generated from message agent.v1.SelectedGitDiff */ export type SelectedGitDiff = Message<"agent.v1.SelectedGitDiff"> & { - /** - * Raw git diff output - * - * @generated from field: string content = 1; - */ - content: string; + /** + * Raw git diff output + * + * @generated from field: string content = 1; + */ + content: string; }; /** * Describes the message agent.v1.SelectedGitDiff. * Use `create(SelectedGitDiffSchema)` to create a new message. */ -export const SelectedGitDiffSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 360); +export const SelectedGitDiffSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 360); /** * Git diff from branch to main @@ -10766,21 +11186,20 @@ export const SelectedGitDiffSchema: GenMessage = /*@__PURE__*/ * @generated from message agent.v1.SelectedGitDiffFromBranchToMain */ export type SelectedGitDiffFromBranchToMain = Message<"agent.v1.SelectedGitDiffFromBranchToMain"> & { - /** - * Raw git diff output - * - * @generated from field: string content = 1; - */ - content: string; + /** + * Raw git diff output + * + * @generated from field: string content = 1; + */ + content: string; }; /** * Describes the message agent.v1.SelectedGitDiffFromBranchToMain. * Use `create(SelectedGitDiffFromBranchToMainSchema)` to create a new message. */ -export const SelectedGitDiffFromBranchToMainSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 361); +export const SelectedGitDiffFromBranchToMainSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 361); /** * A git commit manually attached by the user @@ -10788,34 +11207,35 @@ export const SelectedGitDiffFromBranchToMainSchema: GenMessage & { - /** - * @generated from field: string sha = 1; - */ - sha: string; + /** + * @generated from field: string sha = 1; + */ + sha: string; - /** - * @generated from field: string message = 2; - */ - message: string; + /** + * @generated from field: string message = 2; + */ + message: string; - /** - * @generated from field: optional string description = 3; - */ - description?: string; + /** + * @generated from field: optional string description = 3; + */ + description?: string | undefined; - /** - * Raw git diff output for this commit - * - * @generated from field: string diff = 4; - */ - diff: string; + /** + * Raw git diff output for this commit + * + * @generated from field: string diff = 4; + */ + diff: string; }; /** * Describes the message agent.v1.SelectedGitCommit. * Use `create(SelectedGitCommitSchema)` to create a new message. */ -export const SelectedGitCommitSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 362); +export const SelectedGitCommitSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 362); /** * A pull request manually attached by the user via @mention Uses the same folder structure as ViewedPullRequest for consistency @@ -10823,55 +11243,56 @@ export const SelectedGitCommitSchema: GenMessage = /*@__PURE_ * @generated from message agent.v1.SelectedPullRequest */ export type SelectedPullRequest = Message<"agent.v1.SelectedPullRequest"> & { - /** - * @generated from field: int32 number = 1; - */ - number: number; + /** + * @generated from field: int32 number = 1; + */ + number: number; - /** - * @generated from field: string url = 2; - */ - url: string; + /** + * @generated from field: string url = 2; + */ + url: string; - /** - * @generated from field: optional string title = 3; - */ - title?: string; + /** + * @generated from field: optional string title = 3; + */ + title?: string | undefined; - /** - * Path to the folder containing PR details (diffs, metadata, etc.) - * - * @generated from field: string folder_path = 4; - */ - folderPath: string; + /** + * Path to the folder containing PR details (diffs, metadata, etc.) + * + * @generated from field: string folder_path = 4; + */ + folderPath: string; - /** - * Summary JSON containing file list and diff sizes (contents of summary.json) - * - * @generated from field: optional string summary_json = 5; - */ - summaryJson?: string; + /** + * Summary JSON containing file list and diff sizes (contents of summary.json) + * + * @generated from field: optional string summary_json = 5; + */ + summaryJson?: string | undefined; - /** - * PR description/body - * - * @generated from field: optional string description = 6; - */ - description?: string; + /** + * PR description/body + * + * @generated from field: optional string description = 6; + */ + description?: string | undefined; - /** - * If set, other fields are empty and data should be fetched from the blob - * - * @generated from field: optional bytes blob_id = 7; - */ - blobId?: Uint8Array; + /** + * If set, other fields are empty and data should be fetched from the blob + * + * @generated from field: optional bytes blob_id = 7; + */ + blobId?: Uint8Array | undefined; }; /** * Describes the message agent.v1.SelectedPullRequest. * Use `create(SelectedPullRequestSchema)` to create a new message. */ -export const SelectedPullRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 363); +export const SelectedPullRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 363); /** * A selection from a pull request diff (for files that may not exist on disk) @@ -10879,56 +11300,55 @@ export const SelectedPullRequestSchema: GenMessage = /*@__P * @generated from message agent.v1.SelectedGitPRDiffSelection */ export type SelectedGitPRDiffSelection = Message<"agent.v1.SelectedGitPRDiffSelection"> & { - /** - * Full URL to the pull request - * - * @generated from field: string pr_url = 1; - */ - prUrl: string; + /** + * Full URL to the pull request + * + * @generated from field: string pr_url = 1; + */ + prUrl: string; - /** - * Path to the file within the PR - * - * @generated from field: string file_path = 2; - */ - filePath: string; + /** + * Path to the file within the PR + * + * @generated from field: string file_path = 2; + */ + filePath: string; - /** - * Start line in the diff - * - * @generated from field: int32 start_line = 3; - */ - startLine: number; + /** + * Start line in the diff + * + * @generated from field: int32 start_line = 3; + */ + startLine: number; - /** - * End line in the diff - * - * @generated from field: int32 end_line = 4; - */ - endLine: number; + /** + * End line in the diff + * + * @generated from field: int32 end_line = 4; + */ + endLine: number; - /** - * The diff content for this file (or selection) - * - * @generated from field: optional string diff_content = 5; - */ - diffContent?: string; + /** + * The diff content for this file (or selection) + * + * @generated from field: optional string diff_content = 5; + */ + diffContent?: string | undefined; - /** - * If set, other fields are empty and data should be fetched from the blob - * - * @generated from field: optional bytes blob_id = 6; - */ - blobId?: Uint8Array; + /** + * If set, other fields are empty and data should be fetched from the blob + * + * @generated from field: optional bytes blob_id = 6; + */ + blobId?: Uint8Array | undefined; }; /** * Describes the message agent.v1.SelectedGitPRDiffSelection. * Use `create(SelectedGitPRDiffSelectionSchema)` to create a new message. */ -export const SelectedGitPRDiffSelectionSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 364); +export const SelectedGitPRDiffSelectionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 364); /** * A cursor command manually attached by the user @@ -10936,24 +11356,23 @@ export const SelectedGitPRDiffSelectionSchema: GenMessage & { - /** - * @generated from field: string name = 1; - */ - name: string; + /** + * @generated from field: string name = 1; + */ + name: string; - /** - * @generated from field: string content = 2; - */ - content: string; + /** + * @generated from field: string content = 2; + */ + content: string; }; /** * Describes the message agent.v1.SelectedCursorCommand. * Use `create(SelectedCursorCommandSchema)` to create a new message. */ -export const SelectedCursorCommandSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 365); +export const SelectedCursorCommandSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 365); /** * A documentation manually attached by the user @@ -10961,24 +11380,23 @@ export const SelectedCursorCommandSchema: GenMessage = * @generated from message agent.v1.SelectedDocumentation */ export type SelectedDocumentation = Message<"agent.v1.SelectedDocumentation"> & { - /** - * @generated from field: string doc_id = 1; - */ - docId: string; + /** + * @generated from field: string doc_id = 1; + */ + docId: string; - /** - * @generated from field: string name = 2; - */ - name: string; + /** + * @generated from field: string name = 2; + */ + name: string; }; /** * Describes the message agent.v1.SelectedDocumentation. * Use `create(SelectedDocumentationSchema)` to create a new message. */ -export const SelectedDocumentationSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 366); +export const SelectedDocumentationSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 366); /** * A past chat manually attached by the user (transcript file) @@ -10986,22 +11404,23 @@ export const SelectedDocumentationSchema: GenMessage = * @generated from message agent.v1.SelectedPastChat */ export type SelectedPastChat = Message<"agent.v1.SelectedPastChat"> & { - /** - * @generated from field: string agent_id = 1; - */ - agentId: string; + /** + * @generated from field: string agent_id = 1; + */ + agentId: string; - /** - * @generated from field: string name = 2; - */ - name: string; + /** + * @generated from field: string name = 2; + */ + name: string; }; /** * Describes the message agent.v1.SelectedPastChat. * Use `create(SelectedPastChatSchema)` to create a new message. */ -export const SelectedPastChatSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 367); +export const SelectedPastChatSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 367); /** * A call frame from a stack trace @@ -11009,32 +11428,33 @@ export const SelectedPastChatSchema: GenMessage = /*@__PURE__* * @generated from message agent.v1.CallFrame */ export type CallFrame = Message<"agent.v1.CallFrame"> & { - /** - * @generated from field: optional string function_name = 1; - */ - functionName?: string; + /** + * @generated from field: optional string function_name = 1; + */ + functionName?: string | undefined; - /** - * @generated from field: optional string url = 2; - */ - url?: string; + /** + * @generated from field: optional string url = 2; + */ + url?: string | undefined; - /** - * @generated from field: optional int32 line_number = 3; - */ - lineNumber?: number; + /** + * @generated from field: optional int32 line_number = 3; + */ + lineNumber?: number | undefined; - /** - * @generated from field: optional int32 column_number = 4; - */ - columnNumber?: number; + /** + * @generated from field: optional int32 column_number = 4; + */ + columnNumber?: number | undefined; }; /** * Describes the message agent.v1.CallFrame. * Use `create(CallFrameSchema)` to create a new message. */ -export const CallFrameSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 368); +export const CallFrameSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 368); /** * A stack trace @@ -11042,22 +11462,23 @@ export const CallFrameSchema: GenMessage = /*@__PURE__*/ messageDesc( * @generated from message agent.v1.StackTrace */ export type StackTrace = Message<"agent.v1.StackTrace"> & { - /** - * @generated from field: repeated agent.v1.CallFrame call_frames = 1; - */ - callFrames: CallFrame[]; + /** + * @generated from field: repeated agent.v1.CallFrame call_frames = 1; + */ + callFrames: CallFrame[]; - /** - * @generated from field: optional string raw_stack_trace = 2; - */ - rawStackTrace?: string; + /** + * @generated from field: optional string raw_stack_trace = 2; + */ + rawStackTrace?: string | undefined; }; /** * Describes the message agent.v1.StackTrace. * Use `create(StackTraceSchema)` to create a new message. */ -export const StackTraceSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 369); +export const StackTraceSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 369); /** * A console log entry from the runtime @@ -11065,49 +11486,50 @@ export const StackTraceSchema: GenMessage = /*@__PURE__*/ messageDes * @generated from message agent.v1.SelectedConsoleLog */ export type SelectedConsoleLog = Message<"agent.v1.SelectedConsoleLog"> & { - /** - * @generated from field: string message = 1; - */ - message: string; + /** + * @generated from field: string message = 1; + */ + message: string; - /** - * * Unix timestamp in milliseconds when this log entry was created - * - * @generated from field: double timestamp = 2; - */ - timestamp: number; + /** + * * Unix timestamp in milliseconds when this log entry was created + * + * @generated from field: double timestamp = 2; + */ + timestamp: number; - /** - * @generated from field: string level = 3; - */ - level: string; + /** + * @generated from field: string level = 3; + */ + level: string; - /** - * @generated from field: string client_name = 4; - */ - clientName: string; + /** + * @generated from field: string client_name = 4; + */ + clientName: string; - /** - * @generated from field: string session_id = 5; - */ - sessionId: string; + /** + * @generated from field: string session_id = 5; + */ + sessionId: string; - /** - * @generated from field: optional agent.v1.StackTrace stack_trace = 6; - */ - stackTrace?: StackTrace; + /** + * @generated from field: optional agent.v1.StackTrace stack_trace = 6; + */ + stackTrace?: StackTrace | undefined; - /** - * @generated from field: optional string object_data_json = 7; - */ - objectDataJson?: string; + /** + * @generated from field: optional string object_data_json = 7; + */ + objectDataJson?: string | undefined; }; /** * Describes the message agent.v1.SelectedConsoleLog. * Use `create(SelectedConsoleLogSchema)` to create a new message. */ -export const SelectedConsoleLogSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 370); +export const SelectedConsoleLogSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 370); /** * A UI element picked by the user from the runtime @@ -11115,42 +11537,43 @@ export const SelectedConsoleLogSchema: GenMessage = /*@__PUR * @generated from message agent.v1.SelectedUIElement */ export type SelectedUIElement = Message<"agent.v1.SelectedUIElement"> & { - /** - * @generated from field: string element = 1; - */ - element: string; + /** + * @generated from field: string element = 1; + */ + element: string; - /** - * @generated from field: string xpath = 2; - */ - xpath: string; + /** + * @generated from field: string xpath = 2; + */ + xpath: string; - /** - * @generated from field: string text_content = 3; - */ - textContent: string; + /** + * @generated from field: string text_content = 3; + */ + textContent: string; - /** - * @generated from field: string extra = 4; - */ - extra: string; + /** + * @generated from field: string extra = 4; + */ + extra: string; - /** - * @generated from field: optional string component = 5; - */ - component?: string; + /** + * @generated from field: optional string component = 5; + */ + component?: string | undefined; - /** - * @generated from field: optional string component_props_json = 6; - */ - componentPropsJson?: string; + /** + * @generated from field: optional string component_props_json = 6; + */ + componentPropsJson?: string | undefined; }; /** * Describes the message agent.v1.SelectedUIElement. * Use `create(SelectedUIElementSchema)` to create a new message. */ -export const SelectedUIElementSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 371); +export const SelectedUIElementSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 371); /** * A subagent selected by the user from the slash menu @@ -11158,17 +11581,18 @@ export const SelectedUIElementSchema: GenMessage = /*@__PURE_ * @generated from message agent.v1.SelectedSubagent */ export type SelectedSubagent = Message<"agent.v1.SelectedSubagent"> & { - /** - * @generated from field: string name = 1; - */ - name: string; + /** + * @generated from field: string name = 1; + */ + name: string; }; /** * Describes the message agent.v1.SelectedSubagent. * Use `create(SelectedSubagentSchema)` to create a new message. */ -export const SelectedSubagentSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 372); +export const SelectedSubagentSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 372); /** * Container for selected context from the UI @@ -11176,126 +11600,127 @@ export const SelectedSubagentSchema: GenMessage = /*@__PURE__* * @generated from message agent.v1.SelectedContext */ export type SelectedContext = Message<"agent.v1.SelectedContext"> & { - /** - * @generated from field: repeated agent.v1.SelectedImage selected_images = 1; - */ - selectedImages: SelectedImage[]; + /** + * @generated from field: repeated agent.v1.SelectedImage selected_images = 1; + */ + selectedImages: SelectedImage[]; - /** - * @generated from field: optional agent.v1.InvocationContext invocation_context = 2; - */ - invocationContext?: InvocationContext; + /** + * @generated from field: optional agent.v1.InvocationContext invocation_context = 2; + */ + invocationContext?: InvocationContext | undefined; - /** - * Temporary hack for IDE-based context (@filename, @Diff, etc.) in background agents only. TODO: remove once proper IDE context format is implemented. - * - * @generated from field: repeated string extra_context = 3; - */ - extraContext: string[]; + /** + * Temporary hack for IDE-based context (@filename, @Diff, etc.) in background agents only. TODO: remove once proper IDE context format is implemented. + * + * @generated from field: repeated string extra_context = 3; + */ + extraContext: string[]; - /** - * @generated from field: repeated agent.v1.ExtraContextEntry extra_context_entries = 16; - */ - extraContextEntries: ExtraContextEntry[]; + /** + * @generated from field: repeated agent.v1.ExtraContextEntry extra_context_entries = 16; + */ + extraContextEntries: ExtraContextEntry[]; - /** - * New context types - * - * @generated from field: repeated agent.v1.SelectedFile files = 4; - */ - files: SelectedFile[]; + /** + * New context types + * + * @generated from field: repeated agent.v1.SelectedFile files = 4; + */ + files: SelectedFile[]; - /** - * @generated from field: repeated agent.v1.SelectedCodeSelection code_selections = 5; - */ - codeSelections: SelectedCodeSelection[]; + /** + * @generated from field: repeated agent.v1.SelectedCodeSelection code_selections = 5; + */ + codeSelections: SelectedCodeSelection[]; - /** - * @generated from field: repeated agent.v1.SelectedTerminal terminals = 6; - */ - terminals: SelectedTerminal[]; + /** + * @generated from field: repeated agent.v1.SelectedTerminal terminals = 6; + */ + terminals: SelectedTerminal[]; - /** - * @generated from field: repeated agent.v1.SelectedTerminalSelection terminal_selections = 7; - */ - terminalSelections: SelectedTerminalSelection[]; + /** + * @generated from field: repeated agent.v1.SelectedTerminalSelection terminal_selections = 7; + */ + terminalSelections: SelectedTerminalSelection[]; - /** - * @generated from field: repeated agent.v1.SelectedFolder folders = 8; - */ - folders: SelectedFolder[]; + /** + * @generated from field: repeated agent.v1.SelectedFolder folders = 8; + */ + folders: SelectedFolder[]; - /** - * @generated from field: repeated agent.v1.SelectedExternalLink external_links = 9; - */ - externalLinks: SelectedExternalLink[]; + /** + * @generated from field: repeated agent.v1.SelectedExternalLink external_links = 9; + */ + externalLinks: SelectedExternalLink[]; - /** - * @generated from field: repeated agent.v1.SelectedCursorRule cursor_rules = 10; - */ - cursorRules: SelectedCursorRule[]; + /** + * @generated from field: repeated agent.v1.SelectedCursorRule cursor_rules = 10; + */ + cursorRules: SelectedCursorRule[]; - /** - * @generated from field: optional agent.v1.SelectedGitDiff git_diff = 18; - */ - gitDiff?: SelectedGitDiff; + /** + * @generated from field: optional agent.v1.SelectedGitDiff git_diff = 18; + */ + gitDiff?: SelectedGitDiff | undefined; - /** - * @generated from field: optional agent.v1.SelectedGitDiffFromBranchToMain git_diff_from_branch_to_main = 11; - */ - gitDiffFromBranchToMain?: SelectedGitDiffFromBranchToMain; + /** + * @generated from field: optional agent.v1.SelectedGitDiffFromBranchToMain git_diff_from_branch_to_main = 11; + */ + gitDiffFromBranchToMain?: SelectedGitDiffFromBranchToMain | undefined; - /** - * @generated from field: repeated agent.v1.SelectedCursorCommand cursor_commands = 12; - */ - cursorCommands: SelectedCursorCommand[]; + /** + * @generated from field: repeated agent.v1.SelectedCursorCommand cursor_commands = 12; + */ + cursorCommands: SelectedCursorCommand[]; - /** - * @generated from field: repeated agent.v1.SelectedDocumentation documentations = 13; - */ - documentations: SelectedDocumentation[]; + /** + * @generated from field: repeated agent.v1.SelectedDocumentation documentations = 13; + */ + documentations: SelectedDocumentation[]; - /** - * @generated from field: repeated agent.v1.SelectedUIElement ui_elements = 14; - */ - uiElements: SelectedUIElement[]; + /** + * @generated from field: repeated agent.v1.SelectedUIElement ui_elements = 14; + */ + uiElements: SelectedUIElement[]; - /** - * @generated from field: repeated agent.v1.SelectedConsoleLog console_logs = 15; - */ - consoleLogs: SelectedConsoleLog[]; + /** + * @generated from field: repeated agent.v1.SelectedConsoleLog console_logs = 15; + */ + consoleLogs: SelectedConsoleLog[]; - /** - * @generated from field: repeated agent.v1.SelectedGitCommit git_commits = 17; - */ - gitCommits: SelectedGitCommit[]; + /** + * @generated from field: repeated agent.v1.SelectedGitCommit git_commits = 17; + */ + gitCommits: SelectedGitCommit[]; - /** - * @generated from field: repeated agent.v1.SelectedPastChat past_chats = 19; - */ - pastChats: SelectedPastChat[]; + /** + * @generated from field: repeated agent.v1.SelectedPastChat past_chats = 19; + */ + pastChats: SelectedPastChat[]; - /** - * @generated from field: repeated agent.v1.SelectedGitPRDiffSelection git_pr_diff_selections = 20; - */ - gitPrDiffSelections: SelectedGitPRDiffSelection[]; + /** + * @generated from field: repeated agent.v1.SelectedGitPRDiffSelection git_pr_diff_selections = 20; + */ + gitPrDiffSelections: SelectedGitPRDiffSelection[]; - /** - * @generated from field: repeated agent.v1.SelectedPullRequest selected_pull_requests = 21; - */ - selectedPullRequests: SelectedPullRequest[]; + /** + * @generated from field: repeated agent.v1.SelectedPullRequest selected_pull_requests = 21; + */ + selectedPullRequests: SelectedPullRequest[]; - /** - * @generated from field: repeated agent.v1.SelectedSubagent selected_subagents = 22; - */ - selectedSubagents: SelectedSubagent[]; + /** + * @generated from field: repeated agent.v1.SelectedSubagent selected_subagents = 22; + */ + selectedSubagents: SelectedSubagent[]; }; /** * Describes the message agent.v1.SelectedContext. * Use `create(SelectedContextSchema)` to create a new message. */ -export const SelectedContextSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 373); +export const SelectedContextSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 373); /** * InvocationContext represents the context from the external app/integration that triggered this agent request. @@ -11303,287 +11728,274 @@ export const SelectedContextSchema: GenMessage = /*@__PURE__*/ * @generated from message agent.v1.InvocationContext */ export type InvocationContext = Message<"agent.v1.InvocationContext"> & { - /** - * @generated from oneof agent.v1.InvocationContext.data - */ - data: - | { - /** - * @generated from field: agent.v1.InvocationContext_SlackThread slack_thread = 1; - */ - value: InvocationContext_SlackThread; - case: "slackThread"; - } - | { - /** - * @generated from field: agent.v1.InvocationContext_GithubPR github_pr = 2; - */ - value: InvocationContext_GithubPR; - case: "githubPr"; - } - | { - /** - * @generated from field: agent.v1.InvocationContext_IdeState ide_state = 3; - */ - value: InvocationContext_IdeState; - case: "ideState"; - } - | { - /** - * @generated from field: bytes blob_id = 10; - */ - value: Uint8Array; - case: "blobId"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.InvocationContext.data + */ + data: { + /** + * @generated from field: agent.v1.InvocationContext_SlackThread slack_thread = 1; + */ + value: InvocationContext_SlackThread; + case: "slackThread"; + } | { + /** + * @generated from field: agent.v1.InvocationContext_GithubPR github_pr = 2; + */ + value: InvocationContext_GithubPR; + case: "githubPr"; + } | { + /** + * @generated from field: agent.v1.InvocationContext_IdeState ide_state = 3; + */ + value: InvocationContext_IdeState; + case: "ideState"; + } | { + /** + * @generated from field: bytes blob_id = 10; + */ + value: Uint8Array; + case: "blobId"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.InvocationContext. * Use `create(InvocationContextSchema)` to create a new message. */ -export const InvocationContextSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 374); +export const InvocationContextSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 374); /** * @generated from message agent.v1.InvocationContext_SlackThread */ export type InvocationContext_SlackThread = Message<"agent.v1.InvocationContext_SlackThread"> & { - /** - * @generated from field: string thread = 1; - */ - thread: string; + /** + * @generated from field: string thread = 1; + */ + thread: string; - /** - * @generated from field: optional string channel_name = 2; - */ - channelName?: string; + /** + * @generated from field: optional string channel_name = 2; + */ + channelName?: string | undefined; - /** - * @generated from field: optional string channel_purpose = 3; - */ - channelPurpose?: string; + /** + * @generated from field: optional string channel_purpose = 3; + */ + channelPurpose?: string | undefined; - /** - * @generated from field: optional string channel_topic = 4; - */ - channelTopic?: string; + /** + * @generated from field: optional string channel_topic = 4; + */ + channelTopic?: string | undefined; }; /** * Describes the message agent.v1.InvocationContext_SlackThread. * Use `create(InvocationContext_SlackThreadSchema)` to create a new message. */ -export const InvocationContext_SlackThreadSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 375); +export const InvocationContext_SlackThreadSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 375); /** * @generated from message agent.v1.InvocationContext_GithubPR */ export type InvocationContext_GithubPR = Message<"agent.v1.InvocationContext_GithubPR"> & { - /** - * @generated from field: string title = 1; - */ - title: string; + /** + * @generated from field: string title = 1; + */ + title: string; - /** - * @generated from field: string description = 2; - */ - description: string; + /** + * @generated from field: string description = 2; + */ + description: string; - /** - * @generated from field: string comments = 3; - */ - comments: string; + /** + * @generated from field: string comments = 3; + */ + comments: string; - /** - * @generated from field: optional string ci_failures = 4; - */ - ciFailures?: string; + /** + * @generated from field: optional string ci_failures = 4; + */ + ciFailures?: string | undefined; }; /** * Describes the message agent.v1.InvocationContext_GithubPR. * Use `create(InvocationContext_GithubPRSchema)` to create a new message. */ -export const InvocationContext_GithubPRSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 376); +export const InvocationContext_GithubPRSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 376); /** * @generated from message agent.v1.InvocationContext_IdeState */ export type InvocationContext_IdeState = Message<"agent.v1.InvocationContext_IdeState"> & { - /** - * @generated from field: repeated agent.v1.InvocationContext_IdeState_File visible_files = 1; - */ - visibleFiles: InvocationContext_IdeState_File[]; + /** + * @generated from field: repeated agent.v1.InvocationContext_IdeState_File visible_files = 1; + */ + visibleFiles: InvocationContext_IdeState_File[]; - /** - * @generated from field: repeated agent.v1.InvocationContext_IdeState_File recently_viewed_files = 2; - */ - recentlyViewedFiles: InvocationContext_IdeState_File[]; + /** + * @generated from field: repeated agent.v1.InvocationContext_IdeState_File recently_viewed_files = 2; + */ + recentlyViewedFiles: InvocationContext_IdeState_File[]; - /** - * PRs currently being viewed in the review editor (if any) - * - * @generated from field: repeated agent.v1.InvocationContext_IdeState_ViewedPullRequest currently_viewed_prs = 3; - */ - currentlyViewedPrs: InvocationContext_IdeState_ViewedPullRequest[]; + /** + * PRs currently being viewed in the review editor (if any) + * + * @generated from field: repeated agent.v1.InvocationContext_IdeState_ViewedPullRequest currently_viewed_prs = 3; + */ + currentlyViewedPrs: InvocationContext_IdeState_ViewedPullRequest[]; }; /** * Describes the message agent.v1.InvocationContext_IdeState. * Use `create(InvocationContext_IdeStateSchema)` to create a new message. */ -export const InvocationContext_IdeStateSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 377); +export const InvocationContext_IdeStateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 377); /** * @generated from message agent.v1.InvocationContext_IdeState_File */ export type InvocationContext_IdeState_File = Message<"agent.v1.InvocationContext_IdeState_File"> & { - /** - * This is the full path - * - * @generated from field: string path = 1; - */ - path: string; + /** + * This is the full path + * + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: optional string relative_path = 2; - */ - relativePath?: string; + /** + * @generated from field: optional string relative_path = 2; + */ + relativePath?: string | undefined; - /** - * Present if file is currently focused - * - * @generated from field: optional agent.v1.InvocationContext_IdeState_File_CursorPosition cursor_position = 3; - */ - cursorPosition?: InvocationContext_IdeState_File_CursorPosition; + /** + * Present if file is currently focused + * + * @generated from field: optional agent.v1.InvocationContext_IdeState_File_CursorPosition cursor_position = 3; + */ + cursorPosition?: InvocationContext_IdeState_File_CursorPosition | undefined; - /** - * @generated from field: int32 total_lines = 4; - */ - totalLines: number; + /** + * @generated from field: int32 total_lines = 4; + */ + totalLines: number; - /** - * Present for terminal files - * - * @generated from field: optional string active_command = 5; - */ - activeCommand?: string; + /** + * Present for terminal files + * + * @generated from field: optional string active_command = 5; + */ + activeCommand?: string | undefined; }; /** * Describes the message agent.v1.InvocationContext_IdeState_File. * Use `create(InvocationContext_IdeState_FileSchema)` to create a new message. */ -export const InvocationContext_IdeState_FileSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 378); +export const InvocationContext_IdeState_FileSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 378); /** * @generated from message agent.v1.InvocationContext_IdeState_File_CursorPosition */ -export type InvocationContext_IdeState_File_CursorPosition = - Message<"agent.v1.InvocationContext_IdeState_File_CursorPosition"> & { - /** - * @generated from field: int32 line = 1; - */ - line: number; +export type InvocationContext_IdeState_File_CursorPosition = Message<"agent.v1.InvocationContext_IdeState_File_CursorPosition"> & { + /** + * @generated from field: int32 line = 1; + */ + line: number; - /** - * @generated from field: string text = 2; - */ - text: string; - }; + /** + * @generated from field: string text = 2; + */ + text: string; +}; /** * Describes the message agent.v1.InvocationContext_IdeState_File_CursorPosition. * Use `create(InvocationContext_IdeState_File_CursorPositionSchema)` to create a new message. */ -export const InvocationContext_IdeState_File_CursorPositionSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 379); +export const InvocationContext_IdeState_File_CursorPositionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 379); /** * Information about a PR currently being viewed in a review editor * * @generated from message agent.v1.InvocationContext_IdeState_ViewedPullRequest */ -export type InvocationContext_IdeState_ViewedPullRequest = - Message<"agent.v1.InvocationContext_IdeState_ViewedPullRequest"> & { - /** - * @generated from field: int32 number = 1; - */ - number: number; +export type InvocationContext_IdeState_ViewedPullRequest = Message<"agent.v1.InvocationContext_IdeState_ViewedPullRequest"> & { + /** + * @generated from field: int32 number = 1; + */ + number: number; - /** - * @generated from field: string url = 2; - */ - url: string; + /** + * @generated from field: string url = 2; + */ + url: string; - /** - * @generated from field: optional string title = 3; - */ - title?: string; + /** + * @generated from field: optional string title = 3; + */ + title?: string | undefined; - /** - * Path to the folder containing PR details (diffs, metadata, etc.) - * - * @generated from field: optional string folder_path = 4; - */ - folderPath?: string; + /** + * Path to the folder containing PR details (diffs, metadata, etc.) + * + * @generated from field: optional string folder_path = 4; + */ + folderPath?: string | undefined; - /** - * Summary JSON containing file list and diff sizes (contents of summary.json) - * - * @generated from field: optional string summary_json = 5; - */ - summaryJson?: string; + /** + * Summary JSON containing file list and diff sizes (contents of summary.json) + * + * @generated from field: optional string summary_json = 5; + */ + summaryJson?: string | undefined; - /** - * PR description/body - * - * @generated from field: optional string description = 6; - */ - description?: string; - }; + /** + * PR description/body + * + * @generated from field: optional string description = 6; + */ + description?: string | undefined; +}; /** * Describes the message agent.v1.InvocationContext_IdeState_ViewedPullRequest. * Use `create(InvocationContext_IdeState_ViewedPullRequestSchema)` to create a new message. */ -export const InvocationContext_IdeState_ViewedPullRequestSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 380); +export const InvocationContext_IdeState_ViewedPullRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 380); /** * @generated from message agent.v1.SetupVmEnvironmentArgs */ export type SetupVmEnvironmentArgs = Message<"agent.v1.SetupVmEnvironmentArgs"> & { - /** - * Command to install runtime dependencies (e.g., "npm install") - * - * @generated from field: string install_command = 2; - */ - installCommand: string; + /** + * Command to install runtime dependencies (e.g., "npm install") + * + * @generated from field: string install_command = 2; + */ + installCommand: string; - /** - * @generated from field: string start_command = 3; - */ - startCommand: string; + /** + * @generated from field: string start_command = 3; + */ + startCommand: string; }; /** * Describes the message agent.v1.SetupVmEnvironmentArgs. * Use `create(SetupVmEnvironmentArgsSchema)` to create a new message. */ -export const SetupVmEnvironmentArgsSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 381); +export const SetupVmEnvironmentArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 381); /** * Result of VM environment setup operations @@ -11591,42 +12003,39 @@ export const SetupVmEnvironmentArgsSchema: GenMessage = * @generated from message agent.v1.SetupVmEnvironmentResult */ export type SetupVmEnvironmentResult = Message<"agent.v1.SetupVmEnvironmentResult"> & { - /** - * @generated from oneof agent.v1.SetupVmEnvironmentResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.SetupVmEnvironmentSuccess success = 1; - */ - value: SetupVmEnvironmentSuccess; - case: "success"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.SetupVmEnvironmentResult.result + */ + result: { + /** + * @generated from field: agent.v1.SetupVmEnvironmentSuccess success = 1; + */ + value: SetupVmEnvironmentSuccess; + case: "success"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.SetupVmEnvironmentResult. * Use `create(SetupVmEnvironmentResultSchema)` to create a new message. */ -export const SetupVmEnvironmentResultSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 382); +export const SetupVmEnvironmentResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 382); /** * Successful VM environment setup result * * @generated from message agent.v1.SetupVmEnvironmentSuccess */ -export type SetupVmEnvironmentSuccess = Message<"agent.v1.SetupVmEnvironmentSuccess"> & {}; +export type SetupVmEnvironmentSuccess = Message<"agent.v1.SetupVmEnvironmentSuccess"> & { +}; /** * Describes the message agent.v1.SetupVmEnvironmentSuccess. * Use `create(SetupVmEnvironmentSuccessSchema)` to create a new message. */ -export const SetupVmEnvironmentSuccessSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 383); +export const SetupVmEnvironmentSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 383); /** * Tool call structure for SetupVmEnvironment @@ -11634,982 +12043,1074 @@ export const SetupVmEnvironmentSuccessSchema: GenMessage & { - /** - * Arguments for the tool call - * - * @generated from field: agent.v1.SetupVmEnvironmentArgs args = 1; - */ - args?: SetupVmEnvironmentArgs; + /** + * Arguments for the tool call + * + * @generated from field: agent.v1.SetupVmEnvironmentArgs args = 1; + */ + args?: SetupVmEnvironmentArgs | undefined; - /** - * Result of the tool call (populated after execution) - * - * @generated from field: agent.v1.SetupVmEnvironmentResult result = 2; - */ - result?: SetupVmEnvironmentResult; + /** + * Result of the tool call (populated after execution) + * + * @generated from field: agent.v1.SetupVmEnvironmentResult result = 2; + */ + result?: SetupVmEnvironmentResult | undefined; }; /** * Describes the message agent.v1.SetupVmEnvironmentToolCall. * Use `create(SetupVmEnvironmentToolCallSchema)` to create a new message. */ -export const SetupVmEnvironmentToolCallSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 384); +export const SetupVmEnvironmentToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 384); /** * @generated from message agent.v1.ShellCommandParsingResult */ export type ShellCommandParsingResult = Message<"agent.v1.ShellCommandParsingResult"> & { - /** - * @generated from field: bool parsing_failed = 1; - */ - parsingFailed: boolean; + /** + * @generated from field: bool parsing_failed = 1; + */ + parsingFailed: boolean; - /** - * @generated from field: repeated agent.v1.ShellCommandParsingResult_ExecutableCommand executable_commands = 2; - */ - executableCommands: ShellCommandParsingResult_ExecutableCommand[]; + /** + * @generated from field: repeated agent.v1.ShellCommandParsingResult_ExecutableCommand executable_commands = 2; + */ + executableCommands: ShellCommandParsingResult_ExecutableCommand[]; - /** - * @generated from field: bool has_redirects = 3; - */ - hasRedirects: boolean; + /** + * @generated from field: bool has_redirects = 3; + */ + hasRedirects: boolean; - /** - * @generated from field: bool has_command_substitution = 4; - */ - hasCommandSubstitution: boolean; + /** + * @generated from field: bool has_command_substitution = 4; + */ + hasCommandSubstitution: boolean; + + /** + * @generated from field: optional bool all_redirects_are_dev_null = 5; + */ + allRedirectsAreDevNull?: boolean | undefined; + + /** + * @generated from field: repeated agent.v1.ShellCommandParsingResult_Redirect redirects = 6; + */ + redirects: ShellCommandParsingResult_Redirect[]; }; /** * Describes the message agent.v1.ShellCommandParsingResult. * Use `create(ShellCommandParsingResultSchema)` to create a new message. */ -export const ShellCommandParsingResultSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 385); +export const ShellCommandParsingResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 385); /** * @generated from message agent.v1.ShellCommandParsingResult_ExecutableCommandArg */ -export type ShellCommandParsingResult_ExecutableCommandArg = - Message<"agent.v1.ShellCommandParsingResult_ExecutableCommandArg"> & { - /** - * @generated from field: string type = 1; - */ - type: string; +export type ShellCommandParsingResult_ExecutableCommandArg = Message<"agent.v1.ShellCommandParsingResult_ExecutableCommandArg"> & { + /** + * @generated from field: string type = 1; + */ + type: string; - /** - * @generated from field: string value = 2; - */ - value: string; - }; + /** + * @generated from field: string value = 2; + */ + value: string; +}; /** * Describes the message agent.v1.ShellCommandParsingResult_ExecutableCommandArg. * Use `create(ShellCommandParsingResult_ExecutableCommandArgSchema)` to create a new message. */ -export const ShellCommandParsingResult_ExecutableCommandArgSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 386); +export const ShellCommandParsingResult_ExecutableCommandArgSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 386); /** * @generated from message agent.v1.ShellCommandParsingResult_ExecutableCommand */ -export type ShellCommandParsingResult_ExecutableCommand = - Message<"agent.v1.ShellCommandParsingResult_ExecutableCommand"> & { - /** - * @generated from field: string name = 1; - */ - name: string; +export type ShellCommandParsingResult_ExecutableCommand = Message<"agent.v1.ShellCommandParsingResult_ExecutableCommand"> & { + /** + * @generated from field: string name = 1; + */ + name: string; - /** - * @generated from field: repeated agent.v1.ShellCommandParsingResult_ExecutableCommandArg args = 2; - */ - args: ShellCommandParsingResult_ExecutableCommandArg[]; + /** + * @generated from field: repeated agent.v1.ShellCommandParsingResult_ExecutableCommandArg args = 2; + */ + args: ShellCommandParsingResult_ExecutableCommandArg[]; - /** - * @generated from field: string full_text = 3; - */ - fullText: string; - }; + /** + * @generated from field: string full_text = 3; + */ + fullText: string; +}; /** * Describes the message agent.v1.ShellCommandParsingResult_ExecutableCommand. * Use `create(ShellCommandParsingResult_ExecutableCommandSchema)` to create a new message. */ -export const ShellCommandParsingResult_ExecutableCommandSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 387); +export const ShellCommandParsingResult_ExecutableCommandSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 387); /** * @generated from message agent.v1.ShellArgs */ export type ShellArgs = Message<"agent.v1.ShellArgs"> & { - /** - * @generated from field: string command = 1; - */ - command: string; + /** + * @generated from field: string command = 1; + */ + command: string; - /** - * @generated from field: string working_directory = 2; - */ - workingDirectory: string; + /** + * @generated from field: string working_directory = 2; + */ + workingDirectory: string; - /** - * @generated from field: int32 timeout = 3; - */ - timeout: number; + /** + * @generated from field: int32 timeout = 3; + */ + timeout: number; - /** - * @generated from field: string tool_call_id = 4; - */ - toolCallId: string; + /** + * @generated from field: string tool_call_id = 4; + */ + toolCallId: string; - /** - * @generated from field: repeated string simple_commands = 5; - */ - simpleCommands: string[]; + /** + * @generated from field: repeated string simple_commands = 5; + */ + simpleCommands: string[]; - /** - * @generated from field: bool has_input_redirect = 6; - */ - hasInputRedirect: boolean; + /** + * @generated from field: bool has_input_redirect = 6; + */ + hasInputRedirect: boolean; - /** - * @generated from field: bool has_output_redirect = 7; - */ - hasOutputRedirect: boolean; + /** + * @generated from field: bool has_output_redirect = 7; + */ + hasOutputRedirect: boolean; - /** - * Deprecated: use parsing_result instead @deprecated simpleCommands = []; Deprecated: use parsing_result instead @deprecated hasInputRedirect = false; Deprecated: use parsing_result instead @deprecated hasOutputRedirect = false; - * - * @generated from field: agent.v1.ShellCommandParsingResult parsing_result = 8; - */ - parsingResult?: ShellCommandParsingResult; + /** + * Deprecated: use parsing_result instead @deprecated simpleCommands = []; Deprecated: use parsing_result instead @deprecated hasInputRedirect = false; Deprecated: use parsing_result instead @deprecated hasOutputRedirect = false; + * + * @generated from field: agent.v1.ShellCommandParsingResult parsing_result = 8; + */ + parsingResult?: ShellCommandParsingResult | undefined; - /** - * @generated from field: optional agent.v1.SandboxPolicy requested_sandbox_policy = 9; - */ - requestedSandboxPolicy?: SandboxPolicy; + /** + * @generated from field: optional agent.v1.SandboxPolicy requested_sandbox_policy = 9; + */ + requestedSandboxPolicy?: SandboxPolicy | undefined; - /** - * If output size exceeds this threshold (in bytes), write to file instead of inline. If unset or 0, always use inline output. - * - * @generated from field: optional uint64 file_output_threshold_bytes = 10; - */ - fileOutputThresholdBytes?: bigint; + /** + * If output size exceeds this threshold (in bytes), write to file instead of inline. If unset or 0, always use inline output. + * + * @generated from field: optional uint64 file_output_threshold_bytes = 10; + */ + fileOutputThresholdBytes?: bigint | undefined; - /** - * @generated from field: bool is_background = 11; - */ - isBackground: boolean; + /** + * @generated from field: bool is_background = 11; + */ + isBackground: boolean; - /** - * @generated from field: bool skip_approval = 12; - */ - skipApproval: boolean; + /** + * @generated from field: bool skip_approval = 12; + */ + skipApproval: boolean; - /** - * @generated from field: int32 timeout_behavior = 13; - */ - timeoutBehavior: number; + /** + * @generated from field: int32 timeout_behavior = 13; + */ + timeoutBehavior: number; - /** - * Hard timeout: kill the command after this many ms, even if running in background - * - * @generated from field: optional int32 hard_timeout = 14; - */ - hardTimeout?: number; + /** + * Hard timeout: kill the command after this many ms, even if running in background + * + * @generated from field: optional int32 hard_timeout = 14; + */ + hardTimeout?: number | undefined; + + /** + * @generated from field: optional string description = 15; + */ + description?: string | undefined; + + /** + * @generated from field: optional agent.v1.CommandClassifierResult classifier_result = 16; + */ + classifierResult?: CommandClassifierResult | undefined; + + /** + * @generated from field: bool close_stdin = 17; + */ + closeStdin: boolean; + + /** + * @generated from field: optional agent.v1.ShellOutputNotificationConfig output_notification = 18; + */ + outputNotification?: ShellOutputNotificationConfig | undefined; + + /** + * @generated from field: optional agent.v1.SmartModeApproval smart_mode_approval = 19; + */ + smartModeApproval?: SmartModeApproval | undefined; + + /** + * @generated from field: optional agent.v1.ShellHookApprovalRequirement hook_approval_requirement = 20; + */ + hookApprovalRequirement?: ShellHookApprovalRequirement | undefined; + + /** + * @generated from field: optional string conversation_id = 21; + */ + conversationId?: string | undefined; }; /** * Describes the message agent.v1.ShellArgs. * Use `create(ShellArgsSchema)` to create a new message. */ -export const ShellArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 388); +export const ShellArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 388); /** * @generated from message agent.v1.ShellResult */ export type ShellResult = Message<"agent.v1.ShellResult"> & { - /** - * @generated from field: optional agent.v1.SandboxPolicy sandbox_policy = 101; - */ - sandboxPolicy?: SandboxPolicy; + /** + * @generated from field: optional agent.v1.SandboxPolicy sandbox_policy = 101; + */ + sandboxPolicy?: SandboxPolicy | undefined; - /** - * Rendering is affected by this flag, pass forward from args. - * - * @generated from field: optional bool is_background = 102; - */ - isBackground?: boolean; + /** + * Rendering is affected by this flag, pass forward from args. + * + * @generated from field: optional bool is_background = 102; + */ + isBackground?: boolean | undefined; - /** - * Rendering is affected by this flag, pass forward from args. - * - * @generated from field: optional string terminals_folder = 103; - */ - terminalsFolder?: string; + /** + * Rendering is affected by this flag, pass forward from args. + * + * @generated from field: optional string terminals_folder = 103; + */ + terminalsFolder?: string | undefined; - /** - * Process ID, used for backgrounded shells. - * - * @generated from field: optional uint32 pid = 104; - */ - pid?: number; + /** + * Process ID, used for backgrounded shells. + * + * @generated from field: optional uint32 pid = 104; + */ + pid?: number | undefined; - /** - * @generated from oneof agent.v1.ShellResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.ShellSuccess success = 1; - */ - value: ShellSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.ShellFailure failure = 2; - */ - value: ShellFailure; - case: "failure"; - } - | { - /** - * @generated from field: agent.v1.ShellTimeout timeout = 3; - */ - value: ShellTimeout; - case: "timeout"; - } - | { - /** - * @generated from field: agent.v1.ShellRejected rejected = 4; - */ - value: ShellRejected; - case: "rejected"; - } - | { - /** - * @generated from field: agent.v1.ShellSpawnError spawn_error = 5; - */ - value: ShellSpawnError; - case: "spawnError"; - } - | { - /** - * @generated from field: agent.v1.ShellPermissionDenied permission_denied = 7; - */ - value: ShellPermissionDenied; - case: "permissionDenied"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ShellResult.result + */ + result: { + /** + * @generated from field: agent.v1.ShellSuccess success = 1; + */ + value: ShellSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.ShellFailure failure = 2; + */ + value: ShellFailure; + case: "failure"; + } | { + /** + * @generated from field: agent.v1.ShellTimeout timeout = 3; + */ + value: ShellTimeout; + case: "timeout"; + } | { + /** + * @generated from field: agent.v1.ShellRejected rejected = 4; + */ + value: ShellRejected; + case: "rejected"; + } | { + /** + * @generated from field: agent.v1.ShellSpawnError spawn_error = 5; + */ + value: ShellSpawnError; + case: "spawnError"; + } | { + /** + * @generated from field: agent.v1.ShellPermissionDenied permission_denied = 7; + */ + value: ShellPermissionDenied; + case: "permissionDenied"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ShellResult. * Use `create(ShellResultSchema)` to create a new message. */ -export const ShellResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 389); +export const ShellResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 389); /** * @generated from message agent.v1.ShellStreamStdout */ export type ShellStreamStdout = Message<"agent.v1.ShellStreamStdout"> & { - /** - * @generated from field: string data = 1; - */ - data: string; + /** + * @generated from field: string data = 1; + */ + data: string; }; /** * Describes the message agent.v1.ShellStreamStdout. * Use `create(ShellStreamStdoutSchema)` to create a new message. */ -export const ShellStreamStdoutSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 390); +export const ShellStreamStdoutSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 390); /** * @generated from message agent.v1.ShellStreamStderr */ export type ShellStreamStderr = Message<"agent.v1.ShellStreamStderr"> & { - /** - * @generated from field: string data = 1; - */ - data: string; + /** + * @generated from field: string data = 1; + */ + data: string; }; /** * Describes the message agent.v1.ShellStreamStderr. * Use `create(ShellStreamStderrSchema)` to create a new message. */ -export const ShellStreamStderrSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 391); +export const ShellStreamStderrSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 391); /** * @generated from message agent.v1.ShellStreamExit */ export type ShellStreamExit = Message<"agent.v1.ShellStreamExit"> & { - /** - * @generated from field: uint32 code = 1; - */ - code: number; + /** + * @generated from field: uint32 code = 1; + */ + code: number; - /** - * @generated from field: string cwd = 2; - */ - cwd: string; + /** + * @generated from field: string cwd = 2; + */ + cwd: string; - /** - * @generated from field: optional agent.v1.OutputLocation output_location = 3; - */ - outputLocation?: OutputLocation; + /** + * @generated from field: optional agent.v1.OutputLocation output_location = 3; + */ + outputLocation?: OutputLocation | undefined; - /** - * @generated from field: bool aborted = 4; - */ - aborted: boolean; + /** + * @generated from field: bool aborted = 4; + */ + aborted: boolean; - /** - * If aborted is true, this field indicates the reason for the abort - * - * @generated from field: optional int32 abort_reason = 5; - */ - abortReason?: number; + /** + * If aborted is true, this field indicates the reason for the abort + * + * @generated from field: optional int32 abort_reason = 5; + */ + abortReason?: number | undefined; + + /** + * @generated from field: optional int32 local_execution_time_ms = 6; + */ + localExecutionTimeMs?: number | undefined; }; /** * Describes the message agent.v1.ShellStreamExit. * Use `create(ShellStreamExitSchema)` to create a new message. */ -export const ShellStreamExitSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 392); +export const ShellStreamExitSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 392); /** * @generated from message agent.v1.ShellStreamStart */ export type ShellStreamStart = Message<"agent.v1.ShellStreamStart"> & { - /** - * @generated from field: optional agent.v1.SandboxPolicy sandbox_policy = 1; - */ - sandboxPolicy?: SandboxPolicy; + /** + * @generated from field: optional agent.v1.SandboxPolicy sandbox_policy = 1; + */ + sandboxPolicy?: SandboxPolicy | undefined; }; /** * Describes the message agent.v1.ShellStreamStart. * Use `create(ShellStreamStartSchema)` to create a new message. */ -export const ShellStreamStartSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 393); +export const ShellStreamStartSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 393); /** * @generated from message agent.v1.ShellStreamBackgrounded */ export type ShellStreamBackgrounded = Message<"agent.v1.ShellStreamBackgrounded"> & { - /** - * @generated from field: uint32 shell_id = 1; - */ - shellId: number; + /** + * @generated from field: uint32 shell_id = 1; + */ + shellId: number; - /** - * @generated from field: string command = 2; - */ - command: string; + /** + * @generated from field: string command = 2; + */ + command: string; - /** - * @generated from field: string working_directory = 3; - */ - workingDirectory: string; + /** + * @generated from field: string working_directory = 3; + */ + workingDirectory: string; - /** - * @generated from field: optional uint32 pid = 4; - */ - pid?: number; + /** + * @generated from field: optional uint32 pid = 4; + */ + pid?: number | undefined; - /** - * The ms_to_wait value that was used for backgrounding, for display purposes - * - * @generated from field: optional int32 ms_to_wait = 5; - */ - msToWait?: number; + /** + * The ms_to_wait value that was used for backgrounding, for display purposes + * + * @generated from field: optional int32 ms_to_wait = 5; + */ + msToWait?: number | undefined; + + /** + * @generated from field: optional agent.v1.ShellBackgroundReason reason = 6; + */ + reason?: ShellBackgroundReason | undefined; }; /** * Describes the message agent.v1.ShellStreamBackgrounded. * Use `create(ShellStreamBackgroundedSchema)` to create a new message. */ -export const ShellStreamBackgroundedSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 394); +export const ShellStreamBackgroundedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 394); /** * @generated from message agent.v1.ShellStream */ export type ShellStream = Message<"agent.v1.ShellStream"> & { - /** - * @generated from oneof agent.v1.ShellStream.event - */ - event: - | { - /** - * @generated from field: agent.v1.ShellStreamStdout stdout = 1; - */ - value: ShellStreamStdout; - case: "stdout"; - } - | { - /** - * @generated from field: agent.v1.ShellStreamStderr stderr = 2; - */ - value: ShellStreamStderr; - case: "stderr"; - } - | { - /** - * @generated from field: agent.v1.ShellStreamExit exit = 3; - */ - value: ShellStreamExit; - case: "exit"; - } - | { - /** - * @generated from field: agent.v1.ShellStreamStart start = 4; - */ - value: ShellStreamStart; - case: "start"; - } - | { - /** - * @generated from field: agent.v1.ShellRejected rejected = 5; - */ - value: ShellRejected; - case: "rejected"; - } - | { - /** - * @generated from field: agent.v1.ShellPermissionDenied permission_denied = 6; - */ - value: ShellPermissionDenied; - case: "permissionDenied"; - } - | { - /** - * @generated from field: agent.v1.ShellStreamBackgrounded backgrounded = 7; - */ - value: ShellStreamBackgrounded; - case: "backgrounded"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ShellStream.event + */ + event: { + /** + * @generated from field: agent.v1.ShellStreamStdout stdout = 1; + */ + value: ShellStreamStdout; + case: "stdout"; + } | { + /** + * @generated from field: agent.v1.ShellStreamStderr stderr = 2; + */ + value: ShellStreamStderr; + case: "stderr"; + } | { + /** + * @generated from field: agent.v1.ShellStreamExit exit = 3; + */ + value: ShellStreamExit; + case: "exit"; + } | { + /** + * @generated from field: agent.v1.ShellStreamStart start = 4; + */ + value: ShellStreamStart; + case: "start"; + } | { + /** + * @generated from field: agent.v1.ShellRejected rejected = 5; + */ + value: ShellRejected; + case: "rejected"; + } | { + /** + * @generated from field: agent.v1.ShellPermissionDenied permission_denied = 6; + */ + value: ShellPermissionDenied; + case: "permissionDenied"; + } | { + /** + * @generated from field: agent.v1.ShellStreamBackgrounded backgrounded = 7; + */ + value: ShellStreamBackgrounded; + case: "backgrounded"; + } | { + /** + * @generated from field: agent.v1.ShellStreamHookContext hook_context = 8; + */ + value: ShellStreamHookContext; + case: "hookContext"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ShellStream. * Use `create(ShellStreamSchema)` to create a new message. */ -export const ShellStreamSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 395); +export const ShellStreamSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 395); /** * @generated from message agent.v1.OutputLocation */ export type OutputLocation = Message<"agent.v1.OutputLocation"> & { - /** - * Absolute path to the output file - * - * @generated from field: string file_path = 1; - */ - filePath: string; + /** + * Absolute path to the output file + * + * @generated from field: string file_path = 1; + */ + filePath: string; - /** - * Size of the output in bytes - * - * @generated from field: int64 size_bytes = 2; - */ - sizeBytes: bigint; + /** + * Size of the output in bytes + * + * @generated from field: int64 size_bytes = 2; + */ + sizeBytes: bigint; - /** - * Number of lines in the output - * - * @generated from field: int64 line_count = 3; - */ - lineCount: bigint; + /** + * Number of lines in the output + * + * @generated from field: int64 line_count = 3; + */ + lineCount: bigint; }; /** * Describes the message agent.v1.OutputLocation. * Use `create(OutputLocationSchema)` to create a new message. */ -export const OutputLocationSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 396); +export const OutputLocationSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 396); /** * @generated from message agent.v1.ShellSuccess */ export type ShellSuccess = Message<"agent.v1.ShellSuccess"> & { - /** - * @generated from field: string command = 1; - */ - command: string; + /** + * @generated from field: string command = 1; + */ + command: string; - /** - * @generated from field: string working_directory = 2; - */ - workingDirectory: string; + /** + * @generated from field: string working_directory = 2; + */ + workingDirectory: string; - /** - * @generated from field: int32 exit_code = 3; - */ - exitCode: number; + /** + * @generated from field: int32 exit_code = 3; + */ + exitCode: number; - /** - * @generated from field: string signal = 4; - */ - signal: string; + /** + * @generated from field: string signal = 4; + */ + signal: string; - /** - * Inline stdout - populated when write_output_to_file is false, empty when true - * - * @generated from field: string stdout = 5; - */ - stdout: string; + /** + * Inline stdout - populated when write_output_to_file is false, empty when true + * + * @generated from field: string stdout = 5; + */ + stdout: string; - /** - * Inline stderr - populated when write_output_to_file is false, empty when true - * - * @generated from field: string stderr = 6; - */ - stderr: string; + /** + * Inline stderr - populated when write_output_to_file is false, empty when true + * + * @generated from field: string stderr = 6; + */ + stderr: string; - /** - * @generated from field: int32 execution_time = 7; - */ - executionTime: number; + /** + * @generated from field: int32 execution_time = 7; + */ + executionTime: number; - /** - * File-based output - populated when write_output_to_file is true (chronologically merged stdout+stderr) - * - * @generated from field: optional agent.v1.OutputLocation output_location = 8; - */ - outputLocation?: OutputLocation; + /** + * File-based output - populated when write_output_to_file is true (chronologically merged stdout+stderr) + * + * @generated from field: optional agent.v1.OutputLocation output_location = 8; + */ + outputLocation?: OutputLocation | undefined; - /** - * Used by background shell executor - * - * @generated from field: optional uint32 shell_id = 9; - */ - shellId?: number; + /** + * Used by background shell executor + * + * @generated from field: optional uint32 shell_id = 9; + */ + shellId?: number | undefined; - /** - * @generated from field: optional string interleaved_output = 10; - */ - interleavedOutput?: string; + /** + * @generated from field: optional string interleaved_output = 10; + */ + interleavedOutput?: string | undefined; - /** - * Process ID, used for backgrounded shells - * - * @generated from field: optional uint32 pid = 11; - */ - pid?: number; + /** + * Process ID, used for backgrounded shells + * + * @generated from field: optional uint32 pid = 11; + */ + pid?: number | undefined; - /** - * The ms_to_wait value used for backgrounding (for display in result) - * - * @generated from field: optional int32 ms_to_wait = 12; - */ - msToWait?: number; + /** + * The ms_to_wait value used for backgrounding (for display in result) + * + * @generated from field: optional int32 ms_to_wait = 12; + */ + msToWait?: number | undefined; + + /** + * @generated from field: optional int32 local_execution_time_ms = 13; + */ + localExecutionTimeMs?: number | undefined; + + /** + * @generated from field: optional agent.v1.ShellBackgroundReason background_reason = 14; + */ + backgroundReason?: ShellBackgroundReason | undefined; + + /** + * @generated from field: optional string output_head = 15; + */ + outputHead?: string | undefined; + + /** + * @generated from field: optional string output_tail = 16; + */ + outputTail?: string | undefined; + + /** + * @generated from field: optional uint32 elided_chars = 17; + */ + elidedChars?: number | undefined; }; /** * Describes the message agent.v1.ShellSuccess. * Use `create(ShellSuccessSchema)` to create a new message. */ -export const ShellSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 397); +export const ShellSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 397); /** * @generated from message agent.v1.ShellFailure */ export type ShellFailure = Message<"agent.v1.ShellFailure"> & { - /** - * @generated from field: string command = 1; - */ - command: string; + /** + * @generated from field: string command = 1; + */ + command: string; - /** - * @generated from field: string working_directory = 2; - */ - workingDirectory: string; + /** + * @generated from field: string working_directory = 2; + */ + workingDirectory: string; - /** - * @generated from field: int32 exit_code = 3; - */ - exitCode: number; + /** + * @generated from field: int32 exit_code = 3; + */ + exitCode: number; - /** - * @generated from field: string signal = 4; - */ - signal: string; + /** + * @generated from field: string signal = 4; + */ + signal: string; - /** - * Inline stdout - populated when write_output_to_file is false, empty when true - * - * @generated from field: string stdout = 5; - */ - stdout: string; + /** + * Inline stdout - populated when write_output_to_file is false, empty when true + * + * @generated from field: string stdout = 5; + */ + stdout: string; - /** - * Inline stderr - populated when write_output_to_file is false, empty when true - * - * @generated from field: string stderr = 6; - */ - stderr: string; + /** + * Inline stderr - populated when write_output_to_file is false, empty when true + * + * @generated from field: string stderr = 6; + */ + stderr: string; - /** - * @generated from field: int32 execution_time = 7; - */ - executionTime: number; + /** + * @generated from field: int32 execution_time = 7; + */ + executionTime: number; - /** - * File-based output - populated when write_output_to_file is true (chronologically merged stdout+stderr) - * - * @generated from field: optional agent.v1.OutputLocation output_location = 8; - */ - outputLocation?: OutputLocation; + /** + * File-based output - populated when write_output_to_file is true (chronologically merged stdout+stderr) + * + * @generated from field: optional agent.v1.OutputLocation output_location = 8; + */ + outputLocation?: OutputLocation | undefined; - /** - * @generated from field: optional string interleaved_output = 9; - */ - interleavedOutput?: string; + /** + * @generated from field: optional string interleaved_output = 9; + */ + interleavedOutput?: string | undefined; - /** - * If the command was aborted, this indicates the reason - * - * @generated from field: optional int32 abort_reason = 10; - */ - abortReason?: number; + /** + * If the command was aborted, this indicates the reason + * + * @generated from field: optional int32 abort_reason = 10; + */ + abortReason?: number | undefined; - /** - * Whether the command was aborted (by user or timeout) - * - * @generated from field: bool aborted = 11; - */ - aborted: boolean; + /** + * Whether the command was aborted (by user or timeout) + * + * @generated from field: bool aborted = 11; + */ + aborted: boolean; + + /** + * @generated from field: optional int32 local_execution_time_ms = 12; + */ + localExecutionTimeMs?: number | undefined; + + /** + * @generated from field: optional string output_head = 13; + */ + outputHead?: string | undefined; + + /** + * @generated from field: optional string output_tail = 14; + */ + outputTail?: string | undefined; + + /** + * @generated from field: optional uint32 elided_chars = 15; + */ + elidedChars?: number | undefined; }; /** * Describes the message agent.v1.ShellFailure. * Use `create(ShellFailureSchema)` to create a new message. */ -export const ShellFailureSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 398); +export const ShellFailureSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 398); /** * @generated from message agent.v1.ShellTimeout */ export type ShellTimeout = Message<"agent.v1.ShellTimeout"> & { - /** - * @generated from field: string command = 1; - */ - command: string; + /** + * @generated from field: string command = 1; + */ + command: string; - /** - * @generated from field: string working_directory = 2; - */ - workingDirectory: string; + /** + * @generated from field: string working_directory = 2; + */ + workingDirectory: string; - /** - * @generated from field: int32 timeout_ms = 3; - */ - timeoutMs: number; + /** + * @generated from field: int32 timeout_ms = 3; + */ + timeoutMs: number; }; /** * Describes the message agent.v1.ShellTimeout. * Use `create(ShellTimeoutSchema)` to create a new message. */ -export const ShellTimeoutSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 399); +export const ShellTimeoutSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 399); /** * @generated from message agent.v1.ShellRejected */ export type ShellRejected = Message<"agent.v1.ShellRejected"> & { - /** - * @generated from field: string command = 1; - */ - command: string; + /** + * @generated from field: string command = 1; + */ + command: string; - /** - * @generated from field: string working_directory = 2; - */ - workingDirectory: string; + /** + * @generated from field: string working_directory = 2; + */ + workingDirectory: string; - /** - * @generated from field: string reason = 3; - */ - reason: string; + /** + * @generated from field: string reason = 3; + */ + reason: string; - /** - * @generated from field: bool is_readonly = 4; - */ - isReadonly: boolean; + /** + * @generated from field: bool is_readonly = 4; + */ + isReadonly: boolean; }; /** * Describes the message agent.v1.ShellRejected. * Use `create(ShellRejectedSchema)` to create a new message. */ -export const ShellRejectedSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 400); +export const ShellRejectedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 400); /** * @generated from message agent.v1.ShellPermissionDenied */ export type ShellPermissionDenied = Message<"agent.v1.ShellPermissionDenied"> & { - /** - * @generated from field: string command = 1; - */ - command: string; + /** + * @generated from field: string command = 1; + */ + command: string; - /** - * @generated from field: string working_directory = 2; - */ - workingDirectory: string; + /** + * @generated from field: string working_directory = 2; + */ + workingDirectory: string; - /** - * @generated from field: string error = 3; - */ - error: string; + /** + * @generated from field: string error = 3; + */ + error: string; - /** - * @generated from field: bool is_readonly = 4; - */ - isReadonly: boolean; + /** + * @generated from field: bool is_readonly = 4; + */ + isReadonly: boolean; }; /** * Describes the message agent.v1.ShellPermissionDenied. * Use `create(ShellPermissionDeniedSchema)` to create a new message. */ -export const ShellPermissionDeniedSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 401); +export const ShellPermissionDeniedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 401); /** * @generated from message agent.v1.ShellSpawnError */ export type ShellSpawnError = Message<"agent.v1.ShellSpawnError"> & { - /** - * @generated from field: string command = 1; - */ - command: string; + /** + * @generated from field: string command = 1; + */ + command: string; - /** - * @generated from field: string working_directory = 2; - */ - workingDirectory: string; + /** + * @generated from field: string working_directory = 2; + */ + workingDirectory: string; - /** - * @generated from field: string error = 3; - */ - error: string; + /** + * @generated from field: string error = 3; + */ + error: string; }; /** * Describes the message agent.v1.ShellSpawnError. * Use `create(ShellSpawnErrorSchema)` to create a new message. */ -export const ShellSpawnErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 402); +export const ShellSpawnErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 402); /** * @generated from message agent.v1.ShellPartialResult */ export type ShellPartialResult = Message<"agent.v1.ShellPartialResult"> & { - /** - * @generated from field: string stdout_delta = 1; - */ - stdoutDelta: string; + /** + * @generated from field: string stdout_delta = 1; + */ + stdoutDelta: string; - /** - * @generated from field: string stderr_delta = 2; - */ - stderrDelta: string; + /** + * @generated from field: string stderr_delta = 2; + */ + stderrDelta: string; }; /** * Describes the message agent.v1.ShellPartialResult. * Use `create(ShellPartialResultSchema)` to create a new message. */ -export const ShellPartialResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 403); +export const ShellPartialResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 403); /** * @generated from message agent.v1.ShellToolCall */ export type ShellToolCall = Message<"agent.v1.ShellToolCall"> & { - /** - * @generated from field: agent.v1.ShellArgs args = 1; - */ - args?: ShellArgs; + /** + * @generated from field: agent.v1.ShellArgs args = 1; + */ + args?: ShellArgs | undefined; - /** - * @generated from field: agent.v1.ShellResult result = 2; - */ - result?: ShellResult; + /** + * @generated from field: agent.v1.ShellResult result = 2; + */ + result?: ShellResult | undefined; }; /** * Describes the message agent.v1.ShellToolCall. * Use `create(ShellToolCallSchema)` to create a new message. */ -export const ShellToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 404); +export const ShellToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 404); /** * @generated from message agent.v1.ShellToolCallStdoutDelta */ export type ShellToolCallStdoutDelta = Message<"agent.v1.ShellToolCallStdoutDelta"> & { - /** - * @generated from field: string content = 1; - */ - content: string; + /** + * @generated from field: string content = 1; + */ + content: string; }; /** * Describes the message agent.v1.ShellToolCallStdoutDelta. * Use `create(ShellToolCallStdoutDeltaSchema)` to create a new message. */ -export const ShellToolCallStdoutDeltaSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 405); +export const ShellToolCallStdoutDeltaSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 405); /** * @generated from message agent.v1.ShellToolCallStderrDelta */ export type ShellToolCallStderrDelta = Message<"agent.v1.ShellToolCallStderrDelta"> & { - /** - * @generated from field: string content = 1; - */ - content: string; + /** + * @generated from field: string content = 1; + */ + content: string; }; /** * Describes the message agent.v1.ShellToolCallStderrDelta. * Use `create(ShellToolCallStderrDeltaSchema)` to create a new message. */ -export const ShellToolCallStderrDeltaSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 406); +export const ShellToolCallStderrDeltaSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 406); /** * @generated from message agent.v1.ShellToolCallDelta */ export type ShellToolCallDelta = Message<"agent.v1.ShellToolCallDelta"> & { - /** - * @generated from oneof agent.v1.ShellToolCallDelta.delta - */ - delta: - | { - /** - * @generated from field: agent.v1.ShellToolCallStdoutDelta stdout = 1; - */ - value: ShellToolCallStdoutDelta; - case: "stdout"; - } - | { - /** - * @generated from field: agent.v1.ShellToolCallStderrDelta stderr = 2; - */ - value: ShellToolCallStderrDelta; - case: "stderr"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ShellToolCallDelta.delta + */ + delta: { + /** + * @generated from field: agent.v1.ShellToolCallStdoutDelta stdout = 1; + */ + value: ShellToolCallStdoutDelta; + case: "stdout"; + } | { + /** + * @generated from field: agent.v1.ShellToolCallStderrDelta stderr = 2; + */ + value: ShellToolCallStderrDelta; + case: "stderr"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ShellToolCallDelta. * Use `create(ShellToolCallDeltaSchema)` to create a new message. */ -export const ShellToolCallDeltaSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 407); +export const ShellToolCallDeltaSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 407); /** * @generated from message agent.v1.SubagentType */ export type SubagentType = Message<"agent.v1.SubagentType"> & { - /** - * @generated from oneof agent.v1.SubagentType.type - */ - type: - | { - /** - * @generated from field: agent.v1.SubagentTypeUnspecified unspecified = 1; - */ - value: SubagentTypeUnspecified; - case: "unspecified"; - } - | { - /** - * @generated from field: agent.v1.SubagentTypeComputerUse computer_use = 2; - */ - value: SubagentTypeComputerUse; - case: "computerUse"; - } - | { - /** - * @generated from field: agent.v1.SubagentTypeCustom custom = 3; - */ - value: SubagentTypeCustom; - case: "custom"; - } - | { - /** - * @generated from field: agent.v1.SubagentTypeExplore explore = 4; - */ - value: SubagentTypeExplore; - case: "explore"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.SubagentType.type + */ + type: { + /** + * @generated from field: agent.v1.SubagentTypeUnspecified unspecified = 1; + */ + value: SubagentTypeUnspecified; + case: "unspecified"; + } | { + /** + * @generated from field: agent.v1.SubagentTypeComputerUse computer_use = 2; + */ + value: SubagentTypeComputerUse; + case: "computerUse"; + } | { + /** + * @generated from field: agent.v1.SubagentTypeCustom custom = 3; + */ + value: SubagentTypeCustom; + case: "custom"; + } | { + /** + * @generated from field: agent.v1.SubagentTypeExplore explore = 4; + */ + value: SubagentTypeExplore; + case: "explore"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.SubagentType. * Use `create(SubagentTypeSchema)` to create a new message. */ -export const SubagentTypeSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 408); +export const SubagentTypeSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 408); /** * Empty message for unspecified subagent type * * @generated from message agent.v1.SubagentTypeUnspecified */ -export type SubagentTypeUnspecified = Message<"agent.v1.SubagentTypeUnspecified"> & {}; +export type SubagentTypeUnspecified = Message<"agent.v1.SubagentTypeUnspecified"> & { +}; /** * Describes the message agent.v1.SubagentTypeUnspecified. * Use `create(SubagentTypeUnspecifiedSchema)` to create a new message. */ -export const SubagentTypeUnspecifiedSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 409); +export const SubagentTypeUnspecifiedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 409); /** * Empty message for computer use subagent type * * @generated from message agent.v1.SubagentTypeComputerUse */ -export type SubagentTypeComputerUse = Message<"agent.v1.SubagentTypeComputerUse"> & {}; +export type SubagentTypeComputerUse = Message<"agent.v1.SubagentTypeComputerUse"> & { +}; /** * Describes the message agent.v1.SubagentTypeComputerUse. * Use `create(SubagentTypeComputerUseSchema)` to create a new message. */ -export const SubagentTypeComputerUseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 410); +export const SubagentTypeComputerUseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 410); /** * Empty message for explore subagent type (read-only codebase exploration) * * @generated from message agent.v1.SubagentTypeExplore */ -export type SubagentTypeExplore = Message<"agent.v1.SubagentTypeExplore"> & {}; +export type SubagentTypeExplore = Message<"agent.v1.SubagentTypeExplore"> & { +}; /** * Describes the message agent.v1.SubagentTypeExplore. * Use `create(SubagentTypeExploreSchema)` to create a new message. */ -export const SubagentTypeExploreSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 411); +export const SubagentTypeExploreSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 411); /** * Custom subagent type with a name field @@ -12617,19 +13118,20 @@ export const SubagentTypeExploreSchema: GenMessage = /*@__P * @generated from message agent.v1.SubagentTypeCustom */ export type SubagentTypeCustom = Message<"agent.v1.SubagentTypeCustom"> & { - /** - * unique identifier of the custom subagent - * - * @generated from field: string name = 1; - */ - name: string; + /** + * unique identifier of the custom subagent + * + * @generated from field: string name = 1; + */ + name: string; }; /** * Describes the message agent.v1.SubagentTypeCustom. * Use `create(SubagentTypeCustomSchema)` to create a new message. */ -export const SubagentTypeCustomSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 412); +export const SubagentTypeCustomSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 412); /** * Custom subagent definition loaded from local workspace configuration. @@ -12637,332 +13139,330 @@ export const SubagentTypeCustomSchema: GenMessage = /*@__PUR * @generated from message agent.v1.CustomSubagent */ export type CustomSubagent = Message<"agent.v1.CustomSubagent"> & { - /** - * absolute path to the markdown definition file - * - * @generated from field: string full_path = 1; - */ - fullPath: string; + /** + * absolute path to the markdown definition file + * + * @generated from field: string full_path = 1; + */ + fullPath: string; - /** - * unique identifier of the subagent - * - * @generated from field: string name = 2; - */ - name: string; + /** + * unique identifier of the subagent + * + * @generated from field: string name = 2; + */ + name: string; - /** - * short summary of the agent's specialization - * - * @generated from field: string description = 3; - */ - description: string; + /** + * short summary of the agent's specialization + * + * @generated from field: string description = 3; + */ + description: string; - /** - * list of tool names the subagent can access - * - * @generated from field: repeated string tools = 4; - */ - tools: string[]; + /** + * list of tool names the subagent can access + * + * @generated from field: repeated string tools = 4; + */ + tools: string[]; - /** - * preferred model (or "inherit" to use parent's model) - * - * @generated from field: string model = 5; - */ - model: string; + /** + * preferred model (or "inherit" to use parent's model) + * + * @generated from field: string model = 5; + */ + model: string; - /** - * full prompt contents from the markdown file - * - * @generated from field: string prompt = 6; - */ - prompt: string; + /** + * full prompt contents from the markdown file + * + * @generated from field: string prompt = 6; + */ + prompt: string; - /** - * default permission mode for subagent execution - * - * @generated from field: int32 permission_mode = 7; - */ - permissionMode: number; + /** + * default permission mode for subagent execution + * + * @generated from field: int32 permission_mode = 7; + */ + permissionMode: number; }; /** * Describes the message agent.v1.CustomSubagent. * Use `create(CustomSubagentSchema)` to create a new message. */ -export const CustomSubagentSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 413); +export const CustomSubagentSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 413); /** * @generated from message agent.v1.SwitchModeArgs */ export type SwitchModeArgs = Message<"agent.v1.SwitchModeArgs"> & { - /** - * The unified mode id to switch to (agent/chat/plan/spec/debug/triage) - * - * @generated from field: string target_mode_id = 1; - */ - targetModeId: string; + /** + * The unified mode id to switch to (agent/chat/plan/spec/debug/triage) + * + * @generated from field: string target_mode_id = 1; + */ + targetModeId: string; - /** - * Optional explanation for why the mode switch is requested - * - * @generated from field: optional string explanation = 2; - */ - explanation?: string; + /** + * Optional explanation for why the mode switch is requested + * + * @generated from field: optional string explanation = 2; + */ + explanation?: string | undefined; - /** - * @generated from field: string tool_call_id = 3; - */ - toolCallId: string; + /** + * @generated from field: string tool_call_id = 3; + */ + toolCallId: string; }; /** * Describes the message agent.v1.SwitchModeArgs. * Use `create(SwitchModeArgsSchema)` to create a new message. */ -export const SwitchModeArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 414); +export const SwitchModeArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 414); /** * @generated from message agent.v1.SwitchModeResult */ export type SwitchModeResult = Message<"agent.v1.SwitchModeResult"> & { - /** - * @generated from oneof agent.v1.SwitchModeResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.SwitchModeSuccess success = 1; - */ - value: SwitchModeSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.SwitchModeError error = 2; - */ - value: SwitchModeError; - case: "error"; - } - | { - /** - * @generated from field: agent.v1.SwitchModeRejected rejected = 3; - */ - value: SwitchModeRejected; - case: "rejected"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.SwitchModeResult.result + */ + result: { + /** + * @generated from field: agent.v1.SwitchModeSuccess success = 1; + */ + value: SwitchModeSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.SwitchModeError error = 2; + */ + value: SwitchModeError; + case: "error"; + } | { + /** + * @generated from field: agent.v1.SwitchModeRejected rejected = 3; + */ + value: SwitchModeRejected; + case: "rejected"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.SwitchModeResult. * Use `create(SwitchModeResultSchema)` to create a new message. */ -export const SwitchModeResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 415); +export const SwitchModeResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 415); /** * @generated from message agent.v1.SwitchModeSuccess */ export type SwitchModeSuccess = Message<"agent.v1.SwitchModeSuccess"> & { - /** - * The mode we switched from - * - * @generated from field: string from_mode_id = 1; - */ - fromModeId: string; + /** + * The mode we switched from + * + * @generated from field: string from_mode_id = 1; + */ + fromModeId: string; - /** - * The mode we switched to - * - * @generated from field: string to_mode_id = 2; - */ - toModeId: string; + /** + * The mode we switched to + * + * @generated from field: string to_mode_id = 2; + */ + toModeId: string; }; /** * Describes the message agent.v1.SwitchModeSuccess. * Use `create(SwitchModeSuccessSchema)` to create a new message. */ -export const SwitchModeSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 416); +export const SwitchModeSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 416); /** * @generated from message agent.v1.SwitchModeError */ export type SwitchModeError = Message<"agent.v1.SwitchModeError"> & { - /** - * @generated from field: string error = 1; - */ - error: string; + /** + * @generated from field: string error = 1; + */ + error: string; }; /** * Describes the message agent.v1.SwitchModeError. * Use `create(SwitchModeErrorSchema)` to create a new message. */ -export const SwitchModeErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 417); +export const SwitchModeErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 417); /** * @generated from message agent.v1.SwitchModeRejected */ export type SwitchModeRejected = Message<"agent.v1.SwitchModeRejected"> & { - /** - * @generated from field: string reason = 1; - */ - reason: string; + /** + * @generated from field: string reason = 1; + */ + reason: string; }; /** * Describes the message agent.v1.SwitchModeRejected. * Use `create(SwitchModeRejectedSchema)` to create a new message. */ -export const SwitchModeRejectedSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 418); +export const SwitchModeRejectedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 418); /** * @generated from message agent.v1.SwitchModeToolCall */ export type SwitchModeToolCall = Message<"agent.v1.SwitchModeToolCall"> & { - /** - * @generated from field: agent.v1.SwitchModeArgs args = 1; - */ - args?: SwitchModeArgs; + /** + * @generated from field: agent.v1.SwitchModeArgs args = 1; + */ + args?: SwitchModeArgs | undefined; - /** - * @generated from field: agent.v1.SwitchModeResult result = 2; - */ - result?: SwitchModeResult; + /** + * @generated from field: agent.v1.SwitchModeResult result = 2; + */ + result?: SwitchModeResult | undefined; }; /** * Describes the message agent.v1.SwitchModeToolCall. * Use `create(SwitchModeToolCallSchema)` to create a new message. */ -export const SwitchModeToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 419); +export const SwitchModeToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 419); /** * @generated from message agent.v1.SwitchModeRequestQuery */ export type SwitchModeRequestQuery = Message<"agent.v1.SwitchModeRequestQuery"> & { - /** - * @generated from field: agent.v1.SwitchModeArgs args = 1; - */ - args?: SwitchModeArgs; + /** + * @generated from field: agent.v1.SwitchModeArgs args = 1; + */ + args?: SwitchModeArgs | undefined; }; /** * Describes the message agent.v1.SwitchModeRequestQuery. * Use `create(SwitchModeRequestQuerySchema)` to create a new message. */ -export const SwitchModeRequestQuerySchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 420); +export const SwitchModeRequestQuerySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 420); /** * @generated from message agent.v1.SwitchModeRequestResponse */ export type SwitchModeRequestResponse = Message<"agent.v1.SwitchModeRequestResponse"> & { - /** - * @generated from oneof agent.v1.SwitchModeRequestResponse.result - */ - result: - | { - /** - * @generated from field: agent.v1.SwitchModeRequestResponse_Approved approved = 1; - */ - value: SwitchModeRequestResponse_Approved; - case: "approved"; - } - | { - /** - * @generated from field: agent.v1.SwitchModeRequestResponse_Rejected rejected = 2; - */ - value: SwitchModeRequestResponse_Rejected; - case: "rejected"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.SwitchModeRequestResponse.result + */ + result: { + /** + * @generated from field: agent.v1.SwitchModeRequestResponse_Approved approved = 1; + */ + value: SwitchModeRequestResponse_Approved; + case: "approved"; + } | { + /** + * @generated from field: agent.v1.SwitchModeRequestResponse_Rejected rejected = 2; + */ + value: SwitchModeRequestResponse_Rejected; + case: "rejected"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.SwitchModeRequestResponse. * Use `create(SwitchModeRequestResponseSchema)` to create a new message. */ -export const SwitchModeRequestResponseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 421); +export const SwitchModeRequestResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 421); /** * @generated from message agent.v1.SwitchModeRequestResponse_Approved */ -export type SwitchModeRequestResponse_Approved = Message<"agent.v1.SwitchModeRequestResponse_Approved"> & {}; +export type SwitchModeRequestResponse_Approved = Message<"agent.v1.SwitchModeRequestResponse_Approved"> & { +}; /** * Describes the message agent.v1.SwitchModeRequestResponse_Approved. * Use `create(SwitchModeRequestResponse_ApprovedSchema)` to create a new message. */ -export const SwitchModeRequestResponse_ApprovedSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 422); +export const SwitchModeRequestResponse_ApprovedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 422); /** * @generated from message agent.v1.SwitchModeRequestResponse_Rejected */ export type SwitchModeRequestResponse_Rejected = Message<"agent.v1.SwitchModeRequestResponse_Rejected"> & { - /** - * @generated from field: string reason = 1; - */ - reason: string; + /** + * @generated from field: string reason = 1; + */ + reason: string; }; /** * Describes the message agent.v1.SwitchModeRequestResponse_Rejected. * Use `create(SwitchModeRequestResponse_RejectedSchema)` to create a new message. */ -export const SwitchModeRequestResponse_RejectedSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 423); +export const SwitchModeRequestResponse_RejectedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 423); /** * @generated from message agent.v1.TodoItem */ export type TodoItem = Message<"agent.v1.TodoItem"> & { - /** - * @generated from field: string id = 1; - */ - id: string; + /** + * @generated from field: string id = 1; + */ + id: string; - /** - * @generated from field: string content = 2; - */ - content: string; + /** + * @generated from field: string content = 2; + */ + content: string; - /** - * @generated from field: int32 status = 3; - */ - status: number; + /** + * @generated from field: int32 status = 3; + */ + status: number; - /** - * @generated from field: int64 created_at = 4; - */ - createdAt: bigint; + /** + * @generated from field: int64 created_at = 4; + */ + createdAt: bigint; - /** - * @generated from field: int64 updated_at = 5; - */ - updatedAt: bigint; + /** + * @generated from field: int64 updated_at = 5; + */ + updatedAt: bigint; - /** - * IDs of other TODOs this depends on - * - * @generated from field: repeated string dependencies = 6; - */ - dependencies: string[]; + /** + * IDs of other TODOs this depends on + * + * @generated from field: repeated string dependencies = 6; + */ + dependencies: string[]; }; /** * Describes the message agent.v1.TodoItem. * Use `create(TodoItemSchema)` to create a new message. */ -export const TodoItemSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 424); +export const TodoItemSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 424); /** * UpdateTodos tool call @@ -12970,118 +13470,120 @@ export const TodoItemSchema: GenMessage = /*@__PURE__*/ messageDesc(fi * @generated from message agent.v1.UpdateTodosToolCall */ export type UpdateTodosToolCall = Message<"agent.v1.UpdateTodosToolCall"> & { - /** - * @generated from field: agent.v1.UpdateTodosArgs args = 1; - */ - args?: UpdateTodosArgs; + /** + * @generated from field: agent.v1.UpdateTodosArgs args = 1; + */ + args?: UpdateTodosArgs | undefined; - /** - * @generated from field: agent.v1.UpdateTodosResult result = 2; - */ - result?: UpdateTodosResult; + /** + * @generated from field: agent.v1.UpdateTodosResult result = 2; + */ + result?: UpdateTodosResult | undefined; }; /** * Describes the message agent.v1.UpdateTodosToolCall. * Use `create(UpdateTodosToolCallSchema)` to create a new message. */ -export const UpdateTodosToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 425); +export const UpdateTodosToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 425); /** * @generated from message agent.v1.UpdateTodosArgs */ export type UpdateTodosArgs = Message<"agent.v1.UpdateTodosArgs"> & { - /** - * @generated from field: repeated agent.v1.TodoItem todos = 1; - */ - todos: TodoItem[]; + /** + * @generated from field: repeated agent.v1.TodoItem todos = 1; + */ + todos: TodoItem[]; - /** - * @generated from field: bool merge = 2; - */ - merge: boolean; + /** + * @generated from field: bool merge = 2; + */ + merge: boolean; }; /** * Describes the message agent.v1.UpdateTodosArgs. * Use `create(UpdateTodosArgsSchema)` to create a new message. */ -export const UpdateTodosArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 426); +export const UpdateTodosArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 426); /** * @generated from message agent.v1.UpdateTodosResult */ export type UpdateTodosResult = Message<"agent.v1.UpdateTodosResult"> & { - /** - * @generated from oneof agent.v1.UpdateTodosResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.UpdateTodosSuccess success = 1; - */ - value: UpdateTodosSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.UpdateTodosError error = 2; - */ - value: UpdateTodosError; - case: "error"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.UpdateTodosResult.result + */ + result: { + /** + * @generated from field: agent.v1.UpdateTodosSuccess success = 1; + */ + value: UpdateTodosSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.UpdateTodosError error = 2; + */ + value: UpdateTodosError; + case: "error"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.UpdateTodosResult. * Use `create(UpdateTodosResultSchema)` to create a new message. */ -export const UpdateTodosResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 427); +export const UpdateTodosResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 427); /** * @generated from message agent.v1.UpdateTodosSuccess */ export type UpdateTodosSuccess = Message<"agent.v1.UpdateTodosSuccess"> & { - /** - * @generated from field: repeated agent.v1.TodoItem todos = 1; - */ - todos: TodoItem[]; + /** + * @generated from field: repeated agent.v1.TodoItem todos = 1; + */ + todos: TodoItem[]; - /** - * @generated from field: int32 total_count = 2; - */ - totalCount: number; + /** + * @generated from field: int32 total_count = 2; + */ + totalCount: number; - /** - * Whether this was a merge operation (needed for conditional rendering) - * - * @generated from field: bool was_merge = 3; - */ - wasMerge: boolean; + /** + * Whether this was a merge operation (needed for conditional rendering) + * + * @generated from field: bool was_merge = 3; + */ + wasMerge: boolean; }; /** * Describes the message agent.v1.UpdateTodosSuccess. * Use `create(UpdateTodosSuccessSchema)` to create a new message. */ -export const UpdateTodosSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 428); +export const UpdateTodosSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 428); /** * @generated from message agent.v1.UpdateTodosError */ export type UpdateTodosError = Message<"agent.v1.UpdateTodosError"> & { - /** - * @generated from field: string error = 1; - */ - error: string; + /** + * @generated from field: string error = 1; + */ + error: string; }; /** * Describes the message agent.v1.UpdateTodosError. * Use `create(UpdateTodosErrorSchema)` to create a new message. */ -export const UpdateTodosErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 429); +export const UpdateTodosErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 429); /** * ReadTodos tool call @@ -13089,1339 +13591,1336 @@ export const UpdateTodosErrorSchema: GenMessage = /*@__PURE__* * @generated from message agent.v1.ReadTodosToolCall */ export type ReadTodosToolCall = Message<"agent.v1.ReadTodosToolCall"> & { - /** - * @generated from field: agent.v1.ReadTodosArgs args = 1; - */ - args?: ReadTodosArgs; + /** + * @generated from field: agent.v1.ReadTodosArgs args = 1; + */ + args?: ReadTodosArgs | undefined; - /** - * @generated from field: agent.v1.ReadTodosResult result = 2; - */ - result?: ReadTodosResult; + /** + * @generated from field: agent.v1.ReadTodosResult result = 2; + */ + result?: ReadTodosResult | undefined; }; /** * Describes the message agent.v1.ReadTodosToolCall. * Use `create(ReadTodosToolCallSchema)` to create a new message. */ -export const ReadTodosToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 430); +export const ReadTodosToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 430); /** * @generated from message agent.v1.ReadTodosArgs */ export type ReadTodosArgs = Message<"agent.v1.ReadTodosArgs"> & { - /** - * Optional: filter by status - * - * @generated from field: repeated int32 status_filter = 1; - */ - statusFilter: number[]; + /** + * Optional: filter by status + * + * @generated from field: repeated int32 status_filter = 1; + */ + statusFilter: number[]; - /** - * Optional: filter by IDs - * - * @generated from field: repeated string id_filter = 2; - */ - idFilter: string[]; + /** + * Optional: filter by IDs + * + * @generated from field: repeated string id_filter = 2; + */ + idFilter: string[]; }; /** * Describes the message agent.v1.ReadTodosArgs. * Use `create(ReadTodosArgsSchema)` to create a new message. */ -export const ReadTodosArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 431); +export const ReadTodosArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 431); /** * @generated from message agent.v1.ReadTodosResult */ export type ReadTodosResult = Message<"agent.v1.ReadTodosResult"> & { - /** - * @generated from oneof agent.v1.ReadTodosResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.ReadTodosSuccess success = 1; - */ - value: ReadTodosSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.ReadTodosError error = 2; - */ - value: ReadTodosError; - case: "error"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ReadTodosResult.result + */ + result: { + /** + * @generated from field: agent.v1.ReadTodosSuccess success = 1; + */ + value: ReadTodosSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.ReadTodosError error = 2; + */ + value: ReadTodosError; + case: "error"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ReadTodosResult. * Use `create(ReadTodosResultSchema)` to create a new message. */ -export const ReadTodosResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 432); +export const ReadTodosResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 432); /** * @generated from message agent.v1.ReadTodosSuccess */ export type ReadTodosSuccess = Message<"agent.v1.ReadTodosSuccess"> & { - /** - * @generated from field: repeated agent.v1.TodoItem todos = 1; - */ - todos: TodoItem[]; + /** + * @generated from field: repeated agent.v1.TodoItem todos = 1; + */ + todos: TodoItem[]; - /** - * @generated from field: int32 total_count = 2; - */ - totalCount: number; + /** + * @generated from field: int32 total_count = 2; + */ + totalCount: number; }; /** * Describes the message agent.v1.ReadTodosSuccess. * Use `create(ReadTodosSuccessSchema)` to create a new message. */ -export const ReadTodosSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 433); +export const ReadTodosSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 433); /** * @generated from message agent.v1.ReadTodosError */ export type ReadTodosError = Message<"agent.v1.ReadTodosError"> & { - /** - * @generated from field: string error = 1; - */ - error: string; + /** + * @generated from field: string error = 1; + */ + error: string; }; /** * Describes the message agent.v1.ReadTodosError. * Use `create(ReadTodosErrorSchema)` to create a new message. */ -export const ReadTodosErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 434); +export const ReadTodosErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 434); /** * @generated from message agent.v1.Range */ export type Range = Message<"agent.v1.Range"> & { - /** - * @generated from field: agent.v1.Position start = 1; - */ - start?: Position; + /** + * @generated from field: agent.v1.Position start = 1; + */ + start?: Position | undefined; - /** - * @generated from field: agent.v1.Position end = 2; - */ - end?: Position; + /** + * @generated from field: agent.v1.Position end = 2; + */ + end?: Position | undefined; }; /** * Describes the message agent.v1.Range. * Use `create(RangeSchema)` to create a new message. */ -export const RangeSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 435); +export const RangeSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 435); /** * @generated from message agent.v1.Position */ export type Position = Message<"agent.v1.Position"> & { - /** - * @generated from field: uint32 line = 1; - */ - line: number; + /** + * @generated from field: uint32 line = 1; + */ + line: number; - /** - * @generated from field: uint32 column = 2; - */ - column: number; + /** + * @generated from field: uint32 column = 2; + */ + column: number; }; /** * Describes the message agent.v1.Position. * Use `create(PositionSchema)` to create a new message. */ -export const PositionSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 436); +export const PositionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 436); /** * @generated from message agent.v1.Error */ export type Error = Message<"agent.v1.Error"> & { - /** - * @generated from field: string message = 1; - */ - message: string; + /** + * @generated from field: string message = 1; + */ + message: string; }; /** * Describes the message agent.v1.Error. * Use `create(ErrorSchema)` to create a new message. */ -export const ErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 437); +export const ErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 437); /** * @generated from message agent.v1.WebSearchArgs */ export type WebSearchArgs = Message<"agent.v1.WebSearchArgs"> & { - /** - * @generated from field: string search_term = 1; - */ - searchTerm: string; + /** + * @generated from field: string search_term = 1; + */ + searchTerm: string; - /** - * @generated from field: string tool_call_id = 2; - */ - toolCallId: string; + /** + * @generated from field: string tool_call_id = 2; + */ + toolCallId: string; }; /** * Describes the message agent.v1.WebSearchArgs. * Use `create(WebSearchArgsSchema)` to create a new message. */ -export const WebSearchArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 438); +export const WebSearchArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 438); /** * @generated from message agent.v1.WebSearchResult */ export type WebSearchResult = Message<"agent.v1.WebSearchResult"> & { - /** - * @generated from oneof agent.v1.WebSearchResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.WebSearchSuccess success = 1; - */ - value: WebSearchSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.WebSearchError error = 2; - */ - value: WebSearchError; - case: "error"; - } - | { - /** - * @generated from field: agent.v1.WebSearchRejected rejected = 3; - */ - value: WebSearchRejected; - case: "rejected"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.WebSearchResult.result + */ + result: { + /** + * @generated from field: agent.v1.WebSearchSuccess success = 1; + */ + value: WebSearchSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.WebSearchError error = 2; + */ + value: WebSearchError; + case: "error"; + } | { + /** + * @generated from field: agent.v1.WebSearchRejected rejected = 3; + */ + value: WebSearchRejected; + case: "rejected"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.WebSearchResult. * Use `create(WebSearchResultSchema)` to create a new message. */ -export const WebSearchResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 439); +export const WebSearchResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 439); /** * @generated from message agent.v1.WebSearchSuccess */ export type WebSearchSuccess = Message<"agent.v1.WebSearchSuccess"> & { - /** - * @generated from field: repeated agent.v1.WebSearchReference references = 1; - */ - references: WebSearchReference[]; + /** + * @generated from field: repeated agent.v1.WebSearchReference references = 1; + */ + references: WebSearchReference[]; }; /** * Describes the message agent.v1.WebSearchSuccess. * Use `create(WebSearchSuccessSchema)` to create a new message. */ -export const WebSearchSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 440); +export const WebSearchSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 440); /** * @generated from message agent.v1.WebSearchError */ export type WebSearchError = Message<"agent.v1.WebSearchError"> & { - /** - * @generated from field: string error = 1; - */ - error: string; + /** + * @generated from field: string error = 1; + */ + error: string; }; /** * Describes the message agent.v1.WebSearchError. * Use `create(WebSearchErrorSchema)` to create a new message. */ -export const WebSearchErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 441); +export const WebSearchErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 441); /** * @generated from message agent.v1.WebSearchRejected */ export type WebSearchRejected = Message<"agent.v1.WebSearchRejected"> & { - /** - * @generated from field: string reason = 1; - */ - reason: string; + /** + * @generated from field: string reason = 1; + */ + reason: string; }; /** * Describes the message agent.v1.WebSearchRejected. * Use `create(WebSearchRejectedSchema)` to create a new message. */ -export const WebSearchRejectedSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 442); +export const WebSearchRejectedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 442); /** * @generated from message agent.v1.WebSearchReference */ export type WebSearchReference = Message<"agent.v1.WebSearchReference"> & { - /** - * @generated from field: string title = 1; - */ - title: string; + /** + * @generated from field: string title = 1; + */ + title: string; - /** - * @generated from field: string url = 2; - */ - url: string; + /** + * @generated from field: string url = 2; + */ + url: string; - /** - * @generated from field: string chunk = 3; - */ - chunk: string; + /** + * @generated from field: string chunk = 3; + */ + chunk: string; }; /** * Describes the message agent.v1.WebSearchReference. * Use `create(WebSearchReferenceSchema)` to create a new message. */ -export const WebSearchReferenceSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 443); +export const WebSearchReferenceSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 443); /** * @generated from message agent.v1.WebSearchToolCall */ export type WebSearchToolCall = Message<"agent.v1.WebSearchToolCall"> & { - /** - * @generated from field: agent.v1.WebSearchArgs args = 1; - */ - args?: WebSearchArgs; + /** + * @generated from field: agent.v1.WebSearchArgs args = 1; + */ + args?: WebSearchArgs | undefined; - /** - * @generated from field: agent.v1.WebSearchResult result = 2; - */ - result?: WebSearchResult; + /** + * @generated from field: agent.v1.WebSearchResult result = 2; + */ + result?: WebSearchResult | undefined; }; /** * Describes the message agent.v1.WebSearchToolCall. * Use `create(WebSearchToolCallSchema)` to create a new message. */ -export const WebSearchToolCallSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 444); +export const WebSearchToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 444); /** * @generated from message agent.v1.WebSearchRequestQuery */ export type WebSearchRequestQuery = Message<"agent.v1.WebSearchRequestQuery"> & { - /** - * @generated from field: agent.v1.WebSearchArgs args = 1; - */ - args?: WebSearchArgs; + /** + * @generated from field: agent.v1.WebSearchArgs args = 1; + */ + args?: WebSearchArgs | undefined; }; /** * Describes the message agent.v1.WebSearchRequestQuery. * Use `create(WebSearchRequestQuerySchema)` to create a new message. */ -export const WebSearchRequestQuerySchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 445); +export const WebSearchRequestQuerySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 445); /** * @generated from message agent.v1.WebSearchRequestResponse */ export type WebSearchRequestResponse = Message<"agent.v1.WebSearchRequestResponse"> & { - /** - * @generated from oneof agent.v1.WebSearchRequestResponse.result - */ - result: - | { - /** - * @generated from field: agent.v1.WebSearchRequestResponse_Approved approved = 1; - */ - value: WebSearchRequestResponse_Approved; - case: "approved"; - } - | { - /** - * @generated from field: agent.v1.WebSearchRequestResponse_Rejected rejected = 2; - */ - value: WebSearchRequestResponse_Rejected; - case: "rejected"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.WebSearchRequestResponse.result + */ + result: { + /** + * @generated from field: agent.v1.WebSearchRequestResponse_Approved approved = 1; + */ + value: WebSearchRequestResponse_Approved; + case: "approved"; + } | { + /** + * @generated from field: agent.v1.WebSearchRequestResponse_Rejected rejected = 2; + */ + value: WebSearchRequestResponse_Rejected; + case: "rejected"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.WebSearchRequestResponse. * Use `create(WebSearchRequestResponseSchema)` to create a new message. */ -export const WebSearchRequestResponseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 446); +export const WebSearchRequestResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 446); /** * @generated from message agent.v1.WebSearchRequestResponse_Approved */ -export type WebSearchRequestResponse_Approved = Message<"agent.v1.WebSearchRequestResponse_Approved"> & {}; +export type WebSearchRequestResponse_Approved = Message<"agent.v1.WebSearchRequestResponse_Approved"> & { +}; /** * Describes the message agent.v1.WebSearchRequestResponse_Approved. * Use `create(WebSearchRequestResponse_ApprovedSchema)` to create a new message. */ -export const WebSearchRequestResponse_ApprovedSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 447); +export const WebSearchRequestResponse_ApprovedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 447); /** * @generated from message agent.v1.WebSearchRequestResponse_Rejected */ export type WebSearchRequestResponse_Rejected = Message<"agent.v1.WebSearchRequestResponse_Rejected"> & { - /** - * @generated from field: string reason = 1; - */ - reason: string; + /** + * @generated from field: string reason = 1; + */ + reason: string; }; /** * Describes the message agent.v1.WebSearchRequestResponse_Rejected. * Use `create(WebSearchRequestResponse_RejectedSchema)` to create a new message. */ -export const WebSearchRequestResponse_RejectedSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 448); +export const WebSearchRequestResponse_RejectedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 448); /** * @generated from message agent.v1.WriteArgs */ export type WriteArgs = Message<"agent.v1.WriteArgs"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: string file_text = 2; - */ - fileText: string; + /** + * @generated from field: string file_text = 2; + */ + fileText: string; - /** - * @generated from field: string tool_call_id = 3; - */ - toolCallId: string; + /** + * @generated from field: string tool_call_id = 3; + */ + toolCallId: string; - /** - * @generated from field: bool return_file_content_after_write = 4; - */ - returnFileContentAfterWrite: boolean; + /** + * @generated from field: bool return_file_content_after_write = 4; + */ + returnFileContentAfterWrite: boolean; - /** - * Raw binary data to write. When set, file_text is ignored and the bytes are written directly without any text processing (e.g., line ending normalization). - * - * @generated from field: bytes file_bytes = 5; - */ - fileBytes: Uint8Array; + /** + * Raw binary data to write. When set, file_text is ignored and the bytes are written directly without any text processing (e.g., line ending normalization). + * + * @generated from field: bytes file_bytes = 5; + */ + fileBytes: Uint8Array; + + /** + * @generated from field: optional string encoding_hint = 6; + */ + encodingHint?: string | undefined; }; /** * Describes the message agent.v1.WriteArgs. * Use `create(WriteArgsSchema)` to create a new message. */ -export const WriteArgsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 449); +export const WriteArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 449); /** * @generated from message agent.v1.WriteResult */ export type WriteResult = Message<"agent.v1.WriteResult"> & { - /** - * @generated from oneof agent.v1.WriteResult.result - */ - result: - | { - /** - * @generated from field: agent.v1.WriteSuccess success = 1; - */ - value: WriteSuccess; - case: "success"; - } - | { - /** - * @generated from field: agent.v1.WritePermissionDenied permission_denied = 3; - */ - value: WritePermissionDenied; - case: "permissionDenied"; - } - | { - /** - * @generated from field: agent.v1.WriteNoSpace no_space = 4; - */ - value: WriteNoSpace; - case: "noSpace"; - } - | { - /** - * @generated from field: agent.v1.WriteError error = 5; - */ - value: WriteError; - case: "error"; - } - | { - /** - * @generated from field: agent.v1.WriteRejected rejected = 6; - */ - value: WriteRejected; - case: "rejected"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.WriteResult.result + */ + result: { + /** + * @generated from field: agent.v1.WriteSuccess success = 1; + */ + value: WriteSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.WritePermissionDenied permission_denied = 3; + */ + value: WritePermissionDenied; + case: "permissionDenied"; + } | { + /** + * @generated from field: agent.v1.WriteNoSpace no_space = 4; + */ + value: WriteNoSpace; + case: "noSpace"; + } | { + /** + * @generated from field: agent.v1.WriteError error = 5; + */ + value: WriteError; + case: "error"; + } | { + /** + * @generated from field: agent.v1.WriteRejected rejected = 6; + */ + value: WriteRejected; + case: "rejected"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.WriteResult. * Use `create(WriteResultSchema)` to create a new message. */ -export const WriteResultSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 450); +export const WriteResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 450); /** * @generated from message agent.v1.WriteSuccess */ export type WriteSuccess = Message<"agent.v1.WriteSuccess"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: int32 lines_created = 2; - */ - linesCreated: number; + /** + * @generated from field: int32 lines_created = 2; + */ + linesCreated: number; - /** - * @generated from field: int32 file_size = 3; - */ - fileSize: number; + /** + * @generated from field: int32 file_size = 3; + */ + fileSize: number; - /** - * @generated from field: optional string file_content_after_write = 4; - */ - fileContentAfterWrite?: string; + /** + * @generated from field: optional string file_content_after_write = 4; + */ + fileContentAfterWrite?: string | undefined; }; /** * Describes the message agent.v1.WriteSuccess. * Use `create(WriteSuccessSchema)` to create a new message. */ -export const WriteSuccessSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 451); +export const WriteSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 451); /** * @generated from message agent.v1.WritePermissionDenied */ export type WritePermissionDenied = Message<"agent.v1.WritePermissionDenied"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: string directory = 2; - */ - directory: string; + /** + * @generated from field: string directory = 2; + */ + directory: string; - /** - * "create_directory" or "create_file" - * - * @generated from field: string operation = 3; - */ - operation: string; + /** + * "create_directory" or "create_file" + * + * @generated from field: string operation = 3; + */ + operation: string; - /** - * @generated from field: string error = 4; - */ - error: string; + /** + * @generated from field: string error = 4; + */ + error: string; - /** - * @generated from field: bool is_readonly = 5; - */ - isReadonly: boolean; + /** + * @generated from field: bool is_readonly = 5; + */ + isReadonly: boolean; }; /** * Describes the message agent.v1.WritePermissionDenied. * Use `create(WritePermissionDeniedSchema)` to create a new message. */ -export const WritePermissionDeniedSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 452); +export const WritePermissionDeniedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 452); /** * @generated from message agent.v1.WriteNoSpace */ export type WriteNoSpace = Message<"agent.v1.WriteNoSpace"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; }; /** * Describes the message agent.v1.WriteNoSpace. * Use `create(WriteNoSpaceSchema)` to create a new message. */ -export const WriteNoSpaceSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 453); +export const WriteNoSpaceSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 453); /** * @generated from message agent.v1.WriteError */ export type WriteError = Message<"agent.v1.WriteError"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: string error = 2; - */ - error: string; + /** + * @generated from field: string error = 2; + */ + error: string; }; /** * Describes the message agent.v1.WriteError. * Use `create(WriteErrorSchema)` to create a new message. */ -export const WriteErrorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 454); +export const WriteErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 454); /** * @generated from message agent.v1.WriteRejected */ export type WriteRejected = Message<"agent.v1.WriteRejected"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: string reason = 2; - */ - reason: string; + /** + * @generated from field: string reason = 2; + */ + reason: string; }; /** * Describes the message agent.v1.WriteRejected. * Use `create(WriteRejectedSchema)` to create a new message. */ -export const WriteRejectedSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 455); +export const WriteRejectedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 455); /** * @generated from message agent.v1.BootstrapStatsigRequest */ export type BootstrapStatsigRequest = Message<"agent.v1.BootstrapStatsigRequest"> & { - /** - * When true, the server should evaluate gates as if dev/internal status is ignored. This is used by clients to simulate a prod user experience. - * - * @generated from field: optional bool ignore_dev_status = 1; - */ - ignoreDevStatus?: boolean; + /** + * When true, the server should evaluate gates as if dev/internal status is ignored. This is used by clients to simulate a prod user experience. + * + * @generated from field: optional bool ignore_dev_status = 1; + */ + ignoreDevStatus?: boolean | undefined; - /** - * @generated from field: optional int32 operating_system = 2; - */ - operatingSystem?: number; + /** + * @generated from field: optional int32 operating_system = 2; + */ + operatingSystem?: number | undefined; }; /** * Describes the message agent.v1.BootstrapStatsigRequest. * Use `create(BootstrapStatsigRequestSchema)` to create a new message. */ -export const BootstrapStatsigRequestSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 456); +export const BootstrapStatsigRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 456); /** * @generated from message agent.v1.PingResponse */ -export type PingResponse = Message<"agent.v1.PingResponse"> & {}; +export type PingResponse = Message<"agent.v1.PingResponse"> & { +}; /** * Describes the message agent.v1.PingResponse. * Use `create(PingResponseSchema)` to create a new message. */ -export const PingResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 457); +export const PingResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 457); /** * @generated from message agent.v1.ExecRequest */ export type ExecRequest = Message<"agent.v1.ExecRequest"> & { - /** - * @generated from field: string command = 1; - */ - command: string; + /** + * @generated from field: string command = 1; + */ + command: string; - /** - * @generated from field: optional string cwd = 2; - */ - cwd?: string; + /** + * @generated from field: optional string cwd = 2; + */ + cwd?: string | undefined; - /** - * @generated from field: repeated string args = 3; - */ - args: string[]; + /** + * @generated from field: repeated string args = 3; + */ + args: string[]; - /** - * @generated from field: map environment = 4; - */ - environment: { [key: string]: string }; + /** + * @generated from field: map environment = 4; + */ + environment: { [key: string]: string }; }; /** * Describes the message agent.v1.ExecRequest. * Use `create(ExecRequestSchema)` to create a new message. */ -export const ExecRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 458); +export const ExecRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 458); /** * @generated from message agent.v1.ExecResponse */ export type ExecResponse = Message<"agent.v1.ExecResponse"> & { - /** - * @generated from oneof agent.v1.ExecResponse.event - */ - event: - | { - /** - * @generated from field: agent.v1.StdoutEvent stdout_event = 1; - */ - value: StdoutEvent; - case: "stdoutEvent"; - } - | { - /** - * @generated from field: agent.v1.StderrEvent stderr_event = 2; - */ - value: StderrEvent; - case: "stderrEvent"; - } - | { - /** - * @generated from field: agent.v1.ExitEvent exit_event = 3; - */ - value: ExitEvent; - case: "exitEvent"; - } - | { case: undefined; value?: undefined }; + /** + * @generated from oneof agent.v1.ExecResponse.event + */ + event: { + /** + * @generated from field: agent.v1.StdoutEvent stdout_event = 1; + */ + value: StdoutEvent; + case: "stdoutEvent"; + } | { + /** + * @generated from field: agent.v1.StderrEvent stderr_event = 2; + */ + value: StderrEvent; + case: "stderrEvent"; + } | { + /** + * @generated from field: agent.v1.ExitEvent exit_event = 3; + */ + value: ExitEvent; + case: "exitEvent"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message agent.v1.ExecResponse. * Use `create(ExecResponseSchema)` to create a new message. */ -export const ExecResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 459); +export const ExecResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 459); /** * @generated from message agent.v1.StdoutEvent */ export type StdoutEvent = Message<"agent.v1.StdoutEvent"> & { - /** - * @generated from field: string data = 1; - */ - data: string; + /** + * @generated from field: string data = 1; + */ + data: string; }; /** * Describes the message agent.v1.StdoutEvent. * Use `create(StdoutEventSchema)` to create a new message. */ -export const StdoutEventSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 460); +export const StdoutEventSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 460); /** * @generated from message agent.v1.StderrEvent */ export type StderrEvent = Message<"agent.v1.StderrEvent"> & { - /** - * @generated from field: string data = 1; - */ - data: string; + /** + * @generated from field: string data = 1; + */ + data: string; }; /** * Describes the message agent.v1.StderrEvent. * Use `create(StderrEventSchema)` to create a new message. */ -export const StderrEventSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 461); +export const StderrEventSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 461); /** * @generated from message agent.v1.ExitEvent */ export type ExitEvent = Message<"agent.v1.ExitEvent"> & { - /** - * @generated from field: int32 exit_code = 1; - */ - exitCode: number; + /** + * @generated from field: int32 exit_code = 1; + */ + exitCode: number; }; /** * Describes the message agent.v1.ExitEvent. * Use `create(ExitEventSchema)` to create a new message. */ -export const ExitEventSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 462); +export const ExitEventSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 462); /** * @generated from message agent.v1.ReadTextFileRequest */ export type ReadTextFileRequest = Message<"agent.v1.ReadTextFileRequest"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; }; /** * Describes the message agent.v1.ReadTextFileRequest. * Use `create(ReadTextFileRequestSchema)` to create a new message. */ -export const ReadTextFileRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 463); +export const ReadTextFileRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 463); /** * @generated from message agent.v1.ReadTextFileResponse */ export type ReadTextFileResponse = Message<"agent.v1.ReadTextFileResponse"> & { - /** - * @generated from field: string content = 1; - */ - content: string; + /** + * @generated from field: string content = 1; + */ + content: string; }; /** * Describes the message agent.v1.ReadTextFileResponse. * Use `create(ReadTextFileResponseSchema)` to create a new message. */ -export const ReadTextFileResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 464); +export const ReadTextFileResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 464); /** * @generated from message agent.v1.WriteTextFileRequest */ export type WriteTextFileRequest = Message<"agent.v1.WriteTextFileRequest"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: string content = 2; - */ - content: string; + /** + * @generated from field: string content = 2; + */ + content: string; }; /** * Describes the message agent.v1.WriteTextFileRequest. * Use `create(WriteTextFileRequestSchema)` to create a new message. */ -export const WriteTextFileRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 465); +export const WriteTextFileRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 465); /** * Empty response - success is implied by RPC completion * * @generated from message agent.v1.WriteTextFileResponse */ -export type WriteTextFileResponse = Message<"agent.v1.WriteTextFileResponse"> & {}; +export type WriteTextFileResponse = Message<"agent.v1.WriteTextFileResponse"> & { +}; /** * Describes the message agent.v1.WriteTextFileResponse. * Use `create(WriteTextFileResponseSchema)` to create a new message. */ -export const WriteTextFileResponseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 466); +export const WriteTextFileResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 466); /** * @generated from message agent.v1.ReadBinaryFileRequest */ export type ReadBinaryFileRequest = Message<"agent.v1.ReadBinaryFileRequest"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; }; /** * Describes the message agent.v1.ReadBinaryFileRequest. * Use `create(ReadBinaryFileRequestSchema)` to create a new message. */ -export const ReadBinaryFileRequestSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 467); +export const ReadBinaryFileRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 467); /** * @generated from message agent.v1.ReadBinaryFileResponse */ export type ReadBinaryFileResponse = Message<"agent.v1.ReadBinaryFileResponse"> & { - /** - * @generated from field: bytes content = 1; - */ - content: Uint8Array; + /** + * @generated from field: bytes content = 1; + */ + content: Uint8Array; }; /** * Describes the message agent.v1.ReadBinaryFileResponse. * Use `create(ReadBinaryFileResponseSchema)` to create a new message. */ -export const ReadBinaryFileResponseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 468); +export const ReadBinaryFileResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 468); /** * @generated from message agent.v1.WriteBinaryFileRequest */ export type WriteBinaryFileRequest = Message<"agent.v1.WriteBinaryFileRequest"> & { - /** - * @generated from field: string path = 1; - */ - path: string; + /** + * @generated from field: string path = 1; + */ + path: string; - /** - * @generated from field: bytes content = 2; - */ - content: Uint8Array; + /** + * @generated from field: bytes content = 2; + */ + content: Uint8Array; }; /** * Describes the message agent.v1.WriteBinaryFileRequest. * Use `create(WriteBinaryFileRequestSchema)` to create a new message. */ -export const WriteBinaryFileRequestSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 469); +export const WriteBinaryFileRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 469); /** * Empty response - success is implied by RPC completion * * @generated from message agent.v1.WriteBinaryFileResponse */ -export type WriteBinaryFileResponse = Message<"agent.v1.WriteBinaryFileResponse"> & {}; +export type WriteBinaryFileResponse = Message<"agent.v1.WriteBinaryFileResponse"> & { +}; /** * Describes the message agent.v1.WriteBinaryFileResponse. * Use `create(WriteBinaryFileResponseSchema)` to create a new message. */ -export const WriteBinaryFileResponseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 470); +export const WriteBinaryFileResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 470); /** * @generated from message agent.v1.GetWorkspaceChangesHashRequest */ export type GetWorkspaceChangesHashRequest = Message<"agent.v1.GetWorkspaceChangesHashRequest"> & { - /** - * @generated from field: string root_path = 1; - */ - rootPath: string; + /** + * @generated from field: string root_path = 1; + */ + rootPath: string; - /** - * @generated from field: string base_ref = 2; - */ - baseRef: string; + /** + * @generated from field: string base_ref = 2; + */ + baseRef: string; }; /** * Describes the message agent.v1.GetWorkspaceChangesHashRequest. * Use `create(GetWorkspaceChangesHashRequestSchema)` to create a new message. */ -export const GetWorkspaceChangesHashRequestSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 471); +export const GetWorkspaceChangesHashRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 471); /** * @generated from message agent.v1.GetWorkspaceChangesHashResponse */ export type GetWorkspaceChangesHashResponse = Message<"agent.v1.GetWorkspaceChangesHashResponse"> & { - /** - * @generated from field: string hash = 1; - */ - hash: string; + /** + * @generated from field: string hash = 1; + */ + hash: string; }; /** * Describes the message agent.v1.GetWorkspaceChangesHashResponse. * Use `create(GetWorkspaceChangesHashResponseSchema)` to create a new message. */ -export const GetWorkspaceChangesHashResponseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 472); +export const GetWorkspaceChangesHashResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 472); /** * @generated from message agent.v1.RefreshGithubAccessTokenRequest */ export type RefreshGithubAccessTokenRequest = Message<"agent.v1.RefreshGithubAccessTokenRequest"> & { - /** - * @generated from field: string github_access_token = 1; - */ - githubAccessToken: string; + /** + * @generated from field: string github_access_token = 1; + */ + githubAccessToken: string; - /** - * e.g., "github.com", "gitlab.com", "gitlab.example.com" - * - * @generated from field: string hostname = 2; - */ - hostname: string; + /** + * e.g., "github.com", "gitlab.com", "gitlab.example.com" + * + * @generated from field: string hostname = 2; + */ + hostname: string; }; /** * Describes the message agent.v1.RefreshGithubAccessTokenRequest. * Use `create(RefreshGithubAccessTokenRequestSchema)` to create a new message. */ -export const RefreshGithubAccessTokenRequestSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 473); +export const RefreshGithubAccessTokenRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 473); /** * Empty response - success is implied by RPC completion * * @generated from message agent.v1.RefreshGithubAccessTokenResponse */ -export type RefreshGithubAccessTokenResponse = Message<"agent.v1.RefreshGithubAccessTokenResponse"> & {}; +export type RefreshGithubAccessTokenResponse = Message<"agent.v1.RefreshGithubAccessTokenResponse"> & { +}; /** * Describes the message agent.v1.RefreshGithubAccessTokenResponse. * Use `create(RefreshGithubAccessTokenResponseSchema)` to create a new message. */ -export const RefreshGithubAccessTokenResponseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 474); +export const RefreshGithubAccessTokenResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 474); /** * @generated from message agent.v1.WarmRemoteAccessServerRequest */ export type WarmRemoteAccessServerRequest = Message<"agent.v1.WarmRemoteAccessServerRequest"> & { - /** - * @generated from field: string commit = 1; - */ - commit: string; + /** + * @generated from field: string commit = 1; + */ + commit: string; - /** - * @generated from field: int32 port = 2; - */ - port: number; + /** + * @generated from field: int32 port = 2; + */ + port: number; - /** - * @generated from field: string connection_token = 3; - */ - connectionToken: string; + /** + * @generated from field: string connection_token = 3; + */ + connectionToken: string; }; /** * Describes the message agent.v1.WarmRemoteAccessServerRequest. * Use `create(WarmRemoteAccessServerRequestSchema)` to create a new message. */ -export const WarmRemoteAccessServerRequestSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 475); +export const WarmRemoteAccessServerRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 475); /** * Empty response - success is implied by RPC completion * * @generated from message agent.v1.WarmRemoteAccessServerResponse */ -export type WarmRemoteAccessServerResponse = Message<"agent.v1.WarmRemoteAccessServerResponse"> & {}; +export type WarmRemoteAccessServerResponse = Message<"agent.v1.WarmRemoteAccessServerResponse"> & { +}; /** * Describes the message agent.v1.WarmRemoteAccessServerResponse. * Use `create(WarmRemoteAccessServerResponseSchema)` to create a new message. */ -export const WarmRemoteAccessServerResponseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 476); +export const WarmRemoteAccessServerResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 476); /** * @generated from message agent.v1.ListArtifactsRequest */ -export type ListArtifactsRequest = Message<"agent.v1.ListArtifactsRequest"> & {}; +export type ListArtifactsRequest = Message<"agent.v1.ListArtifactsRequest"> & { +}; /** * Describes the message agent.v1.ListArtifactsRequest. * Use `create(ListArtifactsRequestSchema)` to create a new message. */ -export const ListArtifactsRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 477); +export const ListArtifactsRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 477); /** * @generated from message agent.v1.ArtifactUploadMetadata */ export type ArtifactUploadMetadata = Message<"agent.v1.ArtifactUploadMetadata"> & { - /** - * @generated from field: string absolute_path = 1; - */ - absolutePath: string; + /** + * @generated from field: string absolute_path = 1; + */ + absolutePath: string; - /** - * @generated from field: uint64 size_bytes = 2; - */ - sizeBytes: bigint; + /** + * @generated from field: uint64 size_bytes = 2; + */ + sizeBytes: bigint; - /** - * @generated from field: int64 updated_at_unix_ms = 3; - */ - updatedAtUnixMs: bigint; + /** + * @generated from field: int64 updated_at_unix_ms = 3; + */ + updatedAtUnixMs: bigint; - /** - * @generated from field: int32 status = 4; - */ - status: number; + /** + * @generated from field: int32 status = 4; + */ + status: number; - /** - * @generated from field: uint64 bytes_uploaded = 5; - */ - bytesUploaded: bigint; + /** + * @generated from field: uint64 bytes_uploaded = 5; + */ + bytesUploaded: bigint; - /** - * @generated from field: string last_error = 6; - */ - lastError: string; + /** + * @generated from field: string last_error = 6; + */ + lastError: string; - /** - * @generated from field: uint32 upload_attempts = 7; - */ - uploadAttempts: number; + /** + * @generated from field: uint32 upload_attempts = 7; + */ + uploadAttempts: number; - /** - * @generated from field: int64 last_started_at_unix_ms = 8; - */ - lastStartedAtUnixMs: bigint; + /** + * @generated from field: int64 last_started_at_unix_ms = 8; + */ + lastStartedAtUnixMs: bigint; - /** - * @generated from field: int64 last_finished_at_unix_ms = 9; - */ - lastFinishedAtUnixMs: bigint; + /** + * @generated from field: int64 last_finished_at_unix_ms = 9; + */ + lastFinishedAtUnixMs: bigint; - /** - * @generated from field: string upload_id = 10; - */ - uploadId: string; + /** + * @generated from field: string upload_id = 10; + */ + uploadId: string; }; /** * Describes the message agent.v1.ArtifactUploadMetadata. * Use `create(ArtifactUploadMetadataSchema)` to create a new message. */ -export const ArtifactUploadMetadataSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 478); +export const ArtifactUploadMetadataSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 478); /** * @generated from message agent.v1.ListArtifactsResponse */ export type ListArtifactsResponse = Message<"agent.v1.ListArtifactsResponse"> & { - /** - * @generated from field: repeated agent.v1.ArtifactUploadMetadata artifacts = 1; - */ - artifacts: ArtifactUploadMetadata[]; + /** + * @generated from field: repeated agent.v1.ArtifactUploadMetadata artifacts = 1; + */ + artifacts: ArtifactUploadMetadata[]; }; /** * Describes the message agent.v1.ListArtifactsResponse. * Use `create(ListArtifactsResponseSchema)` to create a new message. */ -export const ListArtifactsResponseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 479); +export const ListArtifactsResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 479); /** * @generated from message agent.v1.UploadArtifactsRequest */ export type UploadArtifactsRequest = Message<"agent.v1.UploadArtifactsRequest"> & { - /** - * @generated from field: repeated agent.v1.ArtifactUploadInstruction uploads = 1; - */ - uploads: ArtifactUploadInstruction[]; + /** + * @generated from field: repeated agent.v1.ArtifactUploadInstruction uploads = 1; + */ + uploads: ArtifactUploadInstruction[]; }; /** * Describes the message agent.v1.UploadArtifactsRequest. * Use `create(UploadArtifactsRequestSchema)` to create a new message. */ -export const UploadArtifactsRequestSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 480); +export const UploadArtifactsRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 480); /** * @generated from message agent.v1.ArtifactUploadInstruction */ export type ArtifactUploadInstruction = Message<"agent.v1.ArtifactUploadInstruction"> & { - /** - * @generated from field: string absolute_path = 1; - */ - absolutePath: string; + /** + * @generated from field: string absolute_path = 1; + */ + absolutePath: string; - /** - * @generated from field: string upload_url = 2; - */ - uploadUrl: string; + /** + * @generated from field: string upload_url = 2; + */ + uploadUrl: string; - /** - * @generated from field: string method = 3; - */ - method: string; + /** + * @generated from field: string method = 3; + */ + method: string; - /** - * @generated from field: map headers = 4; - */ - headers: { [key: string]: string }; + /** + * @generated from field: map headers = 4; + */ + headers: { [key: string]: string }; - /** - * @generated from field: optional string content_type = 5; - */ - contentType?: string; + /** + * @generated from field: optional string content_type = 5; + */ + contentType?: string | undefined; - /** - * @generated from field: optional string slack_upload_url = 6; - */ - slackUploadUrl?: string; + /** + * @generated from field: optional string slack_upload_url = 6; + */ + slackUploadUrl?: string | undefined; - /** - * @generated from field: optional string slack_file_id = 7; - */ - slackFileId?: string; + /** + * @generated from field: optional string slack_file_id = 7; + */ + slackFileId?: string | undefined; }; /** * Describes the message agent.v1.ArtifactUploadInstruction. * Use `create(ArtifactUploadInstructionSchema)` to create a new message. */ -export const ArtifactUploadInstructionSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 481); +export const ArtifactUploadInstructionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 481); /** * @generated from message agent.v1.ArtifactUploadDispatchResult */ export type ArtifactUploadDispatchResult = Message<"agent.v1.ArtifactUploadDispatchResult"> & { - /** - * @generated from field: string absolute_path = 1; - */ - absolutePath: string; + /** + * @generated from field: string absolute_path = 1; + */ + absolutePath: string; - /** - * @generated from field: int32 status = 2; - */ - status: number; + /** + * @generated from field: int32 status = 2; + */ + status: number; - /** - * @generated from field: string message = 3; - */ - message: string; + /** + * @generated from field: string message = 3; + */ + message: string; - /** - * @generated from field: optional string slack_file_id = 4; - */ - slackFileId?: string; + /** + * @generated from field: optional string slack_file_id = 4; + */ + slackFileId?: string | undefined; }; /** * Describes the message agent.v1.ArtifactUploadDispatchResult. * Use `create(ArtifactUploadDispatchResultSchema)` to create a new message. */ -export const ArtifactUploadDispatchResultSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 482); +export const ArtifactUploadDispatchResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 482); /** * @generated from message agent.v1.UploadArtifactsResponse */ export type UploadArtifactsResponse = Message<"agent.v1.UploadArtifactsResponse"> & { - /** - * @generated from field: repeated agent.v1.ArtifactUploadDispatchResult results = 1; - */ - results: ArtifactUploadDispatchResult[]; + /** + * @generated from field: repeated agent.v1.ArtifactUploadDispatchResult results = 1; + */ + results: ArtifactUploadDispatchResult[]; }; /** * Describes the message agent.v1.UploadArtifactsResponse. * Use `create(UploadArtifactsResponseSchema)` to create a new message. */ -export const UploadArtifactsResponseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 483); +export const UploadArtifactsResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 483); /** * @generated from message agent.v1.GetMcpRefreshTokensRequest */ -export type GetMcpRefreshTokensRequest = Message<"agent.v1.GetMcpRefreshTokensRequest"> & {}; +export type GetMcpRefreshTokensRequest = Message<"agent.v1.GetMcpRefreshTokensRequest"> & { +}; /** * Describes the message agent.v1.GetMcpRefreshTokensRequest. * Use `create(GetMcpRefreshTokensRequestSchema)` to create a new message. */ -export const GetMcpRefreshTokensRequestSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 484); +export const GetMcpRefreshTokensRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 484); /** * @generated from message agent.v1.GetMcpRefreshTokensResponse */ export type GetMcpRefreshTokensResponse = Message<"agent.v1.GetMcpRefreshTokensResponse"> & { - /** - * Map from server URL to refresh token - * - * @generated from field: map refresh_tokens = 1; - */ - refreshTokens: { [key: string]: string }; + /** + * Map from server URL to refresh token + * + * @generated from field: map refresh_tokens = 1; + */ + refreshTokens: { [key: string]: string }; }; /** * Describes the message agent.v1.GetMcpRefreshTokensResponse. * Use `create(GetMcpRefreshTokensResponseSchema)` to create a new message. */ -export const GetMcpRefreshTokensResponseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 485); +export const GetMcpRefreshTokensResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 485); /** * @generated from message agent.v1.UpdateEnvironmentVariablesRequest */ export type UpdateEnvironmentVariablesRequest = Message<"agent.v1.UpdateEnvironmentVariablesRequest"> & { - /** - * Environment variables to manage (plaintext values). - * - * @generated from field: map env = 1; - */ - env: { [key: string]: string }; + /** + * Environment variables to manage (plaintext values). + * + * @generated from field: map env = 1; + */ + env: { [key: string]: string }; - /** - * If true, unset previously-managed keys that are not present in `env`. - * - * @generated from field: bool replace = 2; - */ - replace: boolean; + /** + * If true, unset previously-managed keys that are not present in `env`. + * + * @generated from field: bool replace = 2; + */ + replace: boolean; }; /** * Describes the message agent.v1.UpdateEnvironmentVariablesRequest. * Use `create(UpdateEnvironmentVariablesRequestSchema)` to create a new message. */ -export const UpdateEnvironmentVariablesRequestSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 486); +export const UpdateEnvironmentVariablesRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 486); /** * @generated from message agent.v1.UpdateEnvironmentVariablesResponse */ export type UpdateEnvironmentVariablesResponse = Message<"agent.v1.UpdateEnvironmentVariablesResponse"> & { - /** - * @generated from field: uint32 applied = 1; - */ - applied: number; + /** + * @generated from field: uint32 applied = 1; + */ + applied: number; - /** - * @generated from field: uint32 removed = 2; - */ - removed: number; + /** + * @generated from field: uint32 removed = 2; + */ + removed: number; }; /** * Describes the message agent.v1.UpdateEnvironmentVariablesResponse. * Use `create(UpdateEnvironmentVariablesResponseSchema)` to create a new message. */ -export const UpdateEnvironmentVariablesResponseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_agent, 487); +export const UpdateEnvironmentVariablesResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 487); /** * Check if an error is caused by the client disconnecting (e.g., due to timeout or abort). This includes errors like ERR_STREAM_DESTROYED which occur when the HTTP response stream is closed by the client while the server is still writing to it. function isClientDisconnectError(error) { if (!(error instanceof Error)) { return false; const code = error.code; return (code === "ERR_STREAM_DESTROYED" || code === "ERR_STREAM_PREMATURE_CLOSE" || code === "ECONNRESET" || code === "EPIPE"); ;// ../proto/dist/generated/aiserver/v1/mcp_pb.js // @ts-nocheck @@ -14429,633 +14928,4366 @@ export const UpdateEnvironmentVariablesResponseSchema: GenMessage & { - /** - * @generated from field: string refresh_token = 1; - */ - refreshToken: string; + /** + * @generated from field: string refresh_token = 1; + */ + refreshToken: string; - /** - * @generated from field: string client_id = 2; - */ - clientId: string; + /** + * @generated from field: string client_id = 2; + */ + clientId: string; - /** - * @generated from field: optional string client_secret = 3; - */ - clientSecret?: string; + /** + * @generated from field: optional string client_secret = 3; + */ + clientSecret?: string | undefined; - /** - * @generated from field: repeated string redirect_uris = 4; - */ - redirectUris: string[]; + /** + * @generated from field: repeated string redirect_uris = 4; + */ + redirectUris: string[]; }; /** * Describes the message agent.v1.McpOAuthStoredData. * Use `create(McpOAuthStoredDataSchema)` to create a new message. */ -export const McpOAuthStoredDataSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 488); +export const McpOAuthStoredDataSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 488); /** * @generated from message agent.v1.Frame */ export type Frame = Message<"agent.v1.Frame"> & { - /** - * Correlation ID - * - * @generated from field: string id = 1; - */ - id: string; + /** + * Correlation ID + * + * @generated from field: string id = 1; + */ + id: string; - /** - * RPC method (e.g., "/agent.v1.ControlService/Ping") - * - * @generated from field: string method = 2; - */ - method: string; + /** + * RPC method (e.g., "/agent.v1.ControlService/Ping") + * + * @generated from field: string method = 2; + */ + method: string; - /** - * Serialized payload - * - * @generated from field: bytes data = 3; - */ - data: Uint8Array; + /** + * Serialized payload + * + * @generated from field: bytes data = 3; + */ + data: Uint8Array; - /** - * @generated from field: int32 kind = 4; - */ - kind: number; + /** + * @generated from field: int32 kind = 4; + */ + kind: number; - /** - * Error message (kind == ERROR) - * - * @generated from field: string error = 5; - */ - error: string; + /** + * Error message (kind == ERROR) + * + * @generated from field: string error = 5; + */ + error: string; }; /** * Describes the message agent.v1.Frame. * Use `create(FrameSchema)` to create a new message. */ -export const FrameSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 489); +export const FrameSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 489); /** * var Frame_Kind; (function (Frame_Kind) { Frame_Kind[Frame_Kind["UNSPECIFIED"] = 0] = "UNSPECIFIED"; Frame_Kind[Frame_Kind["REQUEST"] = 1] = "REQUEST"; Frame_Kind[Frame_Kind["RESPONSE"] = 2] = "RESPONSE"; Frame_Kind[Frame_Kind["ERROR"] = 3] = "ERROR"; })(Frame_Kind || (Frame_Kind = {})); // Retrieve enum metadata with: proto3.getEnumType(Frame_Kind) proto3/* int32 *\/.C.util.setEnumType(Frame_Kind, "agent.v1.Frame.Kind", [ { no: 0, name: "KIND_UNSPECIFIED" }, { no: 1, name: "KIND_REQUEST" }, { no: 2, name: "KIND_RESPONSE" }, { no: 3, name: "KIND_ERROR" }, ]); * * @generated from message agent.v1.Empty */ -export type Empty = Message<"agent.v1.Empty"> & {}; +export type Empty = Message<"agent.v1.Empty"> & { +}; /** * Describes the message agent.v1.Empty. * Use `create(EmptySchema)` to create a new message. */ -export const EmptySchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 490); +export const EmptySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 490); /** * @generated from message agent.v1.BidiRequestId */ export type BidiRequestId = Message<"agent.v1.BidiRequestId"> & { - /** - * @generated from field: string request_id = 1; - */ - requestId: string; + /** + * @generated from field: string request_id = 1; + */ + requestId: string; }; /** * Describes the message agent.v1.BidiRequestId. * Use `create(BidiRequestIdSchema)` to create a new message. */ -export const BidiRequestIdSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agent, 491); +export const BidiRequestIdSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 491); + +/** + * @generated from message agent.v1.PiTruncation + */ +export type PiTruncation = Message<"agent.v1.PiTruncation"> & { + /** + * @generated from field: bool truncated = 1; + */ + truncated: boolean; + + /** + * @generated from field: string truncated_by = 2; + */ + truncatedBy: string; + + /** + * @generated from field: uint32 total_lines = 3; + */ + totalLines: number; + + /** + * @generated from field: uint32 output_lines = 4; + */ + outputLines: number; + + /** + * @generated from field: uint32 output_bytes = 5; + */ + outputBytes: number; + + /** + * @generated from field: optional uint32 max_lines = 6; + */ + maxLines?: number | undefined; + + /** + * @generated from field: optional uint32 max_bytes = 7; + */ + maxBytes?: number | undefined; + + /** + * @generated from field: bool first_line_exceeds_limit = 8; + */ + firstLineExceedsLimit: boolean; + + /** + * @generated from field: bool last_line_partial = 9; + */ + lastLinePartial: boolean; +}; + +/** + * Describes the message agent.v1.PiTruncation. + * Use `create(PiTruncationSchema)` to create a new message. + */ +export const PiTruncationSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 492); + +/** + * @generated from message agent.v1.PiEditReplacement + */ +export type PiEditReplacement = Message<"agent.v1.PiEditReplacement"> & { + /** + * @generated from field: string old_text = 1; + */ + oldText: string; + + /** + * @generated from field: string new_text = 2; + */ + newText: string; +}; + +/** + * Describes the message agent.v1.PiEditReplacement. + * Use `create(PiEditReplacementSchema)` to create a new message. + */ +export const PiEditReplacementSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 493); + +/** + * @generated from message agent.v1.PiReadExecArgs + */ +export type PiReadExecArgs = Message<"agent.v1.PiReadExecArgs"> & { + /** + * @generated from field: string path = 1; + */ + path: string; + + /** + * @generated from field: optional int32 offset = 2; + */ + offset?: number | undefined; + + /** + * @generated from field: optional int32 limit = 3; + */ + limit?: number | undefined; +}; + +/** + * Describes the message agent.v1.PiReadExecArgs. + * Use `create(PiReadExecArgsSchema)` to create a new message. + */ +export const PiReadExecArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 494); + +/** + * @generated from message agent.v1.PiReadExecSuccess + */ +export type PiReadExecSuccess = Message<"agent.v1.PiReadExecSuccess"> & { + /** + * @generated from field: string output = 1; + */ + output: string; + + /** + * @generated from field: optional agent.v1.PiTruncation truncation = 2; + */ + truncation?: PiTruncation | undefined; +}; + +/** + * Describes the message agent.v1.PiReadExecSuccess. + * Use `create(PiReadExecSuccessSchema)` to create a new message. + */ +export const PiReadExecSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 495); + +/** + * @generated from message agent.v1.PiReadExecError + */ +export type PiReadExecError = Message<"agent.v1.PiReadExecError"> & { + /** + * @generated from field: string error = 1; + */ + error: string; +}; + +/** + * Describes the message agent.v1.PiReadExecError. + * Use `create(PiReadExecErrorSchema)` to create a new message. + */ +export const PiReadExecErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 496); + +/** + * @generated from message agent.v1.PiReadExecResult + */ +export type PiReadExecResult = Message<"agent.v1.PiReadExecResult"> & { + /** + * @generated from oneof agent.v1.PiReadExecResult.result + */ + result: { + /** + * @generated from field: agent.v1.PiReadExecSuccess success = 1; + */ + value: PiReadExecSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.PiReadExecError error = 2; + */ + value: PiReadExecError; + case: "error"; + } | { case: undefined; value?: undefined }; +}; + +/** + * Describes the message agent.v1.PiReadExecResult. + * Use `create(PiReadExecResultSchema)` to create a new message. + */ +export const PiReadExecResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 497); + +/** + * @generated from message agent.v1.PiBashExecArgs + */ +export type PiBashExecArgs = Message<"agent.v1.PiBashExecArgs"> & { + /** + * @generated from field: string command = 1; + */ + command: string; + + /** + * @generated from field: optional double timeout = 2; + */ + timeout?: number | undefined; +}; + +/** + * Describes the message agent.v1.PiBashExecArgs. + * Use `create(PiBashExecArgsSchema)` to create a new message. + */ +export const PiBashExecArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 498); + +/** + * @generated from message agent.v1.PiBashExecSuccess + */ +export type PiBashExecSuccess = Message<"agent.v1.PiBashExecSuccess"> & { + /** + * @generated from field: string output = 1; + */ + output: string; + + /** + * @generated from field: optional agent.v1.PiTruncation truncation = 2; + */ + truncation?: PiTruncation | undefined; + + /** + * @generated from field: optional string full_output_path = 3; + */ + fullOutputPath?: string | undefined; +}; + +/** + * Describes the message agent.v1.PiBashExecSuccess. + * Use `create(PiBashExecSuccessSchema)` to create a new message. + */ +export const PiBashExecSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 499); + +/** + * @generated from message agent.v1.PiBashExecError + */ +export type PiBashExecError = Message<"agent.v1.PiBashExecError"> & { + /** + * @generated from field: string error = 1; + */ + error: string; + + /** + * @generated from field: optional agent.v1.PiTruncation truncation = 2; + */ + truncation?: PiTruncation | undefined; + + /** + * @generated from field: optional string full_output_path = 3; + */ + fullOutputPath?: string | undefined; +}; + +/** + * Describes the message agent.v1.PiBashExecError. + * Use `create(PiBashExecErrorSchema)` to create a new message. + */ +export const PiBashExecErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 500); + +/** + * @generated from message agent.v1.PiBashExecResult + */ +export type PiBashExecResult = Message<"agent.v1.PiBashExecResult"> & { + /** + * @generated from oneof agent.v1.PiBashExecResult.result + */ + result: { + /** + * @generated from field: agent.v1.PiBashExecSuccess success = 1; + */ + value: PiBashExecSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.PiBashExecError error = 2; + */ + value: PiBashExecError; + case: "error"; + } | { case: undefined; value?: undefined }; +}; + +/** + * Describes the message agent.v1.PiBashExecResult. + * Use `create(PiBashExecResultSchema)` to create a new message. + */ +export const PiBashExecResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 501); + +/** + * @generated from message agent.v1.PiEditExecArgs + */ +export type PiEditExecArgs = Message<"agent.v1.PiEditExecArgs"> & { + /** + * @generated from field: string path = 1; + */ + path: string; + + /** + * @generated from field: repeated agent.v1.PiEditReplacement edits = 2; + */ + edits: PiEditReplacement[]; +}; + +/** + * Describes the message agent.v1.PiEditExecArgs. + * Use `create(PiEditExecArgsSchema)` to create a new message. + */ +export const PiEditExecArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 502); + +/** + * @generated from message agent.v1.PiEditExecSuccess + */ +export type PiEditExecSuccess = Message<"agent.v1.PiEditExecSuccess"> & { + /** + * @generated from field: string output = 1; + */ + output: string; + + /** + * @generated from field: string diff = 2; + */ + diff: string; + + /** + * @generated from field: string patch = 3; + */ + patch: string; + + /** + * @generated from field: optional uint32 first_changed_line = 4; + */ + firstChangedLine?: number | undefined; +}; + +/** + * Describes the message agent.v1.PiEditExecSuccess. + * Use `create(PiEditExecSuccessSchema)` to create a new message. + */ +export const PiEditExecSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 503); + +/** + * @generated from message agent.v1.PiEditExecError + */ +export type PiEditExecError = Message<"agent.v1.PiEditExecError"> & { + /** + * @generated from field: string error = 1; + */ + error: string; +}; + +/** + * Describes the message agent.v1.PiEditExecError. + * Use `create(PiEditExecErrorSchema)` to create a new message. + */ +export const PiEditExecErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 504); + +/** + * @generated from message agent.v1.PiEditExecRejected + */ +export type PiEditExecRejected = Message<"agent.v1.PiEditExecRejected"> & { + /** + * @generated from field: string reason = 1; + */ + reason: string; +}; + +/** + * Describes the message agent.v1.PiEditExecRejected. + * Use `create(PiEditExecRejectedSchema)` to create a new message. + */ +export const PiEditExecRejectedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 505); + +/** + * @generated from message agent.v1.PiEditExecResult + */ +export type PiEditExecResult = Message<"agent.v1.PiEditExecResult"> & { + /** + * @generated from oneof agent.v1.PiEditExecResult.result + */ + result: { + /** + * @generated from field: agent.v1.PiEditExecSuccess success = 1; + */ + value: PiEditExecSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.PiEditExecError error = 2; + */ + value: PiEditExecError; + case: "error"; + } | { + /** + * @generated from field: agent.v1.PiEditExecRejected rejected = 3; + */ + value: PiEditExecRejected; + case: "rejected"; + } | { case: undefined; value?: undefined }; +}; + +/** + * Describes the message agent.v1.PiEditExecResult. + * Use `create(PiEditExecResultSchema)` to create a new message. + */ +export const PiEditExecResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 506); + +/** + * @generated from message agent.v1.PiWriteExecArgs + */ +export type PiWriteExecArgs = Message<"agent.v1.PiWriteExecArgs"> & { + /** + * @generated from field: string path = 1; + */ + path: string; + + /** + * @generated from field: string content = 2; + */ + content: string; +}; + +/** + * Describes the message agent.v1.PiWriteExecArgs. + * Use `create(PiWriteExecArgsSchema)` to create a new message. + */ +export const PiWriteExecArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 507); + +/** + * @generated from message agent.v1.PiWriteExecSuccess + */ +export type PiWriteExecSuccess = Message<"agent.v1.PiWriteExecSuccess"> & { + /** + * @generated from field: string output = 1; + */ + output: string; +}; + +/** + * Describes the message agent.v1.PiWriteExecSuccess. + * Use `create(PiWriteExecSuccessSchema)` to create a new message. + */ +export const PiWriteExecSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 508); + +/** + * @generated from message agent.v1.PiWriteExecError + */ +export type PiWriteExecError = Message<"agent.v1.PiWriteExecError"> & { + /** + * @generated from field: string error = 1; + */ + error: string; +}; + +/** + * Describes the message agent.v1.PiWriteExecError. + * Use `create(PiWriteExecErrorSchema)` to create a new message. + */ +export const PiWriteExecErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 509); + +/** + * @generated from message agent.v1.PiWriteExecRejected + */ +export type PiWriteExecRejected = Message<"agent.v1.PiWriteExecRejected"> & { + /** + * @generated from field: string reason = 1; + */ + reason: string; +}; + +/** + * Describes the message agent.v1.PiWriteExecRejected. + * Use `create(PiWriteExecRejectedSchema)` to create a new message. + */ +export const PiWriteExecRejectedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 510); + +/** + * @generated from message agent.v1.PiWriteExecResult + */ +export type PiWriteExecResult = Message<"agent.v1.PiWriteExecResult"> & { + /** + * @generated from oneof agent.v1.PiWriteExecResult.result + */ + result: { + /** + * @generated from field: agent.v1.PiWriteExecSuccess success = 1; + */ + value: PiWriteExecSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.PiWriteExecError error = 2; + */ + value: PiWriteExecError; + case: "error"; + } | { + /** + * @generated from field: agent.v1.PiWriteExecRejected rejected = 3; + */ + value: PiWriteExecRejected; + case: "rejected"; + } | { case: undefined; value?: undefined }; +}; + +/** + * Describes the message agent.v1.PiWriteExecResult. + * Use `create(PiWriteExecResultSchema)` to create a new message. + */ +export const PiWriteExecResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 511); + +/** + * @generated from message agent.v1.PiGrepExecArgs + */ +export type PiGrepExecArgs = Message<"agent.v1.PiGrepExecArgs"> & { + /** + * @generated from field: string pattern = 1; + */ + pattern: string; + + /** + * @generated from field: optional string path = 2; + */ + path?: string | undefined; + + /** + * @generated from field: optional string glob = 3; + */ + glob?: string | undefined; + + /** + * @generated from field: optional bool ignore_case = 4; + */ + ignoreCase?: boolean | undefined; + + /** + * @generated from field: optional bool literal = 5; + */ + literal?: boolean | undefined; + + /** + * @generated from field: optional int32 context = 6; + */ + context?: number | undefined; + + /** + * @generated from field: optional int32 limit = 7; + */ + limit?: number | undefined; +}; + +/** + * Describes the message agent.v1.PiGrepExecArgs. + * Use `create(PiGrepExecArgsSchema)` to create a new message. + */ +export const PiGrepExecArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 512); + +/** + * @generated from message agent.v1.PiGrepExecSuccess + */ +export type PiGrepExecSuccess = Message<"agent.v1.PiGrepExecSuccess"> & { + /** + * @generated from field: string output = 1; + */ + output: string; + + /** + * @generated from field: optional agent.v1.PiTruncation truncation = 2; + */ + truncation?: PiTruncation | undefined; + + /** + * @generated from field: optional uint32 match_limit_reached = 3; + */ + matchLimitReached?: number | undefined; + + /** + * @generated from field: bool lines_truncated = 4; + */ + linesTruncated: boolean; +}; + +/** + * Describes the message agent.v1.PiGrepExecSuccess. + * Use `create(PiGrepExecSuccessSchema)` to create a new message. + */ +export const PiGrepExecSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 513); + +/** + * @generated from message agent.v1.PiGrepExecError + */ +export type PiGrepExecError = Message<"agent.v1.PiGrepExecError"> & { + /** + * @generated from field: string error = 1; + */ + error: string; +}; + +/** + * Describes the message agent.v1.PiGrepExecError. + * Use `create(PiGrepExecErrorSchema)` to create a new message. + */ +export const PiGrepExecErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 514); + +/** + * @generated from message agent.v1.PiGrepExecResult + */ +export type PiGrepExecResult = Message<"agent.v1.PiGrepExecResult"> & { + /** + * @generated from oneof agent.v1.PiGrepExecResult.result + */ + result: { + /** + * @generated from field: agent.v1.PiGrepExecSuccess success = 1; + */ + value: PiGrepExecSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.PiGrepExecError error = 2; + */ + value: PiGrepExecError; + case: "error"; + } | { case: undefined; value?: undefined }; +}; + +/** + * Describes the message agent.v1.PiGrepExecResult. + * Use `create(PiGrepExecResultSchema)` to create a new message. + */ +export const PiGrepExecResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 515); + +/** + * @generated from message agent.v1.PiFindExecArgs + */ +export type PiFindExecArgs = Message<"agent.v1.PiFindExecArgs"> & { + /** + * @generated from field: string pattern = 1; + */ + pattern: string; + + /** + * @generated from field: optional string path = 2; + */ + path?: string | undefined; + + /** + * @generated from field: optional int32 limit = 3; + */ + limit?: number | undefined; +}; + +/** + * Describes the message agent.v1.PiFindExecArgs. + * Use `create(PiFindExecArgsSchema)` to create a new message. + */ +export const PiFindExecArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 516); + +/** + * @generated from message agent.v1.PiFindExecSuccess + */ +export type PiFindExecSuccess = Message<"agent.v1.PiFindExecSuccess"> & { + /** + * @generated from field: string output = 1; + */ + output: string; + + /** + * @generated from field: optional agent.v1.PiTruncation truncation = 2; + */ + truncation?: PiTruncation | undefined; + + /** + * @generated from field: optional uint32 result_limit_reached = 3; + */ + resultLimitReached?: number | undefined; +}; + +/** + * Describes the message agent.v1.PiFindExecSuccess. + * Use `create(PiFindExecSuccessSchema)` to create a new message. + */ +export const PiFindExecSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 517); + +/** + * @generated from message agent.v1.PiFindExecError + */ +export type PiFindExecError = Message<"agent.v1.PiFindExecError"> & { + /** + * @generated from field: string error = 1; + */ + error: string; +}; + +/** + * Describes the message agent.v1.PiFindExecError. + * Use `create(PiFindExecErrorSchema)` to create a new message. + */ +export const PiFindExecErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 518); + +/** + * @generated from message agent.v1.PiFindExecResult + */ +export type PiFindExecResult = Message<"agent.v1.PiFindExecResult"> & { + /** + * @generated from oneof agent.v1.PiFindExecResult.result + */ + result: { + /** + * @generated from field: agent.v1.PiFindExecSuccess success = 1; + */ + value: PiFindExecSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.PiFindExecError error = 2; + */ + value: PiFindExecError; + case: "error"; + } | { case: undefined; value?: undefined }; +}; + +/** + * Describes the message agent.v1.PiFindExecResult. + * Use `create(PiFindExecResultSchema)` to create a new message. + */ +export const PiFindExecResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 519); + +/** + * @generated from message agent.v1.PiLsExecArgs + */ +export type PiLsExecArgs = Message<"agent.v1.PiLsExecArgs"> & { + /** + * @generated from field: optional string path = 1; + */ + path?: string | undefined; + + /** + * @generated from field: optional int32 limit = 2; + */ + limit?: number | undefined; +}; + +/** + * Describes the message agent.v1.PiLsExecArgs. + * Use `create(PiLsExecArgsSchema)` to create a new message. + */ +export const PiLsExecArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 520); + +/** + * @generated from message agent.v1.PiLsExecSuccess + */ +export type PiLsExecSuccess = Message<"agent.v1.PiLsExecSuccess"> & { + /** + * @generated from field: string output = 1; + */ + output: string; + + /** + * @generated from field: optional agent.v1.PiTruncation truncation = 2; + */ + truncation?: PiTruncation | undefined; + + /** + * @generated from field: optional uint32 entry_limit_reached = 3; + */ + entryLimitReached?: number | undefined; +}; + +/** + * Describes the message agent.v1.PiLsExecSuccess. + * Use `create(PiLsExecSuccessSchema)` to create a new message. + */ +export const PiLsExecSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 521); + +/** + * @generated from message agent.v1.PiLsExecError + */ +export type PiLsExecError = Message<"agent.v1.PiLsExecError"> & { + /** + * @generated from field: string error = 1; + */ + error: string; +}; + +/** + * Describes the message agent.v1.PiLsExecError. + * Use `create(PiLsExecErrorSchema)` to create a new message. + */ +export const PiLsExecErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 522); + +/** + * @generated from message agent.v1.PiLsExecResult + */ +export type PiLsExecResult = Message<"agent.v1.PiLsExecResult"> & { + /** + * @generated from oneof agent.v1.PiLsExecResult.result + */ + result: { + /** + * @generated from field: agent.v1.PiLsExecSuccess success = 1; + */ + value: PiLsExecSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.PiLsExecError error = 2; + */ + value: PiLsExecError; + case: "error"; + } | { case: undefined; value?: undefined }; +}; + +/** + * Describes the message agent.v1.PiLsExecResult. + * Use `create(PiLsExecResultSchema)` to create a new message. + */ +export const PiLsExecResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 523); + +/** + * @generated from message agent.v1.McpServerNotFound + */ +export type McpServerNotFound = Message<"agent.v1.McpServerNotFound"> & { + /** + * @generated from field: string name = 1; + */ + name: string; + + /** + * @generated from field: repeated string available_servers = 2; + */ + availableServers: string[]; +}; + +/** + * Describes the message agent.v1.McpServerNotFound. + * Use `create(McpServerNotFoundSchema)` to create a new message. + */ +export const McpServerNotFoundSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 524); + +/** + * @generated from message agent.v1.McpApproved + */ +export type McpApproved = Message<"agent.v1.McpApproved"> & { +}; + +/** + * Describes the message agent.v1.McpApproved. + * Use `create(McpApprovedSchema)` to create a new message. + */ +export const McpApprovedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 525); + +/** + * @generated from message agent.v1.McpStateExecArgs + */ +export type McpStateExecArgs = Message<"agent.v1.McpStateExecArgs"> & { + /** + * @generated from field: repeated string server_identifiers = 1; + */ + serverIdentifiers: string[]; + + /** + * @generated from field: bool kick_only = 2; + */ + kickOnly: boolean; +}; + +/** + * Describes the message agent.v1.McpStateExecArgs. + * Use `create(McpStateExecArgsSchema)` to create a new message. + */ +export const McpStateExecArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 526); + +/** + * @generated from message agent.v1.McpStateServer + */ +export type McpStateServer = Message<"agent.v1.McpStateServer"> & { + /** + * @generated from field: string server_name = 1; + */ + serverName: string; + + /** + * @generated from field: string server_identifier = 2; + */ + serverIdentifier: string; + + /** + * @generated from field: optional string plugin = 3; + */ + plugin?: string | undefined; + + /** + * @generated from field: optional string marketplace = 4; + */ + marketplace?: string | undefined; + + /** + * @generated from field: repeated agent.v1.McpToolDefinition tools = 5; + */ + tools: McpToolDefinition[]; + + /** + * @generated from field: repeated agent.v1.McpInstructions instructions = 6; + */ + instructions: McpInstructions[]; + + /** + * @generated from field: optional string status = 7; + */ + status?: string | undefined; +}; + +/** + * Describes the message agent.v1.McpStateServer. + * Use `create(McpStateServerSchema)` to create a new message. + */ +export const McpStateServerSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 527); + +/** + * @generated from message agent.v1.McpStateSuccess + */ +export type McpStateSuccess = Message<"agent.v1.McpStateSuccess"> & { + /** + * @generated from field: repeated agent.v1.McpStateServer servers = 1; + */ + servers: McpStateServer[]; +}; + +/** + * Describes the message agent.v1.McpStateSuccess. + * Use `create(McpStateSuccessSchema)` to create a new message. + */ +export const McpStateSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 528); + +/** + * @generated from message agent.v1.McpStateError + */ +export type McpStateError = Message<"agent.v1.McpStateError"> & { + /** + * @generated from field: string error = 1; + */ + error: string; +}; + +/** + * Describes the message agent.v1.McpStateError. + * Use `create(McpStateErrorSchema)` to create a new message. + */ +export const McpStateErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 529); + +/** + * @generated from message agent.v1.McpStateRejected + */ +export type McpStateRejected = Message<"agent.v1.McpStateRejected"> & { + /** + * @generated from field: string reason = 1; + */ + reason: string; +}; + +/** + * Describes the message agent.v1.McpStateRejected. + * Use `create(McpStateRejectedSchema)` to create a new message. + */ +export const McpStateRejectedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 530); + +/** + * @generated from message agent.v1.McpStateExecResult + */ +export type McpStateExecResult = Message<"agent.v1.McpStateExecResult"> & { + /** + * @generated from oneof agent.v1.McpStateExecResult.result + */ + result: { + /** + * @generated from field: agent.v1.McpStateSuccess success = 1; + */ + value: McpStateSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.McpStateError error = 2; + */ + value: McpStateError; + case: "error"; + } | { + /** + * @generated from field: agent.v1.McpStateRejected rejected = 3; + */ + value: McpStateRejected; + case: "rejected"; + } | { case: undefined; value?: undefined }; +}; + +/** + * Describes the message agent.v1.McpStateExecResult. + * Use `create(McpStateExecResultSchema)` to create a new message. + */ +export const McpStateExecResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 531); + +/** + * @generated from message agent.v1.CommandClassifierResult_ClassifiedCommand + */ +export type CommandClassifierResult_ClassifiedCommand = Message<"agent.v1.CommandClassifierResult_ClassifiedCommand"> & { + /** + * @generated from field: string name = 1; + */ + name: string; + + /** + * @generated from field: repeated string arguments = 2; + */ + arguments: string[]; + + /** + * @generated from field: optional string suggested_allowlist_entry = 3; + */ + suggestedAllowlistEntry?: string | undefined; + + /** + * @generated from field: repeated string subcommand_tokens = 4; + */ + subcommandTokens: string[]; +}; + +/** + * Describes the message agent.v1.CommandClassifierResult_ClassifiedCommand. + * Use `create(CommandClassifierResult_ClassifiedCommandSchema)` to create a new message. + */ +export const CommandClassifierResult_ClassifiedCommandSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 532); + +/** + * @generated from message agent.v1.CommandClassifierResult + */ +export type CommandClassifierResult = Message<"agent.v1.CommandClassifierResult"> & { + /** + * @generated from field: repeated agent.v1.CommandClassifierResult_ClassifiedCommand commands = 1; + */ + commands: CommandClassifierResult_ClassifiedCommand[]; + + /** + * @generated from field: agent.v1.CommandClassifierResult_SuggestedSandboxMode suggested_sandbox_mode = 2; + */ + suggestedSandboxMode: CommandClassifierResult_SuggestedSandboxMode; + + /** + * @generated from field: bool classification_failed = 3; + */ + classificationFailed: boolean; +}; + +/** + * Describes the message agent.v1.CommandClassifierResult. + * Use `create(CommandClassifierResultSchema)` to create a new message. + */ +export const CommandClassifierResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 533); + +/** + * @generated from message agent.v1.ShellCommandParsingResult_Redirect + */ +export type ShellCommandParsingResult_Redirect = Message<"agent.v1.ShellCommandParsingResult_Redirect"> & { + /** + * @generated from field: string operator = 1; + */ + operator: string; + + /** + * @generated from field: repeated uint32 destination_fds = 2; + */ + destinationFds: number[]; + + /** + * @generated from field: string target_node_type = 3; + */ + targetNodeType: string; + + /** + * @generated from field: optional string target_text = 4; + */ + targetText?: string | undefined; +}; + +/** + * Describes the message agent.v1.ShellCommandParsingResult_Redirect. + * Use `create(ShellCommandParsingResult_RedirectSchema)` to create a new message. + */ +export const ShellCommandParsingResult_RedirectSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 534); + +/** + * @generated from message agent.v1.ShellAllowlistPrecheckArgs + */ +export type ShellAllowlistPrecheckArgs = Message<"agent.v1.ShellAllowlistPrecheckArgs"> & { + /** + * @generated from field: string command = 1; + */ + command: string; + + /** + * @generated from field: string working_directory = 2; + */ + workingDirectory: string; + + /** + * @generated from field: agent.v1.ShellCommandParsingResult parsing_result = 3; + */ + parsingResult?: ShellCommandParsingResult | undefined; + + /** + * @generated from field: optional agent.v1.CommandClassifierResult classifier_result = 4; + */ + classifierResult?: CommandClassifierResult | undefined; + + /** + * @generated from field: optional string tool_call_id = 5; + */ + toolCallId?: string | undefined; +}; + +/** + * Describes the message agent.v1.ShellAllowlistPrecheckArgs. + * Use `create(ShellAllowlistPrecheckArgsSchema)` to create a new message. + */ +export const ShellAllowlistPrecheckArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 535); + +/** + * @generated from message agent.v1.ShellAllowlistPrecheckResult + */ +export type ShellAllowlistPrecheckResult = Message<"agent.v1.ShellAllowlistPrecheckResult"> & { + /** + * @generated from field: bool allowlisted = 1; + */ + allowlisted: boolean; +}; + +/** + * Describes the message agent.v1.ShellAllowlistPrecheckResult. + * Use `create(ShellAllowlistPrecheckResultSchema)` to create a new message. + */ +export const ShellAllowlistPrecheckResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 536); + +/** + * @generated from message agent.v1.McpAllowlistPrecheckArgs + */ +export type McpAllowlistPrecheckArgs = Message<"agent.v1.McpAllowlistPrecheckArgs"> & { + /** + * @generated from field: string provider_identifier = 1; + */ + providerIdentifier: string; + + /** + * @generated from field: string tool_name = 2; + */ + toolName: string; + + /** + * @generated from field: optional string tool_call_id = 3; + */ + toolCallId?: string | undefined; +}; + +/** + * Describes the message agent.v1.McpAllowlistPrecheckArgs. + * Use `create(McpAllowlistPrecheckArgsSchema)` to create a new message. + */ +export const McpAllowlistPrecheckArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 537); + +/** + * @generated from message agent.v1.McpAllowlistPrecheckResult + */ +export type McpAllowlistPrecheckResult = Message<"agent.v1.McpAllowlistPrecheckResult"> & { + /** + * @generated from field: bool allowlisted = 1; + */ + allowlisted: boolean; +}; + +/** + * Describes the message agent.v1.McpAllowlistPrecheckResult. + * Use `create(McpAllowlistPrecheckResultSchema)` to create a new message. + */ +export const McpAllowlistPrecheckResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 538); + +/** + * @generated from message agent.v1.WebFetchAllowlistPrecheckArgs + */ +export type WebFetchAllowlistPrecheckArgs = Message<"agent.v1.WebFetchAllowlistPrecheckArgs"> & { + /** + * @generated from field: string url = 1; + */ + url: string; + + /** + * @generated from field: optional string tool_call_id = 2; + */ + toolCallId?: string | undefined; +}; + +/** + * Describes the message agent.v1.WebFetchAllowlistPrecheckArgs. + * Use `create(WebFetchAllowlistPrecheckArgsSchema)` to create a new message. + */ +export const WebFetchAllowlistPrecheckArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 539); + +/** + * @generated from message agent.v1.WebFetchAllowlistPrecheckResult + */ +export type WebFetchAllowlistPrecheckResult = Message<"agent.v1.WebFetchAllowlistPrecheckResult"> & { + /** + * @generated from field: bool allowlisted = 1; + */ + allowlisted: boolean; +}; + +/** + * Describes the message agent.v1.WebFetchAllowlistPrecheckResult. + * Use `create(WebFetchAllowlistPrecheckResultSchema)` to create a new message. + */ +export const WebFetchAllowlistPrecheckResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 540); + +/** + * @generated from message agent.v1.SmartModeApproval + */ +export type SmartModeApproval = Message<"agent.v1.SmartModeApproval"> & { + /** + * @generated from field: string request_id = 1; + */ + requestId: string; + + /** + * @generated from field: string reason = 2; + */ + reason: string; +}; + +/** + * Describes the message agent.v1.SmartModeApproval. + * Use `create(SmartModeApprovalSchema)` to create a new message. + */ +export const SmartModeApprovalSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 541); + +/** + * @generated from message agent.v1.ShellOutputNotificationConfig + */ +export type ShellOutputNotificationConfig = Message<"agent.v1.ShellOutputNotificationConfig"> & { + /** + * @generated from field: string pattern = 1; + */ + pattern: string; + + /** + * @generated from field: string reason = 2; + */ + reason: string; + + /** + * @generated from field: optional double debounce = 3; + */ + debounce?: number | undefined; + + /** + * @generated from field: optional int32 notification_limit = 4; + */ + notificationLimit?: number | undefined; +}; + +/** + * Describes the message agent.v1.ShellOutputNotificationConfig. + * Use `create(ShellOutputNotificationConfigSchema)` to create a new message. + */ +export const ShellOutputNotificationConfigSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 542); + +/** + * @generated from message agent.v1.ShellHookApprovalRequirement + */ +export type ShellHookApprovalRequirement = Message<"agent.v1.ShellHookApprovalRequirement"> & { + /** + * @generated from field: agent.v1.ShellHookApprovalRequirement_Kind kind = 1; + */ + kind: ShellHookApprovalRequirement_Kind; + + /** + * @generated from field: optional string reason = 2; + */ + reason?: string | undefined; +}; + +/** + * Describes the message agent.v1.ShellHookApprovalRequirement. + * Use `create(ShellHookApprovalRequirementSchema)` to create a new message. + */ +export const ShellHookApprovalRequirementSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 543); + +/** + * @generated from message agent.v1.ForceBackgroundShellArgs + */ +export type ForceBackgroundShellArgs = Message<"agent.v1.ForceBackgroundShellArgs"> & { + /** + * @generated from field: string tool_call_id = 1; + */ + toolCallId: string; +}; + +/** + * Describes the message agent.v1.ForceBackgroundShellArgs. + * Use `create(ForceBackgroundShellArgsSchema)` to create a new message. + */ +export const ForceBackgroundShellArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 544); + +/** + * @generated from message agent.v1.ForceBackgroundShellResult + */ +export type ForceBackgroundShellResult = Message<"agent.v1.ForceBackgroundShellResult"> & { + /** + * @generated from field: agent.v1.ForceBackgroundShellStatus status = 1; + */ + status: ForceBackgroundShellStatus; + + /** + * @generated from field: optional agent.v1.ShellResult shell_result = 2; + */ + shellResult?: ShellResult | undefined; +}; + +/** + * Describes the message agent.v1.ForceBackgroundShellResult. + * Use `create(ForceBackgroundShellResultSchema)` to create a new message. + */ +export const ForceBackgroundShellResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 545); + +/** + * @generated from message agent.v1.HookAdditionalContext + */ +export type HookAdditionalContext = Message<"agent.v1.HookAdditionalContext"> & { + /** + * @generated from field: string hook_event_name = 1; + */ + hookEventName: string; + + /** + * @generated from field: string content = 2; + */ + content: string; +}; + +/** + * Describes the message agent.v1.HookAdditionalContext. + * Use `create(HookAdditionalContextSchema)` to create a new message. + */ +export const HookAdditionalContextSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 546); + +/** + * @generated from message agent.v1.ShellStreamHookContext + */ +export type ShellStreamHookContext = Message<"agent.v1.ShellStreamHookContext"> & { + /** + * @generated from field: repeated agent.v1.HookAdditionalContext hook_additional_contexts = 1; + */ + hookAdditionalContexts: HookAdditionalContext[]; +}; + +/** + * Describes the message agent.v1.ShellStreamHookContext. + * Use `create(ShellStreamHookContextSchema)` to create a new message. + */ +export const ShellStreamHookContextSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 547); + +/** + * Subset: only the fields this client reads. Upstream also defines model_id=3, readonly=5, + * resume_agent_id=6, run_in_background=7, continuation_config=8, parent_conversation_id=9, + * credentials 10-12, interrupt=13, mode=14, fork_agent_id=15, root_parent_conversation_id=16, + * selected_context=17, direct_meta_parent_child_subagent=18, environment=19, cloud_base_branch=20. + * Those decode into unknown fields; do not reuse these numbers. + * + * @generated from message agent.v1.SubagentArgs + */ +export type SubagentArgs = Message<"agent.v1.SubagentArgs"> & { + /** + * @generated from field: string tool_call_id = 1; + */ + toolCallId: string; + + /** + * @generated from field: string subagent_type = 2; + */ + subagentType: string; + + /** + * @generated from field: string prompt = 4; + */ + prompt: string; +}; + +/** + * Describes the message agent.v1.SubagentArgs. + * Use `create(SubagentArgsSchema)` to create a new message. + */ +export const SubagentArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 548); + +/** + * @generated from message agent.v1.SubagentSuccess + */ +export type SubagentSuccess = Message<"agent.v1.SubagentSuccess"> & { + /** + * @generated from field: string agent_id = 1; + */ + agentId: string; + + /** + * @generated from field: optional string final_message = 2; + */ + finalMessage?: string | undefined; + + /** + * @generated from field: int32 tool_call_count = 3; + */ + toolCallCount: number; + + /** + * @generated from field: agent.v1.SubagentBackgroundReason background_reason = 4; + */ + backgroundReason: SubagentBackgroundReason; + + /** + * @generated from field: optional string transcript_path = 5; + */ + transcriptPath?: string | undefined; +}; + +/** + * Describes the message agent.v1.SubagentSuccess. + * Use `create(SubagentSuccessSchema)` to create a new message. + */ +export const SubagentSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 549); + +/** + * @generated from message agent.v1.SubagentError + */ +export type SubagentError = Message<"agent.v1.SubagentError"> & { + /** + * @generated from field: optional string agent_id = 1; + */ + agentId?: string | undefined; + + /** + * @generated from field: string error = 2; + */ + error: string; +}; + +/** + * Describes the message agent.v1.SubagentError. + * Use `create(SubagentErrorSchema)` to create a new message. + */ +export const SubagentErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 550); + +/** + * @generated from message agent.v1.SubagentResult + */ +export type SubagentResult = Message<"agent.v1.SubagentResult"> & { + /** + * @generated from oneof agent.v1.SubagentResult.result + */ + result: { + /** + * @generated from field: agent.v1.SubagentSuccess success = 1; + */ + value: SubagentSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.SubagentError error = 2; + */ + value: SubagentError; + case: "error"; + } | { case: undefined; value?: undefined }; +}; + +/** + * Describes the message agent.v1.SubagentResult. + * Use `create(SubagentResultSchema)` to create a new message. + */ +export const SubagentResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 551); + +/** + * @generated from message agent.v1.SubagentAwaitArgs + */ +export type SubagentAwaitArgs = Message<"agent.v1.SubagentAwaitArgs"> & { + /** + * @generated from field: string agent_id = 1; + */ + agentId: string; + + /** + * @generated from field: uint32 timeout_ms = 2; + */ + timeoutMs: number; +}; + +/** + * Describes the message agent.v1.SubagentAwaitArgs. + * Use `create(SubagentAwaitArgsSchema)` to create a new message. + */ +export const SubagentAwaitArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 552); + +/** + * @generated from message agent.v1.SubagentAwaitComplete + */ +export type SubagentAwaitComplete = Message<"agent.v1.SubagentAwaitComplete"> & { + /** + * @generated from field: string agent_id = 1; + */ + agentId: string; + + /** + * @generated from field: optional string transcript_path = 2; + */ + transcriptPath?: string | undefined; + + /** + * @generated from field: int32 tool_call_count = 3; + */ + toolCallCount: number; + + /** + * @generated from field: optional string final_message = 4; + */ + finalMessage?: string | undefined; +}; + +/** + * Describes the message agent.v1.SubagentAwaitComplete. + * Use `create(SubagentAwaitCompleteSchema)` to create a new message. + */ +export const SubagentAwaitCompleteSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 553); + +/** + * @generated from message agent.v1.SubagentAwaitStillRunning + */ +export type SubagentAwaitStillRunning = Message<"agent.v1.SubagentAwaitStillRunning"> & { + /** + * @generated from field: string agent_id = 1; + */ + agentId: string; + + /** + * @generated from field: optional string transcript_path = 2; + */ + transcriptPath?: string | undefined; +}; + +/** + * Describes the message agent.v1.SubagentAwaitStillRunning. + * Use `create(SubagentAwaitStillRunningSchema)` to create a new message. + */ +export const SubagentAwaitStillRunningSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 554); + +/** + * @generated from message agent.v1.SubagentAwaitNotFound + */ +export type SubagentAwaitNotFound = Message<"agent.v1.SubagentAwaitNotFound"> & { + /** + * @generated from field: string agent_id = 1; + */ + agentId: string; +}; + +/** + * Describes the message agent.v1.SubagentAwaitNotFound. + * Use `create(SubagentAwaitNotFoundSchema)` to create a new message. + */ +export const SubagentAwaitNotFoundSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 555); + +/** + * @generated from message agent.v1.SubagentAwaitError + */ +export type SubagentAwaitError = Message<"agent.v1.SubagentAwaitError"> & { + /** + * @generated from field: optional string agent_id = 1; + */ + agentId?: string | undefined; + + /** + * @generated from field: string error = 2; + */ + error: string; +}; + +/** + * Describes the message agent.v1.SubagentAwaitError. + * Use `create(SubagentAwaitErrorSchema)` to create a new message. + */ +export const SubagentAwaitErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 556); + +/** + * @generated from message agent.v1.SubagentAwaitResult + */ +export type SubagentAwaitResult = Message<"agent.v1.SubagentAwaitResult"> & { + /** + * @generated from oneof agent.v1.SubagentAwaitResult.result + */ + result: { + /** + * @generated from field: agent.v1.SubagentAwaitComplete complete = 1; + */ + value: SubagentAwaitComplete; + case: "complete"; + } | { + /** + * @generated from field: agent.v1.SubagentAwaitStillRunning still_running = 2; + */ + value: SubagentAwaitStillRunning; + case: "stillRunning"; + } | { + /** + * @generated from field: agent.v1.SubagentAwaitNotFound not_found = 3; + */ + value: SubagentAwaitNotFound; + case: "notFound"; + } | { + /** + * @generated from field: agent.v1.SubagentAwaitError error = 4; + */ + value: SubagentAwaitError; + case: "error"; + } | { case: undefined; value?: undefined }; +}; + +/** + * Describes the message agent.v1.SubagentAwaitResult. + * Use `create(SubagentAwaitResultSchema)` to create a new message. + */ +export const SubagentAwaitResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 557); + +/** + * @generated from message agent.v1.ForceBackgroundSubagentArgs + */ +export type ForceBackgroundSubagentArgs = Message<"agent.v1.ForceBackgroundSubagentArgs"> & { + /** + * @generated from field: string tool_call_id = 1; + */ + toolCallId: string; +}; + +/** + * Describes the message agent.v1.ForceBackgroundSubagentArgs. + * Use `create(ForceBackgroundSubagentArgsSchema)` to create a new message. + */ +export const ForceBackgroundSubagentArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 558); + +/** + * @generated from message agent.v1.ForceBackgroundSubagentResult + */ +export type ForceBackgroundSubagentResult = Message<"agent.v1.ForceBackgroundSubagentResult"> & { + /** + * @generated from field: agent.v1.ForceBackgroundSubagentStatus status = 1; + */ + status: ForceBackgroundSubagentStatus; +}; + +/** + * Describes the message agent.v1.ForceBackgroundSubagentResult. + * Use `create(ForceBackgroundSubagentResultSchema)` to create a new message. + */ +export const ForceBackgroundSubagentResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 559); + +/** + * Payload not modelled: this client runs no Cursor hooks and only reads the oneof case. + * + * @generated from message agent.v1.PreCompactRequestQuery + */ +export type PreCompactRequestQuery = Message<"agent.v1.PreCompactRequestQuery"> & { +}; + +/** + * Describes the message agent.v1.PreCompactRequestQuery. + * Use `create(PreCompactRequestQuerySchema)` to create a new message. + */ +export const PreCompactRequestQuerySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 560); + +/** + * Payload not modelled: this client runs no Cursor hooks and only reads the oneof case. + * + * @generated from message agent.v1.SubagentStartRequestQuery + */ +export type SubagentStartRequestQuery = Message<"agent.v1.SubagentStartRequestQuery"> & { +}; + +/** + * Describes the message agent.v1.SubagentStartRequestQuery. + * Use `create(SubagentStartRequestQuerySchema)` to create a new message. + */ +export const SubagentStartRequestQuerySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 561); + +/** + * Payload not modelled: this client runs no Cursor hooks and only reads the oneof case. + * + * @generated from message agent.v1.SubagentStopRequestQuery + */ +export type SubagentStopRequestQuery = Message<"agent.v1.SubagentStopRequestQuery"> & { +}; + +/** + * Describes the message agent.v1.SubagentStopRequestQuery. + * Use `create(SubagentStopRequestQuerySchema)` to create a new message. + */ +export const SubagentStopRequestQuerySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 562); + +/** + * Payload not modelled: this client runs no Cursor hooks and only reads the oneof case. + * + * @generated from message agent.v1.PreToolUseRequestQuery + */ +export type PreToolUseRequestQuery = Message<"agent.v1.PreToolUseRequestQuery"> & { +}; + +/** + * Describes the message agent.v1.PreToolUseRequestQuery. + * Use `create(PreToolUseRequestQuerySchema)` to create a new message. + */ +export const PreToolUseRequestQuerySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 563); + +/** + * Payload not modelled: this client runs no Cursor hooks and only reads the oneof case. + * + * @generated from message agent.v1.PostToolUseRequestQuery + */ +export type PostToolUseRequestQuery = Message<"agent.v1.PostToolUseRequestQuery"> & { +}; + +/** + * Describes the message agent.v1.PostToolUseRequestQuery. + * Use `create(PostToolUseRequestQuerySchema)` to create a new message. + */ +export const PostToolUseRequestQuerySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 564); + +/** + * Payload not modelled: this client runs no Cursor hooks and only reads the oneof case. + * + * @generated from message agent.v1.PostToolUseFailureRequestQuery + */ +export type PostToolUseFailureRequestQuery = Message<"agent.v1.PostToolUseFailureRequestQuery"> & { +}; + +/** + * Describes the message agent.v1.PostToolUseFailureRequestQuery. + * Use `create(PostToolUseFailureRequestQuerySchema)` to create a new message. + */ +export const PostToolUseFailureRequestQuerySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 565); + +/** + * Payload not modelled: this client runs no Cursor hooks and only reads the oneof case. + * + * @generated from message agent.v1.BeforeSubmitPromptRequestQuery + */ +export type BeforeSubmitPromptRequestQuery = Message<"agent.v1.BeforeSubmitPromptRequestQuery"> & { +}; + +/** + * Describes the message agent.v1.BeforeSubmitPromptRequestQuery. + * Use `create(BeforeSubmitPromptRequestQuerySchema)` to create a new message. + */ +export const BeforeSubmitPromptRequestQuerySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 566); + +/** + * Payload not modelled: this client runs no Cursor hooks and only reads the oneof case. + * + * @generated from message agent.v1.AfterAgentResponseRequestQuery + */ +export type AfterAgentResponseRequestQuery = Message<"agent.v1.AfterAgentResponseRequestQuery"> & { +}; + +/** + * Describes the message agent.v1.AfterAgentResponseRequestQuery. + * Use `create(AfterAgentResponseRequestQuerySchema)` to create a new message. + */ +export const AfterAgentResponseRequestQuerySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 567); + +/** + * Payload not modelled: this client runs no Cursor hooks and only reads the oneof case. + * + * @generated from message agent.v1.AfterAgentThoughtRequestQuery + */ +export type AfterAgentThoughtRequestQuery = Message<"agent.v1.AfterAgentThoughtRequestQuery"> & { +}; + +/** + * Describes the message agent.v1.AfterAgentThoughtRequestQuery. + * Use `create(AfterAgentThoughtRequestQuerySchema)` to create a new message. + */ +export const AfterAgentThoughtRequestQuerySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 568); + +/** + * Payload not modelled: this client runs no Cursor hooks and only reads the oneof case. + * + * @generated from message agent.v1.StopRequestQuery + */ +export type StopRequestQuery = Message<"agent.v1.StopRequestQuery"> & { +}; + +/** + * Describes the message agent.v1.StopRequestQuery. + * Use `create(StopRequestQuerySchema)` to create a new message. + */ +export const StopRequestQuerySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 569); + +/** + * @generated from message agent.v1.PreCompactRequestResponse + */ +export type PreCompactRequestResponse = Message<"agent.v1.PreCompactRequestResponse"> & { + /** + * @generated from field: optional string user_message = 1; + */ + userMessage?: string | undefined; +}; + +/** + * Describes the message agent.v1.PreCompactRequestResponse. + * Use `create(PreCompactRequestResponseSchema)` to create a new message. + */ +export const PreCompactRequestResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 570); + +/** + * @generated from message agent.v1.SubagentStartRequestResponse + */ +export type SubagentStartRequestResponse = Message<"agent.v1.SubagentStartRequestResponse"> & { + /** + * @generated from field: optional string permission = 1; + */ + permission?: string | undefined; + + /** + * @generated from field: optional string user_message = 2; + */ + userMessage?: string | undefined; + + /** + * @generated from field: optional string additional_context = 3; + */ + additionalContext?: string | undefined; +}; + +/** + * Describes the message agent.v1.SubagentStartRequestResponse. + * Use `create(SubagentStartRequestResponseSchema)` to create a new message. + */ +export const SubagentStartRequestResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 571); + +/** + * @generated from message agent.v1.SubagentStopRequestResponse + */ +export type SubagentStopRequestResponse = Message<"agent.v1.SubagentStopRequestResponse"> & { + /** + * @generated from field: optional string followup_message = 1; + */ + followupMessage?: string | undefined; + + /** + * @generated from field: optional string additional_context = 2; + */ + additionalContext?: string | undefined; +}; + +/** + * Describes the message agent.v1.SubagentStopRequestResponse. + * Use `create(SubagentStopRequestResponseSchema)` to create a new message. + */ +export const SubagentStopRequestResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 572); + +/** + * @generated from message agent.v1.PreToolUseRequestResponse + */ +export type PreToolUseRequestResponse = Message<"agent.v1.PreToolUseRequestResponse"> & { + /** + * @generated from field: optional string permission = 1; + */ + permission?: string | undefined; + + /** + * @generated from field: optional string user_message = 2; + */ + userMessage?: string | undefined; + + /** + * @generated from field: optional string agent_message = 3; + */ + agentMessage?: string | undefined; + + /** + * @generated from field: optional string updated_input = 4; + */ + updatedInput?: string | undefined; + + /** + * @generated from field: optional string additional_context = 5; + */ + additionalContext?: string | undefined; +}; + +/** + * Describes the message agent.v1.PreToolUseRequestResponse. + * Use `create(PreToolUseRequestResponseSchema)` to create a new message. + */ +export const PreToolUseRequestResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 573); + +/** + * @generated from message agent.v1.PostToolUseRequestResponse + */ +export type PostToolUseRequestResponse = Message<"agent.v1.PostToolUseRequestResponse"> & { + /** + * @generated from field: optional string additional_context = 1; + */ + additionalContext?: string | undefined; +}; + +/** + * Describes the message agent.v1.PostToolUseRequestResponse. + * Use `create(PostToolUseRequestResponseSchema)` to create a new message. + */ +export const PostToolUseRequestResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 574); + +/** + * @generated from message agent.v1.PostToolUseFailureRequestResponse + */ +export type PostToolUseFailureRequestResponse = Message<"agent.v1.PostToolUseFailureRequestResponse"> & { + /** + * @generated from field: optional string additional_context = 1; + */ + additionalContext?: string | undefined; +}; + +/** + * Describes the message agent.v1.PostToolUseFailureRequestResponse. + * Use `create(PostToolUseFailureRequestResponseSchema)` to create a new message. + */ +export const PostToolUseFailureRequestResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 575); + +/** + * @generated from message agent.v1.BeforeSubmitPromptRequestResponse + */ +export type BeforeSubmitPromptRequestResponse = Message<"agent.v1.BeforeSubmitPromptRequestResponse"> & { + /** + * @generated from field: optional bool continue = 1; + */ + continue?: boolean | undefined; + + /** + * @generated from field: optional string user_message = 2; + */ + userMessage?: string | undefined; + + /** + * @generated from field: optional string additional_context = 3; + */ + additionalContext?: string | undefined; +}; + +/** + * Describes the message agent.v1.BeforeSubmitPromptRequestResponse. + * Use `create(BeforeSubmitPromptRequestResponseSchema)` to create a new message. + */ +export const BeforeSubmitPromptRequestResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 576); + +/** + * @generated from message agent.v1.AfterAgentResponseRequestResponse + */ +export type AfterAgentResponseRequestResponse = Message<"agent.v1.AfterAgentResponseRequestResponse"> & { +}; + +/** + * Describes the message agent.v1.AfterAgentResponseRequestResponse. + * Use `create(AfterAgentResponseRequestResponseSchema)` to create a new message. + */ +export const AfterAgentResponseRequestResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 577); + +/** + * @generated from message agent.v1.AfterAgentThoughtRequestResponse + */ +export type AfterAgentThoughtRequestResponse = Message<"agent.v1.AfterAgentThoughtRequestResponse"> & { +}; + +/** + * Describes the message agent.v1.AfterAgentThoughtRequestResponse. + * Use `create(AfterAgentThoughtRequestResponseSchema)` to create a new message. + */ +export const AfterAgentThoughtRequestResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 578); + +/** + * @generated from message agent.v1.StopRequestResponse + */ +export type StopRequestResponse = Message<"agent.v1.StopRequestResponse"> & { + /** + * @generated from field: optional string followup_message = 1; + */ + followupMessage?: string | undefined; +}; + +/** + * Describes the message agent.v1.StopRequestResponse. + * Use `create(StopRequestResponseSchema)` to create a new message. + */ +export const StopRequestResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 579); + +/** + * @generated from message agent.v1.ExecuteHookRequest + */ +export type ExecuteHookRequest = Message<"agent.v1.ExecuteHookRequest"> & { + /** + * @generated from oneof agent.v1.ExecuteHookRequest.request + */ + request: { + /** + * @generated from field: agent.v1.PreCompactRequestQuery pre_compact = 1; + */ + value: PreCompactRequestQuery; + case: "preCompact"; + } | { + /** + * @generated from field: agent.v1.SubagentStartRequestQuery subagent_start = 2; + */ + value: SubagentStartRequestQuery; + case: "subagentStart"; + } | { + /** + * @generated from field: agent.v1.SubagentStopRequestQuery subagent_stop = 3; + */ + value: SubagentStopRequestQuery; + case: "subagentStop"; + } | { + /** + * @generated from field: agent.v1.PreToolUseRequestQuery pre_tool_use = 4; + */ + value: PreToolUseRequestQuery; + case: "preToolUse"; + } | { + /** + * @generated from field: agent.v1.PostToolUseRequestQuery post_tool_use = 5; + */ + value: PostToolUseRequestQuery; + case: "postToolUse"; + } | { + /** + * @generated from field: agent.v1.PostToolUseFailureRequestQuery post_tool_use_failure = 6; + */ + value: PostToolUseFailureRequestQuery; + case: "postToolUseFailure"; + } | { + /** + * @generated from field: agent.v1.BeforeSubmitPromptRequestQuery before_submit_prompt = 7; + */ + value: BeforeSubmitPromptRequestQuery; + case: "beforeSubmitPrompt"; + } | { + /** + * @generated from field: agent.v1.AfterAgentResponseRequestQuery after_agent_response = 8; + */ + value: AfterAgentResponseRequestQuery; + case: "afterAgentResponse"; + } | { + /** + * @generated from field: agent.v1.AfterAgentThoughtRequestQuery after_agent_thought = 9; + */ + value: AfterAgentThoughtRequestQuery; + case: "afterAgentThought"; + } | { + /** + * @generated from field: agent.v1.StopRequestQuery stop = 11; + */ + value: StopRequestQuery; + case: "stop"; + } | { case: undefined; value?: undefined }; +}; + +/** + * Describes the message agent.v1.ExecuteHookRequest. + * Use `create(ExecuteHookRequestSchema)` to create a new message. + */ +export const ExecuteHookRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 580); + +/** + * @generated from message agent.v1.ExecuteHookResponse + */ +export type ExecuteHookResponse = Message<"agent.v1.ExecuteHookResponse"> & { + /** + * @generated from oneof agent.v1.ExecuteHookResponse.response + */ + response: { + /** + * @generated from field: agent.v1.PreCompactRequestResponse pre_compact = 1; + */ + value: PreCompactRequestResponse; + case: "preCompact"; + } | { + /** + * @generated from field: agent.v1.SubagentStartRequestResponse subagent_start = 2; + */ + value: SubagentStartRequestResponse; + case: "subagentStart"; + } | { + /** + * @generated from field: agent.v1.SubagentStopRequestResponse subagent_stop = 3; + */ + value: SubagentStopRequestResponse; + case: "subagentStop"; + } | { + /** + * @generated from field: agent.v1.PreToolUseRequestResponse pre_tool_use = 4; + */ + value: PreToolUseRequestResponse; + case: "preToolUse"; + } | { + /** + * @generated from field: agent.v1.PostToolUseRequestResponse post_tool_use = 5; + */ + value: PostToolUseRequestResponse; + case: "postToolUse"; + } | { + /** + * @generated from field: agent.v1.PostToolUseFailureRequestResponse post_tool_use_failure = 6; + */ + value: PostToolUseFailureRequestResponse; + case: "postToolUseFailure"; + } | { + /** + * @generated from field: agent.v1.BeforeSubmitPromptRequestResponse before_submit_prompt = 7; + */ + value: BeforeSubmitPromptRequestResponse; + case: "beforeSubmitPrompt"; + } | { + /** + * @generated from field: agent.v1.AfterAgentResponseRequestResponse after_agent_response = 8; + */ + value: AfterAgentResponseRequestResponse; + case: "afterAgentResponse"; + } | { + /** + * @generated from field: agent.v1.AfterAgentThoughtRequestResponse after_agent_thought = 9; + */ + value: AfterAgentThoughtRequestResponse; + case: "afterAgentThought"; + } | { + /** + * @generated from field: agent.v1.StopRequestResponse stop = 11; + */ + value: StopRequestResponse; + case: "stop"; + } | { case: undefined; value?: undefined }; +}; + +/** + * Describes the message agent.v1.ExecuteHookResponse. + * Use `create(ExecuteHookResponseSchema)` to create a new message. + */ +export const ExecuteHookResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 581); + +/** + * @generated from message agent.v1.ExecuteHookArgs + */ +export type ExecuteHookArgs = Message<"agent.v1.ExecuteHookArgs"> & { + /** + * @generated from field: agent.v1.ExecuteHookRequest request = 1; + */ + request?: ExecuteHookRequest | undefined; +}; + +/** + * Describes the message agent.v1.ExecuteHookArgs. + * Use `create(ExecuteHookArgsSchema)` to create a new message. + */ +export const ExecuteHookArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 582); + +/** + * @generated from message agent.v1.ExecuteHookResult + */ +export type ExecuteHookResult = Message<"agent.v1.ExecuteHookResult"> & { + /** + * @generated from field: agent.v1.ExecuteHookResponse response = 1; + */ + response?: ExecuteHookResponse | undefined; +}; + +/** + * Describes the message agent.v1.ExecuteHookResult. + * Use `create(ExecuteHookResultSchema)` to create a new message. + */ +export const ExecuteHookResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 583); + +/** + * Subset: arguments=2 (google.protobuf.Struct) decodes into unknown fields; do not reuse. + * + * @generated from message agent.v1.SmartModeRiskTarget + */ +export type SmartModeRiskTarget = Message<"agent.v1.SmartModeRiskTarget"> & { + /** + * @generated from field: string action = 1; + */ + action: string; +}; + +/** + * Describes the message agent.v1.SmartModeRiskTarget. + * Use `create(SmartModeRiskTargetSchema)` to create a new message. + */ +export const SmartModeRiskTargetSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 584); + +/** + * @generated from message agent.v1.SmartModeClassifierConversationMessage + */ +export type SmartModeClassifierConversationMessage = Message<"agent.v1.SmartModeClassifierConversationMessage"> & { + /** + * @generated from field: string role = 1; + */ + role: string; + + /** + * @generated from field: string content = 2; + */ + content: string; +}; + +/** + * Describes the message agent.v1.SmartModeClassifierConversationMessage. + * Use `create(SmartModeClassifierConversationMessageSchema)` to create a new message. + */ +export const SmartModeClassifierConversationMessageSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 585); + +/** + * @generated from message agent.v1.SmartModeClassifierArgs + */ +export type SmartModeClassifierArgs = Message<"agent.v1.SmartModeClassifierArgs"> & { + /** + * @generated from field: string tool_call_id = 1; + */ + toolCallId: string; + + /** + * @generated from field: optional string parent_conversation_id = 2; + */ + parentConversationId?: string | undefined; + + /** + * @generated from field: agent.v1.SmartModeRiskTarget target = 3; + */ + target?: SmartModeRiskTarget | undefined; + + /** + * @generated from field: repeated agent.v1.SmartModeClassifierConversationMessage conversation_context = 4; + */ + conversationContext: SmartModeClassifierConversationMessage[]; +}; + +/** + * Describes the message agent.v1.SmartModeClassifierArgs. + * Use `create(SmartModeClassifierArgsSchema)` to create a new message. + */ +export const SmartModeClassifierArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 586); + +/** + * @generated from message agent.v1.SmartModeClassifierSuccess + */ +export type SmartModeClassifierSuccess = Message<"agent.v1.SmartModeClassifierSuccess"> & { + /** + * @generated from field: agent.v1.SmartModeClassifierDecision decision = 1; + */ + decision: SmartModeClassifierDecision; + + /** + * @generated from field: optional string block_reason = 2; + */ + blockReason?: string | undefined; +}; + +/** + * Describes the message agent.v1.SmartModeClassifierSuccess. + * Use `create(SmartModeClassifierSuccessSchema)` to create a new message. + */ +export const SmartModeClassifierSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 587); + +/** + * @generated from message agent.v1.SmartModeClassifierError + */ +export type SmartModeClassifierError = Message<"agent.v1.SmartModeClassifierError"> & { + /** + * @generated from field: string error = 1; + */ + error: string; +}; + +/** + * Describes the message agent.v1.SmartModeClassifierError. + * Use `create(SmartModeClassifierErrorSchema)` to create a new message. + */ +export const SmartModeClassifierErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 588); + +/** + * @generated from message agent.v1.SmartModeClassifierResult + */ +export type SmartModeClassifierResult = Message<"agent.v1.SmartModeClassifierResult"> & { + /** + * @generated from oneof agent.v1.SmartModeClassifierResult.result + */ + result: { + /** + * @generated from field: agent.v1.SmartModeClassifierSuccess success = 1; + */ + value: SmartModeClassifierSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.SmartModeClassifierError error = 2; + */ + value: SmartModeClassifierError; + case: "error"; + } | { case: undefined; value?: undefined }; +}; + +/** + * Describes the message agent.v1.SmartModeClassifierResult. + * Use `create(SmartModeClassifierResultSchema)` to create a new message. + */ +export const SmartModeClassifierResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 589); + +/** + * @generated from message agent.v1.CanvasDiagnosticsArgs + */ +export type CanvasDiagnosticsArgs = Message<"agent.v1.CanvasDiagnosticsArgs"> & { + /** + * @generated from field: string path = 1; + */ + path: string; + + /** + * @generated from field: string tool_call_id = 2; + */ + toolCallId: string; +}; + +/** + * Describes the message agent.v1.CanvasDiagnosticsArgs. + * Use `create(CanvasDiagnosticsArgsSchema)` to create a new message. + */ +export const CanvasDiagnosticsArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 590); + +/** + * @generated from message agent.v1.CanvasDiagnosticsSuccess + */ +export type CanvasDiagnosticsSuccess = Message<"agent.v1.CanvasDiagnosticsSuccess"> & { + /** + * @generated from field: string path = 1; + */ + path: string; + + /** + * @generated from field: repeated agent.v1.Diagnostic diagnostics = 2; + */ + diagnostics: Diagnostic[]; +}; + +/** + * Describes the message agent.v1.CanvasDiagnosticsSuccess. + * Use `create(CanvasDiagnosticsSuccessSchema)` to create a new message. + */ +export const CanvasDiagnosticsSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 591); + +/** + * @generated from message agent.v1.CanvasDiagnosticsError + */ +export type CanvasDiagnosticsError = Message<"agent.v1.CanvasDiagnosticsError"> & { + /** + * @generated from field: string path = 1; + */ + path: string; + + /** + * @generated from field: string error = 2; + */ + error: string; +}; + +/** + * Describes the message agent.v1.CanvasDiagnosticsError. + * Use `create(CanvasDiagnosticsErrorSchema)` to create a new message. + */ +export const CanvasDiagnosticsErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 592); + +/** + * @generated from message agent.v1.CanvasDiagnosticsResult + */ +export type CanvasDiagnosticsResult = Message<"agent.v1.CanvasDiagnosticsResult"> & { + /** + * @generated from oneof agent.v1.CanvasDiagnosticsResult.result + */ + result: { + /** + * @generated from field: agent.v1.CanvasDiagnosticsSuccess success = 1; + */ + value: CanvasDiagnosticsSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.CanvasDiagnosticsError error = 2; + */ + value: CanvasDiagnosticsError; + case: "error"; + } | { case: undefined; value?: undefined }; +}; + +/** + * Describes the message agent.v1.CanvasDiagnosticsResult. + * Use `create(CanvasDiagnosticsResultSchema)` to create a new message. + */ +export const CanvasDiagnosticsResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 593); + +/** + * @generated from message agent.v1.ConversationSearchHit + */ +export type ConversationSearchHit = Message<"agent.v1.ConversationSearchHit"> & { + /** + * @generated from field: string conversation_id = 1; + */ + conversationId: string; + + /** + * @generated from field: string title = 2; + */ + title: string; + + /** + * @generated from field: agent.v1.ConversationSearchSource source = 3; + */ + source: ConversationSearchSource; + + /** + * @generated from field: int64 updated_at_ms = 4; + */ + updatedAtMs: bigint; + + /** + * @generated from field: optional string snippet = 5; + */ + snippet?: string | undefined; +}; + +/** + * Describes the message agent.v1.ConversationSearchHit. + * Use `create(ConversationSearchHitSchema)` to create a new message. + */ +export const ConversationSearchHitSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 594); + +/** + * @generated from message agent.v1.ConversationSearchSuccess + */ +export type ConversationSearchSuccess = Message<"agent.v1.ConversationSearchSuccess"> & { + /** + * @generated from field: repeated agent.v1.ConversationSearchHit hits = 1; + */ + hits: ConversationSearchHit[]; + + /** + * @generated from field: bool truncated = 2; + */ + truncated: boolean; + + /** + * @generated from field: bool partial = 3; + */ + partial: boolean; + + /** + * @generated from field: bool rebuilding = 4; + */ + rebuilding: boolean; +}; + +/** + * Describes the message agent.v1.ConversationSearchSuccess. + * Use `create(ConversationSearchSuccessSchema)` to create a new message. + */ +export const ConversationSearchSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 595); + +/** + * @generated from message agent.v1.ConversationSearchError + */ +export type ConversationSearchError = Message<"agent.v1.ConversationSearchError"> & { + /** + * @generated from field: string error = 1; + */ + error: string; +}; + +/** + * Describes the message agent.v1.ConversationSearchError. + * Use `create(ConversationSearchErrorSchema)` to create a new message. + */ +export const ConversationSearchErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 596); + +/** + * @generated from message agent.v1.ConversationSearchArgs + */ +export type ConversationSearchArgs = Message<"agent.v1.ConversationSearchArgs"> & { + /** + * @generated from field: string query = 1; + */ + query: string; + + /** + * @generated from field: string tool_call_id = 2; + */ + toolCallId: string; + + /** + * @generated from field: optional int32 limit = 3; + */ + limit?: number | undefined; +}; + +/** + * Describes the message agent.v1.ConversationSearchArgs. + * Use `create(ConversationSearchArgsSchema)` to create a new message. + */ +export const ConversationSearchArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 597); + +/** + * @generated from message agent.v1.ConversationSearchResult + */ +export type ConversationSearchResult = Message<"agent.v1.ConversationSearchResult"> & { + /** + * @generated from oneof agent.v1.ConversationSearchResult.result + */ + result: { + /** + * @generated from field: agent.v1.ConversationSearchSuccess success = 1; + */ + value: ConversationSearchSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.ConversationSearchError error = 2; + */ + value: ConversationSearchError; + case: "error"; + } | { case: undefined; value?: undefined }; +}; + +/** + * Describes the message agent.v1.ConversationSearchResult. + * Use `create(ConversationSearchResultSchema)` to create a new message. + */ +export const ConversationSearchResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 598); + +/** + * @generated from message agent.v1.AgentStoreConflictCursor + */ +export type AgentStoreConflictCursor = Message<"agent.v1.AgentStoreConflictCursor"> & { + /** + * @generated from field: string journal_epoch = 1; + */ + journalEpoch: string; + + /** + * @generated from field: uint64 seq = 2; + */ + seq: bigint; + + /** + * @generated from field: string last_event_id = 3; + */ + lastEventId: string; +}; + +/** + * Describes the message agent.v1.AgentStoreConflictCursor. + * Use `create(AgentStoreConflictCursorSchema)` to create a new message. + */ +export const AgentStoreConflictCursorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 599); + +/** + * @generated from message agent.v1.AgentStoreConflictEvent + */ +export type AgentStoreConflictEvent = Message<"agent.v1.AgentStoreConflictEvent"> & { + /** + * @generated from field: uint32 v = 1; + */ + v: number; + + /** + * @generated from field: string event_id = 2; + */ + eventId: string; + + /** + * @generated from field: string journal_epoch = 3; + */ + journalEpoch: string; + + /** + * @generated from field: uint64 seq = 4; + */ + seq: bigint; + + /** + * @generated from field: uint64 ts_ms = 5; + */ + tsMs: bigint; + + /** + * @generated from field: string kind = 6; + */ + kind: string; + + /** + * @generated from field: optional string store_id = 7; + */ + storeId?: string | undefined; + + /** + * @generated from field: optional string original_rel_path = 8; + */ + originalRelPath?: string | undefined; + + /** + * @generated from field: optional string conflict_rel_path = 9; + */ + conflictRelPath?: string | undefined; + + /** + * @generated from field: optional string original_abs_path = 10; + */ + originalAbsPath?: string | undefined; + + /** + * @generated from field: optional string conflict_abs_path = 11; + */ + conflictAbsPath?: string | undefined; + + /** + * @generated from field: optional uint64 preserved_bytes = 12; + */ + preservedBytes?: bigint | undefined; +}; + +/** + * Describes the message agent.v1.AgentStoreConflictEvent. + * Use `create(AgentStoreConflictEventSchema)` to create a new message. + */ +export const AgentStoreConflictEventSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 600); + +/** + * @generated from message agent.v1.AgentStoreConflictSuccess + */ +export type AgentStoreConflictSuccess = Message<"agent.v1.AgentStoreConflictSuccess"> & { + /** + * @generated from field: repeated agent.v1.AgentStoreConflictEvent events = 1; + */ + events: AgentStoreConflictEvent[]; + + /** + * @generated from field: agent.v1.AgentStoreConflictCursor next_cursor = 2; + */ + nextCursor?: AgentStoreConflictCursor | undefined; + + /** + * @generated from field: bool gap = 3; + */ + gap: boolean; +}; + +/** + * Describes the message agent.v1.AgentStoreConflictSuccess. + * Use `create(AgentStoreConflictSuccessSchema)` to create a new message. + */ +export const AgentStoreConflictSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 601); + +/** + * @generated from message agent.v1.AgentStoreConflictError + */ +export type AgentStoreConflictError = Message<"agent.v1.AgentStoreConflictError"> & { + /** + * @generated from field: string error = 1; + */ + error: string; +}; + +/** + * Describes the message agent.v1.AgentStoreConflictError. + * Use `create(AgentStoreConflictErrorSchema)` to create a new message. + */ +export const AgentStoreConflictErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 602); + +/** + * @generated from message agent.v1.AgentStoreConflictArgs + */ +export type AgentStoreConflictArgs = Message<"agent.v1.AgentStoreConflictArgs"> & { + /** + * @generated from field: optional agent.v1.AgentStoreConflictCursor cursor = 1; + */ + cursor?: AgentStoreConflictCursor | undefined; + + /** + * @generated from field: optional bool advance = 2; + */ + advance?: boolean | undefined; +}; + +/** + * Describes the message agent.v1.AgentStoreConflictArgs. + * Use `create(AgentStoreConflictArgsSchema)` to create a new message. + */ +export const AgentStoreConflictArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 603); + +/** + * @generated from message agent.v1.AgentStoreConflictResult + */ +export type AgentStoreConflictResult = Message<"agent.v1.AgentStoreConflictResult"> & { + /** + * @generated from oneof agent.v1.AgentStoreConflictResult.result + */ + result: { + /** + * @generated from field: agent.v1.AgentStoreConflictSuccess success = 1; + */ + value: AgentStoreConflictSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.AgentStoreConflictError error = 2; + */ + value: AgentStoreConflictError; + case: "error"; + } | { case: undefined; value?: undefined }; +}; + +/** + * Describes the message agent.v1.AgentStoreConflictResult. + * Use `create(AgentStoreConflictResultSchema)` to create a new message. + */ +export const AgentStoreConflictResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 604); + +/** + * @generated from message agent.v1.FileDiff_Chunk + */ +export type FileDiff_Chunk = Message<"agent.v1.FileDiff_Chunk"> & { + /** + * @generated from field: string content = 1; + */ + content: string; + + /** + * @generated from field: repeated string lines = 2; + */ + lines: string[]; + + /** + * @generated from field: int32 old_start = 3; + */ + oldStart: number; + + /** + * @generated from field: int32 old_lines = 4; + */ + oldLines: number; + + /** + * @generated from field: int32 new_start = 5; + */ + newStart: number; + + /** + * @generated from field: int32 new_lines = 6; + */ + newLines: number; +}; + +/** + * Describes the message agent.v1.FileDiff_Chunk. + * Use `create(FileDiff_ChunkSchema)` to create a new message. + */ +export const FileDiff_ChunkSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 605); + +/** + * @generated from message agent.v1.FileDiff + */ +export type FileDiff = Message<"agent.v1.FileDiff"> & { + /** + * @generated from field: int32 added = 4; + */ + added: number; + + /** + * @generated from field: int32 removed = 5; + */ + removed: number; + + /** + * @generated from field: string from = 1; + */ + from: string; + + /** + * @generated from field: string to = 2; + */ + to: string; + + /** + * @generated from field: repeated agent.v1.FileDiff_Chunk chunks = 3; + */ + chunks: FileDiff_Chunk[]; + + /** + * @generated from field: optional string before_file_contents = 6; + */ + beforeFileContents?: string | undefined; + + /** + * @generated from field: optional string after_file_contents = 7; + */ + afterFileContents?: string | undefined; + + /** + * @generated from field: optional bool is_generated = 8; + */ + isGenerated?: boolean | undefined; +}; + +/** + * Describes the message agent.v1.FileDiff. + * Use `create(FileDiffSchema)` to create a new message. + */ +export const FileDiffSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 606); + +/** + * @generated from message agent.v1.GitDiff + */ +export type GitDiff = Message<"agent.v1.GitDiff"> & { + /** + * @generated from field: repeated agent.v1.FileDiff diffs = 1; + */ + diffs: FileDiff[]; + + /** + * @generated from field: agent.v1.GitDiff_DiffType diff_type = 2; + */ + diffType: GitDiff_DiffType; +}; + +/** + * Describes the message agent.v1.GitDiff. + * Use `create(GitDiffSchema)` to create a new message. + */ +export const GitDiffSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 607); + +/** + * @generated from message agent.v1.GetDiffResponse_SubmoduleDiff + */ +export type GetDiffResponse_SubmoduleDiff = Message<"agent.v1.GetDiffResponse_SubmoduleDiff"> & { + /** + * @generated from field: string relative_path = 1; + */ + relativePath: string; + + /** + * @generated from field: agent.v1.GitDiff diff = 2; + */ + diff?: GitDiff | undefined; + + /** + * @generated from field: bool errored = 3; + */ + errored: boolean; +}; + +/** + * Describes the message agent.v1.GetDiffResponse_SubmoduleDiff. + * Use `create(GetDiffResponse_SubmoduleDiffSchema)` to create a new message. + */ +export const GetDiffResponse_SubmoduleDiffSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 608); + +/** + * @generated from message agent.v1.GetDiffRequest + */ +export type GetDiffRequest = Message<"agent.v1.GetDiffRequest"> & { + /** + * @generated from field: string cwd = 1; + */ + cwd: string; + + /** + * @generated from field: string ref = 2; + */ + ref: string; + + /** + * @generated from field: string base_ref = 3; + */ + baseRef: string; + + /** + * @generated from field: bool merge_base = 4; + */ + mergeBase: boolean; + + /** + * @generated from field: repeated string target_paths = 5; + */ + targetPaths: string[]; + + /** + * @generated from field: optional int32 unified_context_lines = 6; + */ + unifiedContextLines?: number | undefined; + + /** + * @generated from field: int32 max_untracked_files = 7; + */ + maxUntrackedFiles: number; + + /** + * @generated from field: int32 submodule_recurse_depth = 9; + */ + submoduleRecurseDepth: number; + + /** + * @generated from field: bool include_space_changes = 10; + */ + includeSpaceChanges: boolean; + + /** + * @generated from field: bool committed_only = 11; + */ + committedOnly: boolean; + + /** + * @generated from field: bool compute_patch_id = 12; + */ + computePatchId: boolean; + + /** + * @generated from field: optional bool return_head_sha = 13; + */ + returnHeadSha?: boolean | undefined; + + /** + * @generated from field: optional int32 max_response_bytes = 14; + */ + maxResponseBytes?: number | undefined; + + /** + * @generated from field: optional agent.v1.GetDiffRequest_OutputFormat output_format = 8; + */ + outputFormat?: GetDiffRequest_OutputFormat | undefined; +}; + +/** + * Describes the message agent.v1.GetDiffRequest. + * Use `create(GetDiffRequestSchema)` to create a new message. + */ +export const GetDiffRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 609); + +/** + * @generated from message agent.v1.GetDiffResponse + */ +export type GetDiffResponse = Message<"agent.v1.GetDiffResponse"> & { + /** + * @generated from field: agent.v1.GitDiff diff = 1; + */ + diff?: GitDiff | undefined; + + /** + * @generated from field: repeated agent.v1.GetDiffResponse_SubmoduleDiff submodule_diffs = 2; + */ + submoduleDiffs: GetDiffResponse_SubmoduleDiff[]; + + /** + * @generated from field: optional string patch_id = 3; + */ + patchId?: string | undefined; + + /** + * @generated from field: optional string head_sha = 4; + */ + headSha?: string | undefined; + + /** + * @generated from field: optional bool has_uncommitted_changes = 5; + */ + hasUncommittedChanges?: boolean | undefined; +}; + +/** + * Describes the message agent.v1.GetDiffResponse. + * Use `create(GetDiffResponseSchema)` to create a new message. + */ +export const GetDiffResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 610); + +/** + * @generated from message agent.v1.PiReadToolCall + */ +export type PiReadToolCall = Message<"agent.v1.PiReadToolCall"> & { + /** + * @generated from field: agent.v1.PiReadExecArgs args = 1; + */ + args?: PiReadExecArgs | undefined; + + /** + * @generated from field: agent.v1.PiReadExecResult result = 2; + */ + result?: PiReadExecResult | undefined; +}; + +/** + * Describes the message agent.v1.PiReadToolCall. + * Use `create(PiReadToolCallSchema)` to create a new message. + */ +export const PiReadToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 611); + +/** + * @generated from message agent.v1.PiBashToolCall + */ +export type PiBashToolCall = Message<"agent.v1.PiBashToolCall"> & { + /** + * @generated from field: agent.v1.PiBashExecArgs args = 1; + */ + args?: PiBashExecArgs | undefined; + + /** + * @generated from field: agent.v1.PiBashExecResult result = 2; + */ + result?: PiBashExecResult | undefined; +}; + +/** + * Describes the message agent.v1.PiBashToolCall. + * Use `create(PiBashToolCallSchema)` to create a new message. + */ +export const PiBashToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 612); + +/** + * @generated from message agent.v1.PiEditToolCall + */ +export type PiEditToolCall = Message<"agent.v1.PiEditToolCall"> & { + /** + * @generated from field: agent.v1.PiEditExecArgs args = 1; + */ + args?: PiEditExecArgs | undefined; + + /** + * @generated from field: agent.v1.PiEditExecResult result = 2; + */ + result?: PiEditExecResult | undefined; +}; + +/** + * Describes the message agent.v1.PiEditToolCall. + * Use `create(PiEditToolCallSchema)` to create a new message. + */ +export const PiEditToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 613); + +/** + * @generated from message agent.v1.PiWriteToolCall + */ +export type PiWriteToolCall = Message<"agent.v1.PiWriteToolCall"> & { + /** + * @generated from field: agent.v1.PiWriteExecArgs args = 1; + */ + args?: PiWriteExecArgs | undefined; + + /** + * @generated from field: agent.v1.PiWriteExecResult result = 2; + */ + result?: PiWriteExecResult | undefined; +}; + +/** + * Describes the message agent.v1.PiWriteToolCall. + * Use `create(PiWriteToolCallSchema)` to create a new message. + */ +export const PiWriteToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 614); + +/** + * @generated from message agent.v1.PiGrepToolCall + */ +export type PiGrepToolCall = Message<"agent.v1.PiGrepToolCall"> & { + /** + * @generated from field: agent.v1.PiGrepExecArgs args = 1; + */ + args?: PiGrepExecArgs | undefined; + + /** + * @generated from field: agent.v1.PiGrepExecResult result = 2; + */ + result?: PiGrepExecResult | undefined; +}; + +/** + * Describes the message agent.v1.PiGrepToolCall. + * Use `create(PiGrepToolCallSchema)` to create a new message. + */ +export const PiGrepToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 615); + +/** + * @generated from message agent.v1.PiFindToolCall + */ +export type PiFindToolCall = Message<"agent.v1.PiFindToolCall"> & { + /** + * @generated from field: agent.v1.PiFindExecArgs args = 1; + */ + args?: PiFindExecArgs | undefined; + + /** + * @generated from field: agent.v1.PiFindExecResult result = 2; + */ + result?: PiFindExecResult | undefined; +}; + +/** + * Describes the message agent.v1.PiFindToolCall. + * Use `create(PiFindToolCallSchema)` to create a new message. + */ +export const PiFindToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 616); + +/** + * @generated from message agent.v1.PiLsToolCall + */ +export type PiLsToolCall = Message<"agent.v1.PiLsToolCall"> & { + /** + * @generated from field: agent.v1.PiLsExecArgs args = 1; + */ + args?: PiLsExecArgs | undefined; + + /** + * @generated from field: agent.v1.PiLsExecResult result = 2; + */ + result?: PiLsExecResult | undefined; +}; + +/** + * Describes the message agent.v1.PiLsToolCall. + * Use `create(PiLsToolCallSchema)` to create a new message. + */ +export const PiLsToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 617); + +/** + * @generated from message agent.v1.SearchConversationsToolCall + */ +export type SearchConversationsToolCall = Message<"agent.v1.SearchConversationsToolCall"> & { + /** + * @generated from field: agent.v1.ConversationSearchArgs args = 1; + */ + args?: ConversationSearchArgs | undefined; + + /** + * @generated from field: agent.v1.ConversationSearchResult result = 2; + */ + result?: ConversationSearchResult | undefined; +}; + +/** + * Describes the message agent.v1.SearchConversationsToolCall. + * Use `create(SearchConversationsToolCallSchema)` to create a new message. + */ +export const SearchConversationsToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 618); + +/** + * @generated from message agent.v1.ConnectScmGithubRepository + */ +export type ConnectScmGithubRepository = Message<"agent.v1.ConnectScmGithubRepository"> & { + /** + * @generated from field: string owner = 1; + */ + owner: string; + + /** + * @generated from field: string repo = 2; + */ + repo: string; +}; + +/** + * Describes the message agent.v1.ConnectScmGithubRepository. + * Use `create(ConnectScmGithubRepositorySchema)` to create a new message. + */ +export const ConnectScmGithubRepositorySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 619); + +/** + * @generated from message agent.v1.ConnectScmGithub + */ +export type ConnectScmGithub = Message<"agent.v1.ConnectScmGithub"> & { + /** + * @generated from field: agent.v1.ConnectScmGithubRepository repository = 1; + */ + repository?: ConnectScmGithubRepository | undefined; + + /** + * @generated from field: optional string ghe_application = 2; + */ + gheApplication?: string | undefined; +}; + +/** + * Describes the message agent.v1.ConnectScmGithub. + * Use `create(ConnectScmGithubSchema)` to create a new message. + */ +export const ConnectScmGithubSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 620); + +/** + * @generated from message agent.v1.ConnectScmArgs + */ +export type ConnectScmArgs = Message<"agent.v1.ConnectScmArgs"> & { + /** + * @generated from field: string tool_call_id = 1; + */ + toolCallId: string; + + /** + * @generated from oneof agent.v1.ConnectScmArgs.target + */ + target: { + /** + * @generated from field: agent.v1.ConnectScmGithub github = 2; + */ + value: ConnectScmGithub; + case: "github"; + } | { case: undefined; value?: undefined }; +}; + +/** + * Describes the message agent.v1.ConnectScmArgs. + * Use `create(ConnectScmArgsSchema)` to create a new message. + */ +export const ConnectScmArgsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 621); + +/** + * @generated from message agent.v1.ConnectScmSuccess + */ +export type ConnectScmSuccess = Message<"agent.v1.ConnectScmSuccess"> & { +}; + +/** + * Describes the message agent.v1.ConnectScmSuccess. + * Use `create(ConnectScmSuccessSchema)` to create a new message. + */ +export const ConnectScmSuccessSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 622); + +/** + * @generated from message agent.v1.ConnectScmError + */ +export type ConnectScmError = Message<"agent.v1.ConnectScmError"> & { + /** + * @generated from field: string error = 1; + */ + error: string; +}; + +/** + * Describes the message agent.v1.ConnectScmError. + * Use `create(ConnectScmErrorSchema)` to create a new message. + */ +export const ConnectScmErrorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 623); + +/** + * @generated from message agent.v1.ConnectScmRejected + */ +export type ConnectScmRejected = Message<"agent.v1.ConnectScmRejected"> & { + /** + * @generated from field: string reason = 1; + */ + reason: string; +}; + +/** + * Describes the message agent.v1.ConnectScmRejected. + * Use `create(ConnectScmRejectedSchema)` to create a new message. + */ +export const ConnectScmRejectedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 624); + +/** + * @generated from message agent.v1.ConnectScmResult + */ +export type ConnectScmResult = Message<"agent.v1.ConnectScmResult"> & { + /** + * @generated from oneof agent.v1.ConnectScmResult.result + */ + result: { + /** + * @generated from field: agent.v1.ConnectScmSuccess success = 1; + */ + value: ConnectScmSuccess; + case: "success"; + } | { + /** + * @generated from field: agent.v1.ConnectScmError error = 2; + */ + value: ConnectScmError; + case: "error"; + } | { + /** + * @generated from field: agent.v1.ConnectScmRejected rejected = 3; + */ + value: ConnectScmRejected; + case: "rejected"; + } | { case: undefined; value?: undefined }; +}; + +/** + * Describes the message agent.v1.ConnectScmResult. + * Use `create(ConnectScmResultSchema)` to create a new message. + */ +export const ConnectScmResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 625); + +/** + * @generated from message agent.v1.ConnectScmToolCall + */ +export type ConnectScmToolCall = Message<"agent.v1.ConnectScmToolCall"> & { + /** + * @generated from field: agent.v1.ConnectScmArgs args = 1; + */ + args?: ConnectScmArgs | undefined; + + /** + * @generated from field: agent.v1.ConnectScmResult result = 2; + */ + result?: ConnectScmResult | undefined; +}; + +/** + * Describes the message agent.v1.ConnectScmToolCall. + * Use `create(ConnectScmToolCallSchema)` to create a new message. + */ +export const ConnectScmToolCallSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agent, 626); /** * @generated from enum agent.v1.AppliedAgentChange_ChangeType */ export enum AppliedAgentChange_ChangeType { - /** - * @generated from enum value: CHANGE_TYPE_UNSPECIFIED = 0; - */ - CHANGE_TYPE_UNSPECIFIED = 0, + /** + * @generated from enum value: CHANGE_TYPE_UNSPECIFIED = 0; + */ + CHANGE_TYPE_UNSPECIFIED = 0, - /** - * @generated from enum value: CHANGE_TYPE_CREATED = 1; - */ - CHANGE_TYPE_CREATED = 1, + /** + * @generated from enum value: CHANGE_TYPE_CREATED = 1; + */ + CHANGE_TYPE_CREATED = 1, - /** - * @generated from enum value: CHANGE_TYPE_MODIFIED = 2; - */ - CHANGE_TYPE_MODIFIED = 2, + /** + * @generated from enum value: CHANGE_TYPE_MODIFIED = 2; + */ + CHANGE_TYPE_MODIFIED = 2, - /** - * @generated from enum value: CHANGE_TYPE_DELETED = 3; - */ - CHANGE_TYPE_DELETED = 3, + /** + * @generated from enum value: CHANGE_TYPE_DELETED = 3; + */ + CHANGE_TYPE_DELETED = 3, } /** * Describes the enum agent.v1.AppliedAgentChange_ChangeType. */ -export const AppliedAgentChange_ChangeTypeSchema: GenEnum = - /*@__PURE__*/ - enumDesc(file_agent, 0); +export const AppliedAgentChange_ChangeTypeSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 0); /** * @generated from enum agent.v1.MouseButton */ export enum MouseButton { - /** - * @generated from enum value: MOUSE_BUTTON_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, + /** + * @generated from enum value: MOUSE_BUTTON_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, - /** - * @generated from enum value: MOUSE_BUTTON_LEFT = 1; - */ - LEFT = 1, + /** + * @generated from enum value: MOUSE_BUTTON_LEFT = 1; + */ + LEFT = 1, - /** - * @generated from enum value: MOUSE_BUTTON_RIGHT = 2; - */ - RIGHT = 2, + /** + * @generated from enum value: MOUSE_BUTTON_RIGHT = 2; + */ + RIGHT = 2, - /** - * @generated from enum value: MOUSE_BUTTON_MIDDLE = 3; - */ - MIDDLE = 3, + /** + * @generated from enum value: MOUSE_BUTTON_MIDDLE = 3; + */ + MIDDLE = 3, - /** - * @generated from enum value: MOUSE_BUTTON_BACK = 4; - */ - BACK = 4, + /** + * @generated from enum value: MOUSE_BUTTON_BACK = 4; + */ + BACK = 4, - /** - * @generated from enum value: MOUSE_BUTTON_FORWARD = 5; - */ - FORWARD = 5, + /** + * @generated from enum value: MOUSE_BUTTON_FORWARD = 5; + */ + FORWARD = 5, } /** * Describes the enum agent.v1.MouseButton. */ -export const MouseButtonSchema: GenEnum = /*@__PURE__*/ enumDesc(file_agent, 1); +export const MouseButtonSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 1); /** * @generated from enum agent.v1.ScrollDirection */ export enum ScrollDirection { - /** - * @generated from enum value: SCROLL_DIRECTION_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, + /** + * @generated from enum value: SCROLL_DIRECTION_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, - /** - * @generated from enum value: SCROLL_DIRECTION_UP = 1; - */ - UP = 1, + /** + * @generated from enum value: SCROLL_DIRECTION_UP = 1; + */ + UP = 1, - /** - * @generated from enum value: SCROLL_DIRECTION_DOWN = 2; - */ - DOWN = 2, + /** + * @generated from enum value: SCROLL_DIRECTION_DOWN = 2; + */ + DOWN = 2, - /** - * @generated from enum value: SCROLL_DIRECTION_LEFT = 3; - */ - LEFT = 3, + /** + * @generated from enum value: SCROLL_DIRECTION_LEFT = 3; + */ + LEFT = 3, - /** - * @generated from enum value: SCROLL_DIRECTION_RIGHT = 4; - */ - RIGHT = 4, + /** + * @generated from enum value: SCROLL_DIRECTION_RIGHT = 4; + */ + RIGHT = 4, } /** * Describes the enum agent.v1.ScrollDirection. */ -export const ScrollDirectionSchema: GenEnum = /*@__PURE__*/ enumDesc(file_agent, 2); +export const ScrollDirectionSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 2); /** * @generated from enum agent.v1.CursorRuleSource */ export enum CursorRuleSource { - /** - * @generated from enum value: CURSOR_RULE_SOURCE_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, + /** + * @generated from enum value: CURSOR_RULE_SOURCE_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, - /** - * @generated from enum value: CURSOR_RULE_SOURCE_TEAM = 1; - */ - TEAM = 1, + /** + * @generated from enum value: CURSOR_RULE_SOURCE_TEAM = 1; + */ + TEAM = 1, - /** - * @generated from enum value: CURSOR_RULE_SOURCE_USER = 2; - */ - USER = 2, + /** + * @generated from enum value: CURSOR_RULE_SOURCE_USER = 2; + */ + USER = 2, } /** * Describes the enum agent.v1.CursorRuleSource. */ -export const CursorRuleSourceSchema: GenEnum = /*@__PURE__*/ enumDesc(file_agent, 3); +export const CursorRuleSourceSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 3); /** * @generated from enum agent.v1.DiagnosticSeverity */ export enum DiagnosticSeverity { - /** - * @generated from enum value: DIAGNOSTIC_SEVERITY_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, + /** + * @generated from enum value: DIAGNOSTIC_SEVERITY_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, - /** - * @generated from enum value: DIAGNOSTIC_SEVERITY_ERROR = 1; - */ - ERROR = 1, + /** + * @generated from enum value: DIAGNOSTIC_SEVERITY_ERROR = 1; + */ + ERROR = 1, - /** - * @generated from enum value: DIAGNOSTIC_SEVERITY_WARNING = 2; - */ - WARNING = 2, + /** + * @generated from enum value: DIAGNOSTIC_SEVERITY_WARNING = 2; + */ + WARNING = 2, - /** - * @generated from enum value: DIAGNOSTIC_SEVERITY_INFORMATION = 3; - */ - INFORMATION = 3, + /** + * @generated from enum value: DIAGNOSTIC_SEVERITY_INFORMATION = 3; + */ + INFORMATION = 3, - /** - * @generated from enum value: DIAGNOSTIC_SEVERITY_HINT = 4; - */ - HINT = 4, + /** + * @generated from enum value: DIAGNOSTIC_SEVERITY_HINT = 4; + */ + HINT = 4, } /** * Describes the enum agent.v1.DiagnosticSeverity. */ -export const DiagnosticSeveritySchema: GenEnum = /*@__PURE__*/ enumDesc(file_agent, 4); +export const DiagnosticSeveritySchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 4); /** * @generated from enum agent.v1.RecordingMode */ export enum RecordingMode { - /** - * @generated from enum value: RECORDING_MODE_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, + /** + * @generated from enum value: RECORDING_MODE_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, - /** - * @generated from enum value: RECORDING_MODE_START_RECORDING = 1; - */ - START_RECORDING = 1, + /** + * @generated from enum value: RECORDING_MODE_START_RECORDING = 1; + */ + START_RECORDING = 1, - /** - * @generated from enum value: RECORDING_MODE_SAVE_RECORDING = 2; - */ - SAVE_RECORDING = 2, + /** + * @generated from enum value: RECORDING_MODE_SAVE_RECORDING = 2; + */ + SAVE_RECORDING = 2, - /** - * @generated from enum value: RECORDING_MODE_DISCARD_RECORDING = 3; - */ - DISCARD_RECORDING = 3, + /** + * @generated from enum value: RECORDING_MODE_DISCARD_RECORDING = 3; + */ + DISCARD_RECORDING = 3, } /** * Describes the enum agent.v1.RecordingMode. */ -export const RecordingModeSchema: GenEnum = /*@__PURE__*/ enumDesc(file_agent, 5); +export const RecordingModeSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 5); /** * @generated from enum agent.v1.RequestedFilePathRejectedReason */ export enum RequestedFilePathRejectedReason { - /** - * @generated from enum value: REQUESTED_FILE_PATH_REJECTED_REASON_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, + /** + * @generated from enum value: REQUESTED_FILE_PATH_REJECTED_REASON_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, - /** - * @generated from enum value: REQUESTED_FILE_PATH_REJECTED_REASON_SLASHES_NOT_ALLOWED = 1; - */ - SLASHES_NOT_ALLOWED = 1, + /** + * @generated from enum value: REQUESTED_FILE_PATH_REJECTED_REASON_SLASHES_NOT_ALLOWED = 1; + */ + SLASHES_NOT_ALLOWED = 1, } /** * Describes the enum agent.v1.RequestedFilePathRejectedReason. */ -export const RequestedFilePathRejectedReasonSchema: GenEnum = - /*@__PURE__*/ - enumDesc(file_agent, 6); +export const RequestedFilePathRejectedReasonSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 6); /** * @generated from enum agent.v1.PackageType */ export enum PackageType { - /** - * @generated from enum value: PACKAGE_TYPE_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, + /** + * @generated from enum value: PACKAGE_TYPE_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, - /** - * @generated from enum value: PACKAGE_TYPE_CURSOR_PROJECT = 1; - */ - CURSOR_PROJECT = 1, + /** + * @generated from enum value: PACKAGE_TYPE_CURSOR_PROJECT = 1; + */ + CURSOR_PROJECT = 1, - /** - * @generated from enum value: PACKAGE_TYPE_CURSOR_PERSONAL = 2; - */ - CURSOR_PERSONAL = 2, + /** + * @generated from enum value: PACKAGE_TYPE_CURSOR_PERSONAL = 2; + */ + CURSOR_PERSONAL = 2, - /** - * @generated from enum value: PACKAGE_TYPE_CLAUDE_SKILL = 3; - */ - CLAUDE_SKILL = 3, + /** + * @generated from enum value: PACKAGE_TYPE_CLAUDE_SKILL = 3; + */ + CLAUDE_SKILL = 3, - /** - * @generated from enum value: PACKAGE_TYPE_CLAUDE_PLUGIN = 4; - */ - CLAUDE_PLUGIN = 4, + /** + * @generated from enum value: PACKAGE_TYPE_CLAUDE_PLUGIN = 4; + */ + CLAUDE_PLUGIN = 4, } /** * Describes the enum agent.v1.PackageType. */ -export const PackageTypeSchema: GenEnum = /*@__PURE__*/ enumDesc(file_agent, 7); +export const PackageTypeSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 7); /** * @generated from enum agent.v1.SandboxPolicy_Type */ export enum SandboxPolicy_Type { - /** - * @generated from enum value: TYPE_UNSPECIFIED = 0; - */ - TYPE_UNSPECIFIED = 0, + /** + * @generated from enum value: TYPE_UNSPECIFIED = 0; + */ + TYPE_UNSPECIFIED = 0, - /** - * @generated from enum value: TYPE_INSECURE_NONE = 1; - */ - TYPE_INSECURE_NONE = 1, + /** + * @generated from enum value: TYPE_INSECURE_NONE = 1; + */ + TYPE_INSECURE_NONE = 1, - /** - * @generated from enum value: TYPE_WORKSPACE_READWRITE = 2; - */ - TYPE_WORKSPACE_READWRITE = 2, + /** + * @generated from enum value: TYPE_WORKSPACE_READWRITE = 2; + */ + TYPE_WORKSPACE_READWRITE = 2, - /** - * @generated from enum value: TYPE_WORKSPACE_READONLY = 3; - */ - TYPE_WORKSPACE_READONLY = 3, + /** + * @generated from enum value: TYPE_WORKSPACE_READONLY = 3; + */ + TYPE_WORKSPACE_READONLY = 3, } /** * Describes the enum agent.v1.SandboxPolicy_Type. */ -export const SandboxPolicy_TypeSchema: GenEnum = /*@__PURE__*/ enumDesc(file_agent, 8); +export const SandboxPolicy_TypeSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 8); /** * @generated from enum agent.v1.TimeoutBehavior */ export enum TimeoutBehavior { - /** - * @generated from enum value: TIMEOUT_BEHAVIOR_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, + /** + * @generated from enum value: TIMEOUT_BEHAVIOR_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, - /** - * @generated from enum value: TIMEOUT_BEHAVIOR_CANCEL = 1; - */ - CANCEL = 1, + /** + * @generated from enum value: TIMEOUT_BEHAVIOR_CANCEL = 1; + */ + CANCEL = 1, - /** - * @generated from enum value: TIMEOUT_BEHAVIOR_BACKGROUND = 2; - */ - BACKGROUND = 2, + /** + * @generated from enum value: TIMEOUT_BEHAVIOR_BACKGROUND = 2; + */ + BACKGROUND = 2, } /** * Describes the enum agent.v1.TimeoutBehavior. */ -export const TimeoutBehaviorSchema: GenEnum = /*@__PURE__*/ enumDesc(file_agent, 9); +export const TimeoutBehaviorSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 9); /** * @generated from enum agent.v1.ShellAbortReason */ export enum ShellAbortReason { - /** - * @generated from enum value: SHELL_ABORT_REASON_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, + /** + * @generated from enum value: SHELL_ABORT_REASON_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, - /** - * @generated from enum value: SHELL_ABORT_REASON_USER_ABORT = 1; - */ - USER_ABORT = 1, + /** + * @generated from enum value: SHELL_ABORT_REASON_USER_ABORT = 1; + */ + USER_ABORT = 1, - /** - * @generated from enum value: SHELL_ABORT_REASON_TIMEOUT = 2; - */ - TIMEOUT = 2, + /** + * @generated from enum value: SHELL_ABORT_REASON_TIMEOUT = 2; + */ + TIMEOUT = 2, } /** * Describes the enum agent.v1.ShellAbortReason. */ -export const ShellAbortReasonSchema: GenEnum = /*@__PURE__*/ enumDesc(file_agent, 10); +export const ShellAbortReasonSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 10); /** * @generated from enum agent.v1.CustomSubagentPermissionMode */ export enum CustomSubagentPermissionMode { - /** - * @generated from enum value: CUSTOM_SUBAGENT_PERMISSION_MODE_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, + /** + * @generated from enum value: CUSTOM_SUBAGENT_PERMISSION_MODE_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, - /** - * @generated from enum value: CUSTOM_SUBAGENT_PERMISSION_MODE_DEFAULT = 1; - */ - DEFAULT = 1, + /** + * @generated from enum value: CUSTOM_SUBAGENT_PERMISSION_MODE_DEFAULT = 1; + */ + DEFAULT = 1, - /** - * @generated from enum value: CUSTOM_SUBAGENT_PERMISSION_MODE_READONLY = 2; - */ - READONLY = 2, + /** + * @generated from enum value: CUSTOM_SUBAGENT_PERMISSION_MODE_READONLY = 2; + */ + READONLY = 2, } /** * Describes the enum agent.v1.CustomSubagentPermissionMode. */ -export const CustomSubagentPermissionModeSchema: GenEnum = - /*@__PURE__*/ - enumDesc(file_agent, 11); +export const CustomSubagentPermissionModeSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 11); /** * @generated from enum agent.v1.TodoStatus */ export enum TodoStatus { - /** - * @generated from enum value: TODO_STATUS_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, + /** + * @generated from enum value: TODO_STATUS_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, - /** - * @generated from enum value: TODO_STATUS_PENDING = 1; - */ - PENDING = 1, + /** + * @generated from enum value: TODO_STATUS_PENDING = 1; + */ + PENDING = 1, - /** - * @generated from enum value: TODO_STATUS_IN_PROGRESS = 2; - */ - IN_PROGRESS = 2, + /** + * @generated from enum value: TODO_STATUS_IN_PROGRESS = 2; + */ + IN_PROGRESS = 2, - /** - * @generated from enum value: TODO_STATUS_COMPLETED = 3; - */ - COMPLETED = 3, + /** + * @generated from enum value: TODO_STATUS_COMPLETED = 3; + */ + COMPLETED = 3, - /** - * @generated from enum value: TODO_STATUS_CANCELLED = 4; - */ - CANCELLED = 4, + /** + * @generated from enum value: TODO_STATUS_CANCELLED = 4; + */ + CANCELLED = 4, } /** * Describes the enum agent.v1.TodoStatus. */ -export const TodoStatusSchema: GenEnum = /*@__PURE__*/ enumDesc(file_agent, 12); +export const TodoStatusSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 12); /** * @generated from enum agent.v1.ClientOS */ export enum ClientOS { - /** - * @generated from enum value: CLIENT_OS_UNSPECIFIED = 0; - */ - CLIENT_OS_UNSPECIFIED = 0, + /** + * @generated from enum value: CLIENT_OS_UNSPECIFIED = 0; + */ + CLIENT_OS_UNSPECIFIED = 0, - /** - * @generated from enum value: CLIENT_OS_WINDOWS = 1; - */ - CLIENT_OS_WINDOWS = 1, + /** + * @generated from enum value: CLIENT_OS_WINDOWS = 1; + */ + CLIENT_OS_WINDOWS = 1, - /** - * @generated from enum value: CLIENT_OS_MACOS = 2; - */ - CLIENT_OS_MACOS = 2, + /** + * @generated from enum value: CLIENT_OS_MACOS = 2; + */ + CLIENT_OS_MACOS = 2, - /** - * @generated from enum value: CLIENT_OS_LINUX = 3; - */ - CLIENT_OS_LINUX = 3, + /** + * @generated from enum value: CLIENT_OS_LINUX = 3; + */ + CLIENT_OS_LINUX = 3, } /** * Describes the enum agent.v1.ClientOS. */ -export const ClientOSSchema: GenEnum = /*@__PURE__*/ enumDesc(file_agent, 13); +export const ClientOSSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 13); /** * @generated from enum agent.v1.ArtifactUploadDispatchStatus */ export enum ArtifactUploadDispatchStatus { - /** - * @generated from enum value: ARTIFACT_UPLOAD_DISPATCH_STATUS_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, + /** + * @generated from enum value: ARTIFACT_UPLOAD_DISPATCH_STATUS_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, - /** - * @generated from enum value: ARTIFACT_UPLOAD_DISPATCH_STATUS_ACCEPTED = 1; - */ - ACCEPTED = 1, + /** + * @generated from enum value: ARTIFACT_UPLOAD_DISPATCH_STATUS_ACCEPTED = 1; + */ + ACCEPTED = 1, - /** - * @generated from enum value: ARTIFACT_UPLOAD_DISPATCH_STATUS_REJECTED = 2; - */ - REJECTED = 2, + /** + * @generated from enum value: ARTIFACT_UPLOAD_DISPATCH_STATUS_REJECTED = 2; + */ + REJECTED = 2, - /** - * @generated from enum value: ARTIFACT_UPLOAD_DISPATCH_STATUS_SKIPPED_ALREADY_IN_PROGRESS = 3; - */ - SKIPPED_ALREADY_IN_PROGRESS = 3, + /** + * @generated from enum value: ARTIFACT_UPLOAD_DISPATCH_STATUS_SKIPPED_ALREADY_IN_PROGRESS = 3; + */ + SKIPPED_ALREADY_IN_PROGRESS = 3, } /** * Describes the enum agent.v1.ArtifactUploadDispatchStatus. */ -export const ArtifactUploadDispatchStatusSchema: GenEnum = - /*@__PURE__*/ - enumDesc(file_agent, 14); +export const ArtifactUploadDispatchStatusSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 14); /** * @generated from enum agent.v1.Frame_Kind */ export enum Frame_Kind { - /** - * @generated from enum value: KIND_UNSPECIFIED = 0; - */ - KIND_UNSPECIFIED = 0, + /** + * @generated from enum value: KIND_UNSPECIFIED = 0; + */ + KIND_UNSPECIFIED = 0, - /** - * @generated from enum value: KIND_REQUEST = 1; - */ - KIND_REQUEST = 1, + /** + * @generated from enum value: KIND_REQUEST = 1; + */ + KIND_REQUEST = 1, - /** - * @generated from enum value: KIND_RESPONSE = 2; - */ - KIND_RESPONSE = 2, + /** + * @generated from enum value: KIND_RESPONSE = 2; + */ + KIND_RESPONSE = 2, - /** - * @generated from enum value: KIND_ERROR = 3; - */ - KIND_ERROR = 3, + /** + * @generated from enum value: KIND_ERROR = 3; + */ + KIND_ERROR = 3, } /** * Describes the enum agent.v1.Frame_Kind. */ -export const Frame_KindSchema: GenEnum = /*@__PURE__*/ enumDesc(file_agent, 15); +export const Frame_KindSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 15); /** * @generated from enum agent.v1.BugbotDeeplinkEventKind */ export enum BugbotDeeplinkEventKind { - /** - * @generated from enum value: BUGBOT_DEEPLINK_EVENT_KIND_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, + /** + * @generated from enum value: BUGBOT_DEEPLINK_EVENT_KIND_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, - /** - * @generated from enum value: BUGBOT_DEEPLINK_EVENT_KIND_CLICKED = 1; - */ - CLICKED = 1, + /** + * @generated from enum value: BUGBOT_DEEPLINK_EVENT_KIND_CLICKED = 1; + */ + CLICKED = 1, - /** - * @generated from enum value: BUGBOT_DEEPLINK_EVENT_KIND_HANDLED_DIALOG_SHOWN = 2; - */ - HANDLED_DIALOG_SHOWN = 2, + /** + * @generated from enum value: BUGBOT_DEEPLINK_EVENT_KIND_HANDLED_DIALOG_SHOWN = 2; + */ + HANDLED_DIALOG_SHOWN = 2, - /** - * @generated from enum value: BUGBOT_DEEPLINK_EVENT_KIND_HANDLED_CHAT_CREATED = 3; - */ - HANDLED_CHAT_CREATED = 3, + /** + * @generated from enum value: BUGBOT_DEEPLINK_EVENT_KIND_HANDLED_CHAT_CREATED = 3; + */ + HANDLED_CHAT_CREATED = 3, - /** - * @generated from enum value: BUGBOT_DEEPLINK_EVENT_KIND_ERROR = 4; - */ - ERROR = 4, + /** + * @generated from enum value: BUGBOT_DEEPLINK_EVENT_KIND_ERROR = 4; + */ + ERROR = 4, - /** - * @generated from enum value: BUGBOT_DEEPLINK_EVENT_KIND_HANDLED_FIX_IN_WEB = 5; - */ - HANDLED_FIX_IN_WEB = 5, + /** + * @generated from enum value: BUGBOT_DEEPLINK_EVENT_KIND_HANDLED_FIX_IN_WEB = 5; + */ + HANDLED_FIX_IN_WEB = 5, } /** * Describes the enum agent.v1.BugbotDeeplinkEventKind. */ -export const BugbotDeeplinkEventKindSchema: GenEnum = /*@__PURE__*/ enumDesc(file_agent, 16); +export const BugbotDeeplinkEventKindSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 16); + +/** + * @generated from enum agent.v1.CommandClassifierResult_SuggestedSandboxMode + */ +export enum CommandClassifierResult_SuggestedSandboxMode { + /** + * @generated from enum value: SUGGESTED_SANDBOX_MODE_UNSPECIFIED = 0; + */ + SUGGESTED_SANDBOX_MODE_UNSPECIFIED = 0, + + /** + * @generated from enum value: SUGGESTED_SANDBOX_MODE_SANDBOX = 1; + */ + SUGGESTED_SANDBOX_MODE_SANDBOX = 1, + + /** + * @generated from enum value: SUGGESTED_SANDBOX_MODE_NO_SANDBOX = 2; + */ + SUGGESTED_SANDBOX_MODE_NO_SANDBOX = 2, + + /** + * @generated from enum value: SUGGESTED_SANDBOX_MODE_UNDETERMINED = 3; + */ + SUGGESTED_SANDBOX_MODE_UNDETERMINED = 3, +} + +/** + * Describes the enum agent.v1.CommandClassifierResult_SuggestedSandboxMode. + */ +export const CommandClassifierResult_SuggestedSandboxModeSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 17); + +/** + * @generated from enum agent.v1.ShellHookApprovalRequirement_Kind + */ +export enum ShellHookApprovalRequirement_Kind { + /** + * @generated from enum value: SHELL_HOOK_APPROVAL_REQUIREMENT_KIND_UNSPECIFIED = 0; + */ + SHELL_HOOK_APPROVAL_REQUIREMENT_KIND_UNSPECIFIED = 0, + + /** + * @generated from enum value: SHELL_HOOK_APPROVAL_REQUIREMENT_KIND_FORCE_PROMPT = 1; + */ + SHELL_HOOK_APPROVAL_REQUIREMENT_KIND_FORCE_PROMPT = 1, +} + +/** + * Describes the enum agent.v1.ShellHookApprovalRequirement_Kind. + */ +export const ShellHookApprovalRequirement_KindSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 18); + +/** + * @generated from enum agent.v1.ShellBackgroundReason + */ +export enum ShellBackgroundReason { + /** + * @generated from enum value: SHELL_BACKGROUND_REASON_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: SHELL_BACKGROUND_REASON_TIMEOUT = 1; + */ + TIMEOUT = 1, + + /** + * @generated from enum value: SHELL_BACKGROUND_REASON_USER_REQUEST = 2; + */ + USER_REQUEST = 2, +} + +/** + * Describes the enum agent.v1.ShellBackgroundReason. + */ +export const ShellBackgroundReasonSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 19); + +/** + * @generated from enum agent.v1.ForceBackgroundShellStatus + */ +export enum ForceBackgroundShellStatus { + /** + * @generated from enum value: FORCE_BACKGROUND_SHELL_STATUS_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: FORCE_BACKGROUND_SHELL_STATUS_ACCEPTED = 1; + */ + ACCEPTED = 1, + + /** + * @generated from enum value: FORCE_BACKGROUND_SHELL_STATUS_NOT_FOUND = 2; + */ + NOT_FOUND = 2, +} + +/** + * Describes the enum agent.v1.ForceBackgroundShellStatus. + */ +export const ForceBackgroundShellStatusSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 20); + +/** + * @generated from enum agent.v1.SubagentBackgroundReason + */ +export enum SubagentBackgroundReason { + /** + * @generated from enum value: SUBAGENT_BACKGROUND_REASON_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: SUBAGENT_BACKGROUND_REASON_AGENT_REQUEST = 1; + */ + AGENT_REQUEST = 1, + + /** + * @generated from enum value: SUBAGENT_BACKGROUND_REASON_USER_REQUEST = 2; + */ + USER_REQUEST = 2, + + /** + * @generated from enum value: SUBAGENT_BACKGROUND_REASON_QUEUED_FOLLOW_UP = 3; + */ + QUEUED_FOLLOW_UP = 3, +} + +/** + * Describes the enum agent.v1.SubagentBackgroundReason. + */ +export const SubagentBackgroundReasonSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 21); + +/** + * @generated from enum agent.v1.ForceBackgroundSubagentStatus + */ +export enum ForceBackgroundSubagentStatus { + /** + * @generated from enum value: FORCE_BACKGROUND_SUBAGENT_STATUS_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: FORCE_BACKGROUND_SUBAGENT_STATUS_ACCEPTED = 1; + */ + ACCEPTED = 1, + + /** + * @generated from enum value: FORCE_BACKGROUND_SUBAGENT_STATUS_NOT_FOUND = 2; + */ + NOT_FOUND = 2, +} + +/** + * Describes the enum agent.v1.ForceBackgroundSubagentStatus. + */ +export const ForceBackgroundSubagentStatusSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 22); + +/** + * @generated from enum agent.v1.SmartModeClassifierDecision + */ +export enum SmartModeClassifierDecision { + /** + * @generated from enum value: SMART_MODE_CLASSIFIER_DECISION_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: SMART_MODE_CLASSIFIER_DECISION_ALLOW = 1; + */ + ALLOW = 1, + + /** + * @generated from enum value: SMART_MODE_CLASSIFIER_DECISION_BLOCK = 2; + */ + BLOCK = 2, +} + +/** + * Describes the enum agent.v1.SmartModeClassifierDecision. + */ +export const SmartModeClassifierDecisionSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 23); + +/** + * @generated from enum agent.v1.ConversationSearchSource + */ +export enum ConversationSearchSource { + /** + * @generated from enum value: CONVERSATION_SEARCH_SOURCE_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: CONVERSATION_SEARCH_SOURCE_LOCAL = 1; + */ + LOCAL = 1, + + /** + * @generated from enum value: CONVERSATION_SEARCH_SOURCE_CLOUD_CACHE = 2; + */ + CLOUD_CACHE = 2, +} + +/** + * Describes the enum agent.v1.ConversationSearchSource. + */ +export const ConversationSearchSourceSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 24); + +/** + * @generated from enum agent.v1.GetDiffRequest_OutputFormat + */ +export enum GetDiffRequest_OutputFormat { + /** + * @generated from enum value: OUTPUT_FORMAT_UNSPECIFIED = 0; + */ + OUTPUT_FORMAT_UNSPECIFIED = 0, + + /** + * @generated from enum value: OUTPUT_FORMAT_NAME_STATUS = 1; + */ + OUTPUT_FORMAT_NAME_STATUS = 1, + + /** + * @generated from enum value: OUTPUT_FORMAT_NAME_STATUS_AND_NUMSTAT = 2; + */ + OUTPUT_FORMAT_NAME_STATUS_AND_NUMSTAT = 2, + + /** + * @generated from enum value: OUTPUT_FORMAT_FILE_DIFFS = 3; + */ + OUTPUT_FORMAT_FILE_DIFFS = 3, + + /** + * @generated from enum value: OUTPUT_FORMAT_DIFFS_WITH_BEFORE_AND_AFTER = 4; + */ + OUTPUT_FORMAT_DIFFS_WITH_BEFORE_AND_AFTER = 4, +} + +/** + * Describes the enum agent.v1.GetDiffRequest_OutputFormat. + */ +export const GetDiffRequest_OutputFormatSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 25); + +/** + * @generated from enum agent.v1.GitDiff_DiffType + */ +export enum GitDiff_DiffType { + /** + * @generated from enum value: DIFF_TYPE_UNSPECIFIED = 0; + */ + DIFF_TYPE_UNSPECIFIED = 0, + + /** + * @generated from enum value: DIFF_TYPE_DIFF_TO_HEAD = 1; + */ + DIFF_TYPE_DIFF_TO_HEAD = 1, + + /** + * @generated from enum value: DIFF_TYPE_DIFF_FROM_BRANCH_TO_MAIN = 2; + */ + DIFF_TYPE_DIFF_FROM_BRANCH_TO_MAIN = 2, +} + +/** + * Describes the enum agent.v1.GitDiff_DiffType. + */ +export const GitDiff_DiffTypeSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_agent, 26); /** * Agent Service with bidirectional streaming @@ -15063,187 +19295,192 @@ export const BugbotDeeplinkEventKindSchema: GenEnum = / * @generated from service agent.v1.AgentService */ export const AgentService: GenService<{ - /** - * @generated from rpc agent.v1.AgentService.Run - */ - run: { - methodKind: "unary"; - input: typeof AgentClientMessageSchema; - output: typeof AgentServerMessageSchema; - }; - /** - * @generated from rpc agent.v1.AgentService.RunSSE - */ - runSSE: { - methodKind: "unary"; - input: typeof BidiRequestIdSchema; - output: typeof AgentServerMessageSchema; - }; - /** - * Generate a very short, succinct agent name from the provided user message. - * - * @generated from rpc agent.v1.AgentService.NameAgent - */ - nameAgent: { - methodKind: "unary"; - input: typeof NameAgentRequestSchema; - output: typeof NameAgentResponseSchema; - }; - /** - * @generated from rpc agent.v1.AgentService.GetUsableModels - */ - getUsableModels: { - methodKind: "unary"; - input: typeof GetUsableModelsRequestSchema; - output: typeof GetUsableModelsResponseSchema; - }; - /** - * @generated from rpc agent.v1.AgentService.GetDefaultModelForCli - */ - getDefaultModelForCli: { - methodKind: "unary"; - input: typeof GetDefaultModelForCliRequestSchema; - output: typeof GetDefaultModelForCliResponseSchema; - }; - /** - * Internal endpoint: returns all allowed model intents for devs - * - * @generated from rpc agent.v1.AgentService.GetAllowedModelIntents - */ - getAllowedModelIntents: { - methodKind: "unary"; - input: typeof GetAllowedModelIntentsRequestSchema; - output: typeof GetAllowedModelIntentsResponseSchema; - }; -}> = /*@__PURE__*/ serviceDesc(file_agent, 0); + /** + * @generated from rpc agent.v1.AgentService.Run + */ + run: { + methodKind: "unary"; + input: typeof AgentClientMessageSchema; + output: typeof AgentServerMessageSchema; + }, + /** + * @generated from rpc agent.v1.AgentService.RunSSE + */ + runSSE: { + methodKind: "unary"; + input: typeof BidiRequestIdSchema; + output: typeof AgentServerMessageSchema; + }, + /** + * Generate a very short, succinct agent name from the provided user message. + * + * @generated from rpc agent.v1.AgentService.NameAgent + */ + nameAgent: { + methodKind: "unary"; + input: typeof NameAgentRequestSchema; + output: typeof NameAgentResponseSchema; + }, + /** + * @generated from rpc agent.v1.AgentService.GetUsableModels + */ + getUsableModels: { + methodKind: "unary"; + input: typeof GetUsableModelsRequestSchema; + output: typeof GetUsableModelsResponseSchema; + }, + /** + * @generated from rpc agent.v1.AgentService.GetDefaultModelForCli + */ + getDefaultModelForCli: { + methodKind: "unary"; + input: typeof GetDefaultModelForCliRequestSchema; + output: typeof GetDefaultModelForCliResponseSchema; + }, + /** + * Internal endpoint: returns all allowed model intents for devs + * + * @generated from rpc agent.v1.AgentService.GetAllowedModelIntents + */ + getAllowedModelIntents: { + methodKind: "unary"; + input: typeof GetAllowedModelIntentsRequestSchema; + output: typeof GetAllowedModelIntentsResponseSchema; + }, +}> = /*@__PURE__*/ + serviceDesc(file_agent, 0); /** * @generated from service agent.v1.ControlService */ export const ControlService: GenService<{ - /** - * Spawn - * File read / write - * - * @generated from rpc agent.v1.ControlService.ReadTextFile - */ - readTextFile: { - methodKind: "unary"; - input: typeof ReadTextFileRequestSchema; - output: typeof ReadTextFileResponseSchema; - }; - /** - * @generated from rpc agent.v1.ControlService.WriteTextFile - */ - writeTextFile: { - methodKind: "unary"; - input: typeof WriteTextFileRequestSchema; - output: typeof WriteTextFileResponseSchema; - }; - /** - * Binary file read / write - * - * @generated from rpc agent.v1.ControlService.ReadBinaryFile - */ - readBinaryFile: { - methodKind: "unary"; - input: typeof ReadBinaryFileRequestSchema; - output: typeof ReadBinaryFileResponseSchema; - }; - /** - * @generated from rpc agent.v1.ControlService.WriteBinaryFile - */ - writeBinaryFile: { - methodKind: "unary"; - input: typeof WriteBinaryFileRequestSchema; - output: typeof WriteBinaryFileResponseSchema; - }; - /** - * Git - * - * @generated from rpc agent.v1.ControlService.GetWorkspaceChangesHash - */ - getWorkspaceChangesHash: { - methodKind: "unary"; - input: typeof GetWorkspaceChangesHashRequestSchema; - output: typeof GetWorkspaceChangesHashResponseSchema; - }; - /** - * @generated from rpc agent.v1.ControlService.RefreshGithubAccessToken - */ - refreshGithubAccessToken: { - methodKind: "unary"; - input: typeof RefreshGithubAccessTokenRequestSchema; - output: typeof RefreshGithubAccessTokenResponseSchema; - }; - /** - * Remote access - * - * @generated from rpc agent.v1.ControlService.WarmRemoteAccessServer - */ - warmRemoteAccessServer: { - methodKind: "unary"; - input: typeof WarmRemoteAccessServerRequestSchema; - output: typeof WarmRemoteAccessServerResponseSchema; - }; - /** - * Artifact uploads - * - * @generated from rpc agent.v1.ControlService.ListArtifacts - */ - listArtifacts: { - methodKind: "unary"; - input: typeof ListArtifactsRequestSchema; - output: typeof ListArtifactsResponseSchema; - }; - /** - * @generated from rpc agent.v1.ControlService.UploadArtifacts - */ - uploadArtifacts: { - methodKind: "unary"; - input: typeof UploadArtifactsRequestSchema; - output: typeof UploadArtifactsResponseSchema; - }; - /** - * @generated from rpc agent.v1.ControlService.GetMcpRefreshTokens - */ - getMcpRefreshTokens: { - methodKind: "unary"; - input: typeof GetMcpRefreshTokensRequestSchema; - output: typeof GetMcpRefreshTokensResponseSchema; - }; - /** - * Update the exec-daemon's environment variables for subsequent process spawns. This does NOT affect already-running processes. - * - * @generated from rpc agent.v1.ControlService.UpdateEnvironmentVariables - */ - updateEnvironmentVariables: { - methodKind: "unary"; - input: typeof UpdateEnvironmentVariablesRequestSchema; - output: typeof UpdateEnvironmentVariablesResponseSchema; - }; -}> = /*@__PURE__*/ serviceDesc(file_agent, 1); + /** + * Spawn + * File read / write + * + * @generated from rpc agent.v1.ControlService.ReadTextFile + */ + readTextFile: { + methodKind: "unary"; + input: typeof ReadTextFileRequestSchema; + output: typeof ReadTextFileResponseSchema; + }, + /** + * @generated from rpc agent.v1.ControlService.WriteTextFile + */ + writeTextFile: { + methodKind: "unary"; + input: typeof WriteTextFileRequestSchema; + output: typeof WriteTextFileResponseSchema; + }, + /** + * Binary file read / write + * + * @generated from rpc agent.v1.ControlService.ReadBinaryFile + */ + readBinaryFile: { + methodKind: "unary"; + input: typeof ReadBinaryFileRequestSchema; + output: typeof ReadBinaryFileResponseSchema; + }, + /** + * @generated from rpc agent.v1.ControlService.WriteBinaryFile + */ + writeBinaryFile: { + methodKind: "unary"; + input: typeof WriteBinaryFileRequestSchema; + output: typeof WriteBinaryFileResponseSchema; + }, + /** + * Git + * + * @generated from rpc agent.v1.ControlService.GetWorkspaceChangesHash + */ + getWorkspaceChangesHash: { + methodKind: "unary"; + input: typeof GetWorkspaceChangesHashRequestSchema; + output: typeof GetWorkspaceChangesHashResponseSchema; + }, + /** + * @generated from rpc agent.v1.ControlService.RefreshGithubAccessToken + */ + refreshGithubAccessToken: { + methodKind: "unary"; + input: typeof RefreshGithubAccessTokenRequestSchema; + output: typeof RefreshGithubAccessTokenResponseSchema; + }, + /** + * Remote access + * + * @generated from rpc agent.v1.ControlService.WarmRemoteAccessServer + */ + warmRemoteAccessServer: { + methodKind: "unary"; + input: typeof WarmRemoteAccessServerRequestSchema; + output: typeof WarmRemoteAccessServerResponseSchema; + }, + /** + * Artifact uploads + * + * @generated from rpc agent.v1.ControlService.ListArtifacts + */ + listArtifacts: { + methodKind: "unary"; + input: typeof ListArtifactsRequestSchema; + output: typeof ListArtifactsResponseSchema; + }, + /** + * @generated from rpc agent.v1.ControlService.UploadArtifacts + */ + uploadArtifacts: { + methodKind: "unary"; + input: typeof UploadArtifactsRequestSchema; + output: typeof UploadArtifactsResponseSchema; + }, + /** + * @generated from rpc agent.v1.ControlService.GetMcpRefreshTokens + */ + getMcpRefreshTokens: { + methodKind: "unary"; + input: typeof GetMcpRefreshTokensRequestSchema; + output: typeof GetMcpRefreshTokensResponseSchema; + }, + /** + * Update the exec-daemon's environment variables for subsequent process spawns. This does NOT affect already-running processes. + * + * @generated from rpc agent.v1.ControlService.UpdateEnvironmentVariables + */ + updateEnvironmentVariables: { + methodKind: "unary"; + input: typeof UpdateEnvironmentVariablesRequestSchema; + output: typeof UpdateEnvironmentVariablesResponseSchema; + }, +}> = /*@__PURE__*/ + serviceDesc(file_agent, 1); /** * Agent Service with unary RPC * * @generated from service agent.v1.ExecService */ -export const ExecService: GenService<{}> = /*@__PURE__*/ serviceDesc(file_agent, 2); +export const ExecService: GenService<{ +}> = /*@__PURE__*/ + serviceDesc(file_agent, 2); /** * @generated from service agent.v1.PrivateWorkerBridgeExternalService */ export const PrivateWorkerBridgeExternalService: GenService<{ - /** - * @generated from rpc agent.v1.PrivateWorkerBridgeExternalService.Connect - */ - connect: { - methodKind: "unary"; - input: typeof FrameSchema; - output: typeof FrameSchema; - }; -}> = /*@__PURE__*/ serviceDesc(file_agent, 3); + /** + * @generated from rpc agent.v1.PrivateWorkerBridgeExternalService.Connect + */ + connect: { + methodKind: "unary"; + input: typeof FrameSchema; + output: typeof FrameSchema; + }, +}> = /*@__PURE__*/ + serviceDesc(file_agent, 3); /** * LifecycleService is exposed by the bridge *client*, in addition to ExecService (tool calls) and ControlService (control operations "within the daemon"). It operates at a similar abstraction level as AnyrunService: it represents operations similar to creating a VM, checking out a repository, etc. @@ -15251,24 +19488,26 @@ export const PrivateWorkerBridgeExternalService: GenService<{ * @generated from service agent.v1.LifecycleService */ export const LifecycleService: GenService<{ - /** - * Resets a long-lived worker - * - * @generated from rpc agent.v1.LifecycleService.ResetInstance - */ - resetInstance: { - methodKind: "unary"; - input: typeof EmptySchema; - output: typeof EmptySchema; - }; - /** - * Asks worker to exit(0) so that a new worker can take his place - * - * @generated from rpc agent.v1.LifecycleService.RenewInstance - */ - renewInstance: { - methodKind: "unary"; - input: typeof EmptySchema; - output: typeof EmptySchema; - }; -}> = /*@__PURE__*/ serviceDesc(file_agent, 4); + /** + * Resets a long-lived worker + * + * @generated from rpc agent.v1.LifecycleService.ResetInstance + */ + resetInstance: { + methodKind: "unary"; + input: typeof EmptySchema; + output: typeof EmptySchema; + }, + /** + * Asks worker to exit(0) so that a new worker can take his place + * + * @generated from rpc agent.v1.LifecycleService.RenewInstance + */ + renewInstance: { + methodKind: "unary"; + input: typeof EmptySchema; + output: typeof EmptySchema; + }, +}> = /*@__PURE__*/ + serviceDesc(file_agent, 4); + diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index afc819fcb..d46b3097b 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -7,6 +7,12 @@ - Added server-name autocomplete for `/mcp` commands (`enable`, `disable`, `test`, `remove`, `reconnect`, `reauth`, `unauth`) using configured and runtime-discovered MCP servers. - Added `--from-claude` and `--from-codex` session imports, also available from `/resume @claude` and `/resume @codex`. - Added an opt-in OMP-native software-security workflow (`security.enabled`, default off) with immutable scan plans, exact-account Codex subscription affinity, native task-worker review, canonical findings/coverage/SARIF publication, project-scoped history, explicit dispositions, producer-differential comparison, and the read-only `security://` resource namespace. Generic SARIF and official Codex Security bundles normalize into the same OMP-owned store. +- Added `--from-claude` and `--from-codex` session imports (including compaction state for Codex), also available from `/resume @claude` and `/resume @codex`. +- Added interactive Exa API-key onboarding through `/login exa`, opening the official key dashboard and saving pasted keys for authenticated web search while preserving `EXA_API_KEY` and explicit-selection public MCP fallback behavior ([#1798](https://github.com/can1357/oh-my-pi/issues/1798)). +- Added `ExtensionContext.getAsyncJobSnapshot()` so extensions can read the owning session's async-job state without relying on process-global job-manager identity +- Added opt-in `tui.codexResetFireworks` celebrations for unscheduled Codex weekly usage resets and newly banked saved resets, shown in a theme-aware top-third modal until Escape ([#6858](https://github.com/can1357/oh-my-pi/pull/6858) by [@joshrzemien](https://github.com/joshrzemien)). +- The Cursor exec bridge serves the seven modern Pi tool frames, mapping each to its local equivalent: `pi_read`/`pi_ls` → `read`, `pi_bash` → `bash`, `pi_edit` → `edit`, `pi_write` → `write`, `pi_grep` → `grep`, and `pi_find` → `glob`. The frames are a separate wire family from the legacy args, not aliases, so each mapping is a real translation — `pi_grep`'s `ignore_case` is the inverse of the local tool's case-sensitivity flag, `pi_find` searches filenames rather than contents, and `pi_edit`'s replacements are renamed to the local snake_case pairs. +- `providers.autoThinkingMaxEffort` (`xhigh` | `max`, default `xhigh`) raises the ceiling of the `auto` thinking classifier. `max` became a first-class effort tier after the classifier prompt was written, so `auto` could never reach it on models that expose the tier — only the `ultrathink` keyword could. Opting in adds `max` to the classifier's vocabulary, gated on the target model actually supporting it; the default keeps today's prompt byte-for-byte. The ceiling is enforced inside the effort clamp rather than on the classifier's answer, so a sparse ladder cannot snap an excluded request back up, and the Low floor is still resolved against the model's own ladder. The on-device 3-bucket classifier stays capped at `xhigh` regardless of the setting. The ceiling governs what `auto` resolves: a ladder with nothing underneath it yields no auto level, and a `thinking.requiresEffort` model still gets its lowest supported effort from the transport. ### Changed @@ -15,9 +21,15 @@ - Optimized tool guidance for bash, grep, and glob to be more concise while clarifying shell boundaries and search timeouts. - Optimized models configuration resource probing to run in a single child process, reducing startup contention. - Reserved `security://` from RPC host URI shadowing so vendor adapters cannot replace OMP's canonical security-analysis namespace. +- Startup release notes now default to a compact change-count summary. Use `startup.changelogMode` (`summary` | `expanded` | `hidden`) to control them; legacy `collapseChangelog` choices migrate automatically ([#6771](https://github.com/can1357/oh-my-pi/issues/6771)). ### Fixed +- Fixed task tool blocks duplicating their per-agent progress rows into terminal scrollback on every update: live task frames now pin the transcript live region so mid-run rows are never recorded as frozen snapshots, and a detached background task freezes its progress the moment any of its rows commit to scrollback instead of mutating committed history. +- Fixed Codex reset fireworks comparing different quota tiers or plans, preventing false celebrations when usage reports switch between Spark and base weekly limits. +- Fixed Cursor ranged-read results losing the full file byte size after applying the requested window. +- Fixed empty Codex final-stop recovery discarding an earlier commentary message when both messages shared response metadata. +- Fixed Advisor availability with providers that refuse echoed reasoning by retrying once with primary thinking stripped and surfacing persistent refusals immediately. - Fixed `/tan` agents being unable to read parent-session `local://` attachments by correctly resolving local protocol options against the parent session's artifacts. - Fixed Codex web search silently returning plain completions when the hosted web search tool was skipped. - Fixed TUI collaboration guest loader not starting when joining or reconnecting mid-turn. @@ -36,6 +48,44 @@ - Fixed file corruption and snapshot mismatches when writing files through the ACP client bridge by verifying the final on-disk content after client-side post-save formatting. - Fixed `omp ttsr test` silently evaluating source files as prose when their extensions were missing from the allowlist, and expanded the allowlist to support .NET, Shell, SQL, Zig, Dart, Scala, Elixir, and Protobuf files. - Fixed automatic light/dark theme switching in direct WezTerm sessions on macOS when DEC Mode 2031 is unsupported, and improved theme-change color responsiveness. +- Fixed configured `retry.maxDelayMs` not being forwarded into Anthropic retry handling, so over-budget server retry delays fail fast. +- Added tokens-per-second throughput to RPC `get_state` responses for non-TUI clients. +- Added the RPC `set_fast_mode` command and typed TypeScript/Python client methods for live fast-mode control. +- Added `fastModeEnabled` and `fastModeActive` to RPC `get_state` responses. +- Fixed RPC fast-mode state reporting after direct Anthropic rejects `speed: "fast"`, while allowing explicit re-enable requests to retry priority service. +- Added opt-in subagent access to `checkpoint`, `rewind`, `learn`, and `manage_skill` when explicitly listed in an agent definition's `tools:` frontmatter. Listing one of `checkpoint`/`rewind` auto-includes the other. Settings (`checkpoint.enabled`, `autolearn.enabled`) remain master toggles. +- Added a `browser.cdpUrl` setting that points browser automation at an already-running CDP endpoint by default, so `app.cdp_url` no longer has to be repeated on every call. Explicit `app` options still take precedence. +- Native compaction preserves provider-native success and non-authentication failure semantics while retaining authenticated cross-provider fallback when the native provider rejects credentials. +- Fixed the Cursor Pi exec bridge silently dropping frame arguments. `pi_read`'s `offset`/`limit` were ignored, so a ranged read returned the whole file; `pi_grep`'s `literal` was ignored, so a fixed-string search ran as a regex and matched the wrong lines; and the path/glob join produced a `./`-prefixed spec. Ranges are now composed onto `read`'s `:N+K` inline selector, literal patterns are escaped, and the join uses `node:path`. These are `optional int32` fields, so a present `0` is honored rather than folded into a default: `pi_read` with `limit: 0` answers with empty output instead of the entire file, and `pi_find` with `limit: 0` clamps to 1 the way the reference client does. +- `pi_grep`'s `context` and `limit` are honored. Neither is expressible in the model-facing `grep` schema — context width comes from `grep.contextBefore`/`grep.contextAfter` fixed at tool construction — so the bridge builds a per-call `grep` for frames that supply them. `GrepTool` accepts these as constructor options; the model-facing schema is unchanged, and a frame that supplies neither keeps the shared instance and the session's defaults. +- `pi_ls`'s `limit` is still not mapped, now deliberately: it caps directory *entries*, while the local `read` tool renders a depth-2 tree with per-directory caps and elision rows and applies a selector as a *rendered line* slice. Mapping it to `:1+K` would cap a different unit while appearing honored. +- The legacy pi shim's regex-literal escaper and path/glob join were verbatim copies of the modern bridge's. Both paths now call the shared helpers, so the two Pi translations cannot drift. +- Fixed every Cursor `pi_edit` frame failing instead of editing. Two independent causes: the session drops `edit` from the tool registry for Cursor so the model uses full-file `write`, but that registry is also the exec bridge's tool source, so the native frame — which the server sends regardless of the advertised catalog — found no tool; and the retained instance followed the session's configured edit mode, while `PiEditExecArgs` carries `old_text`/`new_text` pairs that only `replace` accepts (the default `hashline` takes a single `input` string). The bridge now resolves a `replace`-mode instance through its fallback resolver, still wrapped for approval. +- Fixed a `pi_grep` frame carrying `context` or `limit` escaping the approval gate. Honoring those fields needs a per-call `grep`, and the per-call instance was built raw while every registry tool is wrapped, so such calls bypassed `tools.approval.grep` and the exec-tier check for SSH-targeted paths. Both bridge callsites now build it through one shared factory that applies the same wrapper. +- Fixed Cursor advisors ignoring `pi_grep`'s `context` and `limit`. Only the primary session supplied the per-call `grep` factory, so advisor frames silently fell back to session defaults. Advisors now receive the same factory, gated on the advisor actually having been granted `grep`. +- Fixed Cursor advisors failing every `pi_edit`. The advisor roster handed the bridge the `edit` instance built for the advisor's own loop, which follows the configured `edit.mode` (`hashline` by default) and rejects the frame's `old_text`/`new_text` pairs — the same mode mismatch the primary bridge already fixed, on the path it missed. The exec map now substitutes a `replace`-mode instance, gated on the advisor actually having been granted `edit`, while the advisor's own loop keeps the tool it was given. +- Fixed `pi_bash` killing commands that explicitly asked for no deadline. `timeout` is `optional int32` and `bash` documents `0` as "disables the command deadline", but a truthiness check folded a supplied `0` into unset, applying the 300s default instead. A present `0` now passes through; negatives, which have no local meaning and would otherwise clamp to the 1s floor, still fall back to the default. +- Fixed the Cursor exec bridge granting `edit` and `grep` to sessions that withheld them. Both bridge-only tools are constructed rather than looked up, and `executeTool` prefers a constructed override over the registry, so a restricted tool set (`toolNames` without them, or `restrictToolNames`) still got a working `pi_edit`/`pi_grep` — native frames arrive regardless of the advertised catalog. Both are now gated on the session having actually granted the tool, matching the `delete` frame's existing check (issue #5680). +- Fixed Cursor advisor bridge tools bypassing approval settings. The advisor's `pi_edit`/`pi_grep` instances are approval-wrapped, but the wrapper reads `tools.approvalMode`, per-tool `tools.approval.` policies and `autoApprove` only from the execute-time tool context — which the advisor bridge never supplied, so every native advisor frame resolved as `yolo` with empty policies and ran past a configured `ask` or `deny`. Advisors now receive the same context store as the primary bridge. +- Fixed Cursor's `list_mcp_resources`/`read_mcp_resource` frames answering as though the client hosted no MCP servers. The bridge hardcoded an empty catalog and `not_found`, so resources from servers the session held live connections to were invisible to the model even while the same session read them through `mcp://`. Both frames now answer from the session's `MCPManager` — awaiting a server's background resource discovery rather than reading the not-yet-populated cache and reporting "advertises nothing" — and a lookup failure surfaces as an error rather than an empty catalog, which would read as "asked, none exist". A read carrying `download_path` writes the resource to that path and answers with the path alone, per the wire contract, instead of putting the payload back in the model's context. That path arrives from the server while the general-purpose resolver deliberately honors absolute paths and `..`, so downloads are confined to the workspace: the resolved target and its deepest existing ancestor must stay inside it, and a target that is itself a symlink is refused. The write then opens `O_NOFOLLOW` and refuses a non-regular or hard-linked file before truncating, so the final component cannot be swapped for a link or an inode shared outside after the check. A parent directory replaced by a symlink mid-write is still followed; closing that needs `openat`/dirfd walking, which this does not attempt. +- Fixed the Cursor native `delete` frame bypassing approval settings. Unlike every other frame it removes the file directly instead of running a registry tool, so no approval wrapper sat in front of it — the bridge's `allowDirectFileMutation` grant answers whether a mutating tool was granted, which is a different question from whether the user's policy allows the call. A configured `tools.approval.delete: deny`, or an `always-ask` session that this channel cannot prompt in, now refuses the frame and keeps the file. +- Fixed Cursor download-mode resource reads bypassing the session's mutation restrictions. A `read_mcp_resource` frame carrying `download_path` creates and overwrites workspace files without running a registry tool — the same hole the native `delete` frame had — so a session that withheld `write`/`edit`, or one whose `write` tier is `deny`/`always-ask`, still had files written. Both frames now share one grant (`allowDirectFileMutation`, renamed from `allowNativeDelete` now that it gates more than deletion) and one `write`-tier policy check, and the download refuses before the read so a blocked call does not fetch the resource either. The primary session derives that grant before it rewrites its registry: Cursor moves `edit` out of the tool map and `write` may be auto-registered later, so reading the map at bridge-construction time would have misjudged both. +- Fixed `pi_ls` never reporting that a listing was clipped. The bridge read the entry cap from a flat `details.resultLimitReached`, which `glob` sets but `read` — the tool serving `pi_ls` — does not: it records the cap through `OutputMeta` at `details.meta.limits.resultLimit.reached`. Every capped listing therefore reached Cursor with `entry_limit_reached` unset, reading as complete. Both shapes are now checked, the same way the truncation translation already handles its two producers. +- Fixed a mixed-content MCP resource read reaching Cursor mislabelled. The mime type was taken from the first content item while the payload came from whichever item supplied it, so an image blob followed by a text note sent the text as `image/png`. Each branch now reports the type of the part it actually sends. +- Fixed `pi_read`'s `offset`/`limit` returning more lines than the frame asked for. The range is composed onto the local `read` tool's inline selector, and a plain `:N+K` deliberately pads with one leading and three trailing context lines — helpful when a human reads a snippet, wrong for a caller that named an exact range: offset 5/limit 20 handed Cursor lines 4-27. Ranged Pi reads now compose `:raw:N+K`, which slices exactly the requested lines. +- Fixed `pi_grep` returning fewer matches than it asked for when they spread across many files. The local `grep` windows results to the first 20 files and tells the caller to paginate with `skip`, but `PiGrepExecArgs` has no `skip` field — so a frame asking for 100 matches over 25 one-match files got 20, `match_limit_reached` unset, and advice it could not act on: output silently short and labelled complete. A search carrying a total match cap now reads enough files to satisfy it (cap+1, so a result landing exactly on the cap is distinguishable from a clipped one) and reports the cap when it actually bites. +- Fixed every native `pi_edit` failing after a session switched onto Cursor. The replace-mode `edit` instance the frame needs was built only for sessions *created* on Cursor, and the tool roster is not rebuilt on a model switch — so a session that started elsewhere kept its configured-mode `edit` in the registry, which the bridge resolves before its fallback, and the frame's `old_text`/`new_text` pairs failed validation against a `hashline` schema. The instance is now built from the `edit` grant regardless of the session's initial provider (lazily, so a session that never reaches Cursor never constructs one) and `pi_edit` asks for it explicitly through a dedicated accessor. A session that was never granted `edit` is still refused. +- Fixed the Cursor bridge's tool resolver being able to execute an unadvertised `edit`. That resolver doubles as the agent loop's fallback for any call outside the advertised set, so serving `edit` from it meant a hallucinated call — or one naming a tool the session deselected after startup — could run a replace-mode edit the model was never offered. It is device-only again; `pi_edit` uses its own accessor. +- Fixed the legacy Cursor `read` frame ignoring the `offset`/`limit` modern builds paginate with. Only the Pi variant composed a range, so every page of a legacy read returned the whole file (or its own truncation) and a model walking a large file never advanced past the first window. Both frames now translate a range through the same helper, and the answer sets `range_applied` to describe whether a window was actually composed. +- Fixed the legacy Cursor `grep` frame ignoring its pagination `offset`. The local `grep` paginates by file through `skip` and advertises exactly that in its own "use skip=N" advice, so an unforwarded offset re-ran the identical search and answered page one for every page. The answer now reports the offset it applied in `offset_applied`. +- Fixed a paginated Cursor `read` or `grep` frame being recorded as an unpaginated one. The executed call and the transcript block are built separately, so forwarding the frame's range and page fixed only the execution: the block still showed a bare path and an unskipped search, which is what a reloaded session replays and what the next turn reasons from — a slice of a file presented as the whole thing, and results from a later window presented as page one. Both are now synthesized from the same translation that runs them, including a `limit: 0` read, which is recorded as the zero lines it returns rather than a whole-file read. +- Fixed Cursor advisors answering every MCP resource frame as though the client hosted no servers. Only the primary bridge received the `MCPManager`-backed resource adapter, so an advisor's `list_mcp_resources` reported an empty catalog and its `read_mcp_resource` a `not_found` even though the advisor shares the session's live connections. Advisors now receive the same adapter; it is not gated on a tool grant, since reading what a server advertises is a different permission from calling one of its tools. +- Fixed advisor tools bypassing the approval gate. They are built straight from the builtin table, outside the loop that wraps every registry tool, and both the advisor's own agent loop and its Cursor exec bridge (`pi_write`, `pi_bash`) run those instances directly — so an advisor granted `write` or `bash` executed them regardless of a configured `ask` or `deny`. They now carry the same `ExtensionToolWrapper` as every other tool. +- Added `mcp_notification` extension event and multi-listener `MCPManager.addNotificationListener` API. The runtime already received MCP server-initiated JSON-RPC notifications at the transport layer but had no path to forward them to extensions; every notification (including server-custom methods) is now delivered as `{ server, method, params }` after the manager's own list/update handling. For known list-change methods (`notifications/tools/list_changed`, `notifications/resources/list_changed`, `notifications/prompts/list_changed`) the internal refresh promise is awaited before fanout, so a listener acting on `tools/list_changed` sees fresh `getTools()`. Notifications received before any listener attaches are buffered (bounded FIFO, cap 100, drop-oldest — matches `IrcBus`'s `MAILBOX_CAP`) and drained into the first subscriber, so startup-time frames aren't lost even if the extension binds after MCP discovery. Extensions can use this to bridge push-capable MCP servers (e.g. peer messaging) into session behavior by injecting a mid-turn steer via `pi.sendMessage` / `pi.sendUserMessage`. + +### Removed + +- Removed the dangling `MCPManager.setOnNotification` single-slot setter, which had no callers in the runtime. Replaced by `MCPManager.addNotificationListener` — multi-listener, per-listener error isolation, returns an unsubscribe function. ## [17.1.8] - 2026-07-28 diff --git a/packages/coding-agent/src/advisor/runtime.ts b/packages/coding-agent/src/advisor/runtime.ts index 3e2546471..245e0abba 100644 --- a/packages/coding-agent/src/advisor/runtime.ts +++ b/packages/coding-agent/src/advisor/runtime.ts @@ -78,6 +78,8 @@ export interface AdvisorRuntimeHost { * recovery (credential switch, fallback chain) declined. Cleared only by * an explicit reset (`/new`, config rebuild, session restart). */ notifyQuotaExhausted?(): void; + /** Stable identity for the live advisor model. Used to restore full transcript rendering after a model switch. */ + getModelIdentity?(): string; } /** @@ -230,7 +232,6 @@ const MAX_COALESCE_ROUNDS = 3; const MAX_QUARANTINE_RETRIES = 2; const ADVISOR_RENDER_OPTIONS = { - includeThinking: true, includeToolIntent: true, watchedRoles: true, expandPrimaryContext: true, @@ -295,6 +296,9 @@ export class AdvisorRuntime { #failureNotified = false; /** Consecutive quarantined turns since the last success/reset (issue #6661). */ #consecutiveQuarantines = 0; + /** Whether primary reasoning is included in advisor deltas for the current model. */ + #includeThinking = true; + #modelIdentity: string | undefined; /** Completed 3-failure backlog-drop cycles since the last success/reset. */ #droppedBacklogs = 0; /** @@ -563,13 +567,23 @@ export class AdvisorRuntime { this.#wakeAllWaiters(); } + #syncModelIdentity(): void { + const identity = this.host.getModelIdentity?.(); + if (identity === undefined || identity === this.#modelIdentity) return; + this.#modelIdentity = identity; + this.#includeThinking = true; + } + #formatRawDelta(rawMessages: AgentMessage[], wip = false): string | null { const delta = rawMessages .filter(message => !(message.role === "custom" && message.customType === "advisor")) .map(message => this.#dedupContextMessage(message)); if (delta.length === 0) return null; const obfuscator = this.host.obfuscator; - let md = formatSessionHistoryMarkdown(delta, ADVISOR_RENDER_OPTIONS); + let md = formatSessionHistoryMarkdown(delta, { + ...ADVISOR_RENDER_OPTIONS, + includeThinking: this.#includeThinking, + }); if (!md.trim()) return null; if (obfuscator?.hasSecrets()) { let discoveredNewRegexSecretValue = false; @@ -603,7 +617,7 @@ export class AdvisorRuntime { ? obfuscateAdvisorMessage(obfuscator, message, this.#advisorRegexSecretValues) : message, ), - ADVISOR_RENDER_OPTIONS, + { ...ADVISOR_RENDER_OPTIONS, includeThinking: this.#includeThinking }, ); md = obfuscator.obfuscate(md, this.#advisorRegexSecretValues); } @@ -842,7 +856,9 @@ export class AdvisorRuntime { if (this.#busy || this.#sessionTransitionPaused) return; this.#busy = true; try { + this.#syncModelIdentity(); while (!this.disposed && !this.#sessionTransitionPaused && this.#pending.length) { + this.#syncModelIdentity(); let popped: PendingDelta[]; if (this.#pending[0]?.overflowRecovery) { const recovery = this.#pending.shift(); @@ -944,6 +960,9 @@ export class AdvisorRuntime { this.#wakeAllWaiters(); const failedMessages = this.agent.state.messages.slice(messageSnapshot); const terminalFailure = this.#terminalAssistantFailure(messageSnapshot); + const classifierRefusal = + (terminalFailure !== undefined && isClassifierRefusal(terminalFailure)) || + AIError.is(AIError.classify(err), AIError.Flag.ContentBlocked); const terminalFailureId = terminalFailure === undefined ? undefined : AIError.classifyMessage(terminalFailure); const contextOverflow = @@ -959,6 +978,29 @@ export class AdvisorRuntime { AIError.is(terminalFailureId, AIError.Flag.ContextOverflow); this.#rollbackFailedTurn(messageSnapshot); logger.debug("advisor turn failed", { err: String(err) }); + if (classifierRefusal) { + if (this.#includeThinking) { + this.#includeThinking = false; + const strippedBatch = this.#formatRawDelta(rawMessages, wip); + if (strippedBatch) { + this.#pending.unshift({ + text: strippedBatch, + rawMessages, + renderRevision: this.#renderRevision, + turns: finalTurns, + wip, + overflowRecovery: recoveringOverflow || undefined, + }); + logger.debug("advisor refusal recovered by stripping primary reasoning"); + continue; + } + } + this.#notifyFailureOnce(err); + this.#clearSeenContext(); + this.#backlog = Math.max(0, this.#backlog - finalTurns); + this.#notifyWaiters(); + continue; + } let recovered = false; try { recovered = @@ -1112,6 +1154,14 @@ export class AdvisorRuntime { } } +/** Mirrors turn recovery's refusal classification and retains AIError's provider-neutral content-block fallback. */ +function isClassifierRefusal(message: AssistantMessage): boolean { + if (message.stopReason !== "error") return false; + const stopType = message.stopDetails?.type; + if (stopType === "refusal" || stopType === "sensitive") return true; + return AIError.is(AIError.classifyMessage(message), AIError.Flag.ContentBlocked); +} + /** * The only malformed advisor turn shape: the prompt resolved but produced no * assistant response at all. Everything an assistant message carries — advice, diff --git a/packages/coding-agent/src/auto-thinking/classifier.ts b/packages/coding-agent/src/auto-thinking/classifier.ts index 381e3b3f5..70a00f3a3 100644 --- a/packages/coding-agent/src/auto-thinking/classifier.ts +++ b/packages/coding-agent/src/auto-thinking/classifier.ts @@ -5,7 +5,8 @@ * {@link Effort}, clamped into the active model's supported range (never below * {@link Effort.Low}). Two backends, selected by `providers.autoThinkingModel`: * - * - `online` (default): a smol model classifies into `low|medium|high|xhigh`. + * - `online` (default): a smol model classifies into `low|medium|high|xhigh`, + * plus `max` when the target model exposes that tier. * - a local key: an on-device memory model classifies into the coarser * `trivial|moderate|hard` scheme (3-class is more reliable than 4-way ordinal * on sub-2B models), mapped to `low|high|xhigh`. @@ -14,6 +15,7 @@ * the caller falls back to a concrete level and continues the turn. */ import { type AssistantMessage, completeSimple, Effort, type Model } from "@oh-my-pi/pi-ai"; +import { getSupportedEfforts } from "@oh-my-pi/pi-catalog/model-thinking"; import { prompt } from "@oh-my-pi/pi-utils"; import type { ModelRegistry } from "../config/model-registry"; @@ -30,7 +32,31 @@ import { } from "../tiny/models"; import { tinyModelClient } from "../tiny/title-client"; -const DIFFICULTY_SYSTEM_PROMPT = prompt.render(difficultySystemPrompt); +/** + * Rendered classifier prompts, keyed by whether `max` is offered as a label. + * Two variants only, so both are memoized on first use. + */ +const DIFFICULTY_SYSTEM_PROMPTS: Partial> = {}; + +/** + * Highest effort this turn's classification may resolve to: the configured + * ceiling, further limited by what the target model actually exposes. The + * default keeps `auto` one tier below the top, so only an explicit + * `ultrathink` reaches {@link Effort.Max}. + */ +function autoEffortCeiling(deps: ClassifyDifficultyDeps): Effort { + if (deps.settings.get("providers.autoThinkingMaxEffort") !== Effort.Max) return Effort.XHigh; + return getSupportedEfforts(deps.model).includes(Effort.Max) ? Effort.Max : Effort.XHigh; +} + +function difficultySystemPromptFor(ceiling: Effort): string { + const key = ceiling === Effort.Max ? "max" : "xhigh"; + const cached = DIFFICULTY_SYSTEM_PROMPTS[key]; + if (cached !== undefined) return cached; + const rendered = prompt.render(difficultySystemPrompt, { allowMax: key === "max" }); + DIFFICULTY_SYSTEM_PROMPTS[key] = rendered; + return rendered; +} /** Local classifiers occasionally need more room for chat-template boilerplate. */ const LOCAL_ANSWER_MAX_TOKENS = 16; @@ -64,14 +90,18 @@ export async function classifyDifficulty( ): Promise { const backend = deps.settings.get("providers.autoThinkingModel"); const input = preprocessTinyMessage(promptText); - const effort = - backend === ONLINE_AUTO_THINKING_MODEL_KEY - ? await classifyOnline(input, deps) - : await classifyLocal(input, backend, deps); - return clampAutoThinkingEffort(deps.model, effort); + const online = backend === ONLINE_AUTO_THINKING_MODEL_KEY; + // The 3-bucket local classifier cannot select `max`, so its ceiling stays at + // XHigh whatever the setting says — otherwise a sparse ladder would snap its + // `hard` bucket up to a tier it never chose. + const ceiling = online ? autoEffortCeiling(deps) : Effort.XHigh; + const effort = online ? await classifyOnline(input, deps, ceiling) : await classifyLocal(input, backend, deps); + // The ceiling goes into the clamp itself: capping the request alone is not + // enough, because a sparse ladder snaps an excluded request back up. + return clampAutoThinkingEffort(deps.model, effort, ceiling); } -async function classifyOnline(input: string, deps: ClassifyDifficultyDeps): Promise { +async function classifyOnline(input: string, deps: ClassifyDifficultyDeps, ceiling: Effort): Promise { const resolved = resolveRoleSelection(["tiny", "smol"], deps.settings, deps.registry.getAvailable()); const model = resolved?.model; if (!model) { @@ -88,7 +118,7 @@ async function classifyOnline(input: string, deps: ClassifyDifficultyDeps): Prom const response = await completeSimple( model, { - systemPrompt: [DIFFICULTY_SYSTEM_PROMPT], + systemPrompt: [difficultySystemPromptFor(ceiling)], messages: [{ role: "user", content: input, timestamp: Date.now() }], }, { @@ -134,7 +164,13 @@ async function classifyLocal(input: string, modelKey: string, deps: ClassifyDiff return effort; } -/** Map the online 4-way level keyword to an {@link Effort}; earliest match wins. */ +/** + * Map an online level keyword to an {@link Effort}; earliest match wins. + * + * `max` is only offered to the classifier when the target model exposes that + * tier, but it is always parsed: an unsupported `max` is snapped back down by + * {@link clampAutoThinkingEffort} rather than failing the turn. + */ export function parseDifficultyLevel(text: string): Effort | undefined { const lower = text.toLowerCase(); const candidates: Array<[number, Effort]> = []; @@ -142,6 +178,8 @@ export function parseDifficultyLevel(text: string): Effort | undefined { // inside "xhigh" (no word boundary between `x` and `h`), so the two never collide. const xhigh = lower.search(/x[\s_-]?high/); if (xhigh >= 0) candidates.push([xhigh, Effort.XHigh]); + const max = lower.search(/\bmax\b/); + if (max >= 0) candidates.push([max, Effort.Max]); const high = lower.search(/\bhigh\b/); if (high >= 0) candidates.push([high, Effort.High]); const medium = lower.search(/\bmed(?:ium)?\b/); diff --git a/packages/coding-agent/src/config/settings-schema.ts b/packages/coding-agent/src/config/settings-schema.ts index 105eb3ba3..3022aac9c 100644 --- a/packages/coding-agent/src/config/settings-schema.ts +++ b/packages/coding-agent/src/config/settings-schema.ts @@ -927,6 +927,18 @@ export const SETTINGS_SCHEMA = { }, }, + "tui.codexResetFireworks": { + type: "boolean", + default: false, + ui: { + tab: "appearance", + group: "Display", + label: "Codex Reset Fireworks", + description: + "Celebrate unscheduled Codex weekly usage resets and newly banked saved resets with a top-third fireworks overlay that remains until Escape", + }, + }, + "tui.titleState": { type: "boolean", default: true, @@ -1811,14 +1823,32 @@ export const SETTINGS_SCHEMA = { }, }, - collapseChangelog: { - type: "boolean", - default: false, + "startup.changelogMode": { + type: "enum", + values: ["summary", "expanded", "hidden"] as const, + default: "summary", ui: { tab: "interaction", group: "Startup & Updates", - label: "Collapse Changelog", - description: "Show condensed changelog after updates", + label: "Startup Changelog", + description: "Choose whether update notes start as a summary, full details, or stay hidden", + options: [ + { + value: "summary", + label: "Summary", + description: "Show release and change counts with a /changelog hint", + }, + { + value: "expanded", + label: "Expanded", + description: "Show the recent release notes in full", + }, + { + value: "hidden", + label: "Hidden", + description: "Do not show release notes on startup", + }, + ], }, }, @@ -4066,6 +4096,18 @@ export const SETTINGS_SCHEMA = { }, }, + "browser.cdpUrl": { + type: "string", + default: undefined, + ui: { + tab: "tools", + group: "Grep & Browser", + label: "Browser CDP URL", + description: + "Default HTTP CDP discovery endpoint (for example http://127.0.0.1:9222) to attach to instead of launching a browser. Explicit app.cdp_url or app.path on the tool call take precedence.", + }, + }, + "browser.headless": { type: "boolean", default: true, @@ -5076,6 +5118,23 @@ export const SETTINGS_SCHEMA = { options: AUTO_THINKING_MODEL_OPTIONS, }, }, + "providers.autoThinkingMaxEffort": { + type: "enum", + values: ["xhigh", "max"] as const, + default: "xhigh", + ui: { + tab: "model", + group: "Thinking", + label: "Auto Thinking Ceiling", + description: + "Highest effort the `auto` classifier may resolve. `xhigh` keeps the classifier one tier below the top, so only an explicit `ultrathink` reaches `max`; `max` lets a turn the classifier judges exceptional bill the top tier on models that expose it.", + condition: "autoThinkingActive", + options: [ + { value: "xhigh", label: "xhigh", description: "Classifier stops at xhigh (default)" }, + { value: "max", label: "max", description: "Classifier may resolve max where the model supports it" }, + ], + }, + }, "features.unexpectedStopDetection": { type: "boolean", default: false, diff --git a/packages/coding-agent/src/config/settings.ts b/packages/coding-agent/src/config/settings.ts index d8e391096..fe10e861b 100644 --- a/packages/coding-agent/src/config/settings.ts +++ b/packages/coding-agent/src/config/settings.ts @@ -1346,6 +1346,31 @@ export class Settings { } delete raw.lastChangelogVersion; + // collapseChangelog (boolean) -> startup.changelogMode (enum). Preserve + // every explicit legacy choice while giving new installs the schema's + // "summary" default: true -> summary, false -> expanded. A separately + // configured new mode always wins. + const startupObj = isRecord(raw.startup) ? (raw.startup as Record) : undefined; + const legacyCollapseChangelog = typeof raw.collapseChangelog === "boolean" ? raw.collapseChangelog : undefined; + const flatChangelogMode = raw["startup.changelogMode"]; + const normalizedFlatChangelogMode = + flatChangelogMode === "summary" || flatChangelogMode === "expanded" || flatChangelogMode === "hidden" + ? flatChangelogMode + : undefined; + if (legacyCollapseChangelog !== undefined || normalizedFlatChangelogMode !== undefined) { + if (!startupObj) { + raw.startup = {}; + } + const target = raw.startup as Record; + if (target.changelogMode === undefined) { + target.changelogMode = + normalizedFlatChangelogMode ?? + (legacyCollapseChangelog !== undefined ? (legacyCollapseChangelog ? "summary" : "expanded") : undefined); + } + } + delete raw.collapseChangelog; + delete raw["startup.changelogMode"]; + // ask.timeout: ms -> seconds (if value > 1000, it's old ms format) if (raw.ask && typeof (raw.ask as Record).timeout === "number") { const oldValue = (raw.ask as Record).timeout as number; diff --git a/packages/coding-agent/src/cursor-bridge-tools.ts b/packages/coding-agent/src/cursor-bridge-tools.ts new file mode 100644 index 000000000..e2a037af2 --- /dev/null +++ b/packages/coding-agent/src/cursor-bridge-tools.ts @@ -0,0 +1,81 @@ +/** + * Per-call tools the Cursor exec bridge needs but the model-facing registry + * cannot supply. + * + * Both bridge callsites — the primary session and the advisor roster — build + * the same instances, and both must apply the session's approval wrapper. A + * raw tool here silently escapes the gate every registry call goes through, so + * the construction lives in one place rather than being repeated per callsite. + */ + +import type { AgentTool } from "@oh-my-pi/pi-agent-core"; +import { EditTool } from "./edit"; +import type { ExtensionRunner } from "./extensibility/extensions"; +import { ExtensionToolWrapper } from "./extensibility/extensions"; +import type { GrepToolOptions, Tool, ToolSession } from "./tools"; +import { GrepTool } from "./tools"; + +/** + * Build the bridge's `createGrepTool` factory for one tool session. + * + * A `pi_grep` frame carries its own context width and total match cap. Neither + * is expressible in the model-facing `grep` schema — context comes from + * `grep.contextBefore`/`grep.contextAfter`, fixed when the shared instance is + * constructed — so honoring them needs a fresh tool per call. + * + * The result is wrapped exactly like a registry tool: the approval gate runs on + * every call site, and a per-call instance is no exception. + */ +export function createBridgeGrepFactory( + session: ToolSession, + extensionRunner: ExtensionRunner, +): (options: GrepToolOptions) => AgentTool { + return options => { + const grepTool: Tool = new GrepTool(session, options); + return new ExtensionToolWrapper(grepTool, extensionRunner); + }; +} + +/** + * Build the `replace`-mode `edit` the bridge answers `pi_edit` with. + * + * `PiEditExecArgs` carries `old_text`/`new_text` pairs, which is exactly + * `replace`'s schema and nothing else's. The session's own instance follows the + * configured `edit.mode` — `hashline` by default, whose schema is a single + * `input` string — so a frame handed that instance fails validation instead of + * editing the file. + * + * Callers MUST gate this on the session having actually granted `edit`: the + * tool is constructed rather than looked up, so building one unconditionally + * hands a restricted agent a mutating tool it was denied (issue #5680). + */ +export function createBridgeEditTool(session: ToolSession, extensionRunner: ExtensionRunner): AgentTool { + const editTool: Tool = new EditTool(session, "replace"); + return new ExtensionToolWrapper(editTool, extensionRunner); +} + +/** + * The tool map the exec bridge should run, given the map a caller granted. + * + * `pi_edit` needs a `replace`-mode instance, but only when `edit` was granted: + * the tool is constructed rather than looked up, so substituting + * unconditionally would hand a restricted roster a mutating tool it was denied + * (issue #5680). The granted map is never mutated: an unsubstituted result is a + * copy, so a caller without an `edit` grant cannot accidentally gain one. + * + * The advisor roster passes its granted map here; the primary session keeps its + * instance out of the registry entirely (Cursor does not advertise `edit`) and + * serves it through the bridge's `getEditReplaceTool` accessor instead — not + * the `getTool` fallback, which doubles as the agent loop's resolver for + * unadvertised calls and must stay device-only. + */ +export function bridgeToolMap( + granted: ReadonlyMap, + createEditTool: (() => AgentTool | undefined) | undefined, +): Map { + const bridged = new Map(granted); + if (!granted.has("edit") || !createEditTool) return bridged; + const bridgeEdit = createEditTool(); + if (bridgeEdit) bridged.set("edit", bridgeEdit); + return bridged; +} diff --git a/packages/coding-agent/src/cursor.ts b/packages/coding-agent/src/cursor.ts index 7c30755a6..b0f63cd1b 100644 --- a/packages/coding-agent/src/cursor.ts +++ b/packages/coding-agent/src/cursor.ts @@ -1,5 +1,6 @@ import { randomUUID } from "node:crypto"; import * as fs from "node:fs"; +import * as path from "node:path"; import type { AgentEvent, AgentTool, @@ -9,35 +10,91 @@ import type { } from "@oh-my-pi/pi-agent-core"; import type { CursorMcpCall, + CursorMcpResource, + CursorMcpResourceContent, CursorShellStreamCallbacks, CursorTodoSnapshot, CursorExecHandlers as ICursorExecHandlers, ToolResultMessage, } from "@oh-my-pi/pi-ai"; +import { + piEscapeRegexLiteral, + piGrepSkip, + piJoinPath, + piLimit, + piLsPath, + piReadPath, + piTimeout, +} from "@oh-my-pi/pi-ai/providers/cursor/exec-modern"; import { sanitizeText } from "@oh-my-pi/pi-utils"; -import { resolveToCwd } from "./tools/path-utils"; +import type { MCPResourceReadResult } from "./mcp/types"; +import type { ApprovalMode } from "./tools/approval"; +import { resolveApproval } from "./tools/approval"; +import { confineToWorkspace, resolveToCwd } from "./tools/path-utils"; import type { TodoPhase, TodoStatus } from "./tools/todo"; /** Phase used for Cursor-owned tasks with no local phase grouping. */ const CURSOR_TODO_PHASE = "Tasks"; +/** + * A tool instance the bridge can run, matching the erased shape the session's + * tool registry stores. The concrete tools have narrower `execute` parameter + * types than the default `AgentTool`, which only unify through this alias. + */ +type CursorBridgeTool = AgentTool; + +/** + * The live MCP connections Cursor's resource frames are answered from. + * + * Named so every construction site can hand over the same adapter; a session + * and its advisors share one set of connections. + */ +export interface CursorMcpResourceAdapter { + serverNames(): string[]; + getServerResources( + name: string, + ): Promise<{ resources: { uri: string; name?: string; description?: string; mimeType?: string }[] } | undefined>; + readServerResource(name: string, uri: string): Promise; +} + interface CursorExecBridgeOptions { cwd: string; getCwd?: () => string; tools: Map; /** Resolves execution overrides (mounted-device permission wrappers) before the canonical map. */ getExecutableTool?: (name: string) => AgentTool | undefined; + /** + * The `replace`-mode `edit` instance `pi_edit` must run, when the session + * granted `edit` at all. + * + * `PiEditExecArgs` is that mode's schema verbatim, and the session's own + * `edit` may be in any mode — `hashline` by default — whose schema rejects + * `old_text`/`new_text` outright. {@link tools} therefore cannot be trusted + * for this one frame: a session that starts on another provider keeps its + * configured instance in the map (only Cursor sessions move `edit` out), and + * switching to Cursor later does not rebuild the roster. + */ + getEditReplaceTool?: () => CursorBridgeTool | undefined; getToolContext?: () => AgentToolContext | undefined; emitEvent?: (event: AgentEvent) => void; /** - * Whether the Cursor native `delete` frame may remove files. Unlike every - * other exec handler, `executeDelete` mutates the filesystem directly instead - * of consulting {@link tools}, so a background read-only advisor could delete - * workspace files it was never granted a mutating tool for (issue #5680 - * review). Defaults to allowed to preserve the primary agent's behavior; - * callers with a restricted tool set (advisors) opt out. + * Whether frames that mutate the filesystem WITHOUT running a registry tool + * may do so: the native `delete` frame, and a `read_mcp_resource` carrying + * `download_path`. Both write or remove workspace files directly instead of + * consulting {@link tools}, so a background read-only advisor could touch + * files it was never granted a mutating tool for (issue #5680 review). + * + * This is a grant, not a policy: it answers "did the session hand this + * channel a file-writing tool", which callers derive from their own roster + * before any bridge-specific rewriting. The primary Cursor session moves + * `edit` out of {@link tools} and serves it through + * {@link getEditReplaceTool}, so reading the map here would deny an + * edit-only session. Defaults to allowed + * to preserve the primary agent's behavior; callers with a restricted tool + * set (advisors) opt out. The user's approval policy is resolved separately, + * per call. */ - allowNativeDelete?: boolean; + allowDirectFileMutation?: boolean; /** * Mirror Cursor's server-owned todo list into local session state. Cursor * resolves `update_todos` / `read_todos` remotely, so without this bridge @@ -50,6 +107,99 @@ interface CursorExecBridgeOptions { * Cursor emits no local `todo` toolResult, so nothing else records it. */ persistTodoPhases?: (phases: TodoPhase[]) => void; + /** + * Build a `grep` tool honoring a frame's own context width and match cap. + * + * The modern `pi_grep` frame carries both, and the shared `grep` instance + * is fixed to the session settings at construction — so without this the + * two fields are silently dropped. Callers that cannot supply it keep the + * shared instance and the session's defaults. + * + * The returned tool is executed as-is. Callers whose registry tools carry an + * approval wrapper MUST apply the same wrapper here, or a frame supplying + * either field silently escapes the approval gate that every other call + * goes through. + */ + createGrepTool?(options: { context?: number; totalMatchLimit?: number }): CursorBridgeTool | undefined; + /** + * The session's live MCP connections, for Cursor's resource frames. + * + * `list_mcp_resources` / `read_mcp_resource` ask what this client's servers + * advertise. Without this the bridge answers an empty catalog and + * `not_found`, hiding resources the session is in fact connected to. + * + * `getServerResources` is async because a server's catalog loads in the + * background after its tools register: a frame arriving in that window would + * otherwise read the not-yet-populated cache and report an empty catalog, + * which is indistinguishable from a server that advertises nothing. + */ + mcpResources?: CursorMcpResourceAdapter; +} + +/** + * Write a downloaded resource without following a link at the target. + * + * The containment check and the write are separate syscalls, so a link planted + * at the target in between would redirect the bytes — the check cannot close + * that window on its own. `O_NOFOLLOW` decides it atomically for symlinks. + * + * A hard link needs a second check: it is a regular file that passes both the + * containment check and `O_NOFOLLOW` while sharing its inode with a file + * anywhere else on the volume, so truncating it overwrites that file too. + * `nlink > 1` on the OPEN handle is the test — statting the path first would + * reintroduce the race the open just closed. Same reasoning, and the same + * refusal, as `autolearn/managed-skills.ts`. + * + * Truncation therefore happens after that check rather than through `O_TRUNC`, + * which would have already destroyed the contents by the time it ran. + * + * Scope: `O_NOFOLLOW` applies to the FINAL component only. A parent directory + * swapped for an outward symlink between the check and this open is still + * followed; refusing that needs an `openat`/dirfd walk of every segment, which + * this does not attempt — an attacker who can rewrite the workspace tree + * mid-download is already inside the boundary this guard defends. + * + * Parent directories are created first, since the frame may name a path whose + * directories do not exist yet. + * + * `O_NONBLOCK` is what keeps the guard below reachable. A write-only open of a + * FIFO blocks until a reader arrives, so a `download_path` naming one would + * hang the turn forever WITHOUT the non-regular check ever running — the open + * itself never returns. Non-blocking turns that into `ENXIO` when no reader is + * attached, and hands back a descriptor the `isFile()` check refuses when one + * is. The flag has no effect on regular files, which is every legitimate + * target. + */ +async function writeWithoutFollowingLinks(absolutePath: string, payload: string | Buffer): Promise { + await fs.promises.mkdir(path.dirname(absolutePath), { recursive: true }); + const handle = await fs.promises + .open( + absolutePath, + fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK, + ) + .catch((error: NodeJS.ErrnoException) => { + // A readerless FIFO. Reported as the refusal it is, rather than the + // bare "no such device or address" the errno spells out. + if (error.code === "ENXIO") { + throw new Error(`Refusing to download onto a special file: ${absolutePath}`); + } + throw error; + }); + try { + const stat = await handle.stat(); + if (!stat.isFile()) { + throw new Error(`Refusing to download onto a non-regular file: ${absolutePath}`); + } + if (stat.nlink > 1) { + throw new Error( + `Refusing to download onto a file with ${stat.nlink} hard links, which would overwrite its other names: ${absolutePath}`, + ); + } + await handle.truncate(0); + await handle.writeFile(payload); + } finally { + await handle.close(); + } } function createToolResultMessage( @@ -81,8 +231,9 @@ async function executeTool( toolName: string, toolCallId: string, args: Record, + overrideTool?: CursorBridgeTool, ): Promise { - const tool = options.getExecutableTool?.(toolName) ?? options.tools.get(toolName); + const tool = overrideTool ?? options.getExecutableTool?.(toolName) ?? options.tools.get(toolName); if (!tool) { const result = buildToolErrorResult(`Tool "${toolName}" not available`); return createToolResultMessage(toolCallId, toolName, result, true); @@ -133,14 +284,49 @@ async function executeTool( return createToolResultMessage(toolCallId, toolName, result, isError); } +/** + * Resolve the user's policy for a frame that mutates the filesystem directly. + * + * The native `delete` and `read_mcp_resource` download frames both bypass the + * registry, so no approval wrapper sits in front of them. `write` is the tier + * a file creation or removal belongs to. Returns `null` when the call may + * proceed, or the refusal text to answer with. + */ +function refuseByWritePolicy(options: CursorExecBridgeOptions, toolName: string, pathArg: string): string | null { + const context = options.getToolContext?.(); + const settings = context?.settings; + const approvalMode: ApprovalMode = + context?.autoApprove === true ? "yolo" : (settings?.get("tools.approvalMode") ?? "yolo"); + const approval = resolveApproval( + { name: toolName, approval: "write" }, + { path: pathArg }, + approvalMode, + (settings?.get("tools.approval") ?? {}) as Record, + ); + if (approval.policy === "allow") return null; + return approval.policy === "deny" + ? `Tool "${toolName}" is blocked by user policy.` + : `Tool "${toolName}" requires approval, which this channel cannot request.`; +} + async function executeDelete(options: CursorExecBridgeOptions, pathArg: string, toolCallId: string) { const toolName = "delete"; - if (options.allowNativeDelete === false) { + if (options.allowDirectFileMutation === false) { const result = buildToolErrorResult(`Tool "${toolName}" not available`); return createToolResultMessage(toolCallId, toolName, result, true); } + // Unlike every other frame, this one mutates the filesystem directly instead + // of running a registry tool, so no approval wrapper sits in front of it. + // `allowDirectFileMutation` answers "was a mutating tool granted", which is a + // different question from "does the user's policy allow this call" — without + // this, a configured `deny` or an `always-ask` session still lost the file. + const refusal = refuseByWritePolicy(options, toolName, pathArg); + if (refusal) { + return createToolResultMessage(toolCallId, toolName, buildToolErrorResult(refusal), true); + } + options.emitEvent?.({ type: "tool_execution_start", toolCallId, toolName, args: { path: pathArg } }); const absolutePath = resolveToCwd(pathArg, options.getCwd?.() ?? options.cwd); @@ -241,10 +427,21 @@ function buildTodoSyncResult( export class CursorExecHandlers implements ICursorExecHandlers { constructor(private options: CursorExecBridgeOptions) {} + /** + * Modern Cursor builds paginate the legacy `read` frame with + * `offset`/`limit`, exactly as `pi_read` does. Dropping them returns the + * whole file (or its own truncation) for every page, so a model walking a + * large file never advances. Composed with the same helper, so both frames + * translate a range identically. + */ async read(args: Parameters>[0]) { const toolCallId = decodeToolCallId(args.toolCallId); - const toolResultMessage = await executeTool(this.options, "read", toolCallId, { path: args.path }); - return toolResultMessage; + const composed = piReadPath(args.path, args.offset, args.limit); + // A present `limit: 0` asks for zero lines; no selector expresses that. + if (composed === null) { + return createToolResultMessage(toolCallId, "read", { content: [{ type: "text", text: "" }] }, false); + } + return await executeTool(this.options, "read", toolCallId, { path: composed }); } async ls(args: Parameters>[0]) { @@ -254,6 +451,12 @@ export class CursorExecHandlers implements ICursorExecHandlers { return toolResultMessage; } + /** + * Modern Cursor builds paginate this frame with `offset`. The local `grep` + * paginates by file through `skip`, which is the same unit its own + * "use skip=N for the next page" advice counts in — so an unforwarded + * offset re-runs the identical search and returns page one forever. + */ async grep(args: Parameters>[0]) { const toolCallId = decodeToolCallId(args.toolCallId); const searchPath = args.glob ? `${args.path || "."}/${args.glob}` : args.path || "."; @@ -261,6 +464,7 @@ export class CursorExecHandlers implements ICursorExecHandlers { pattern: args.pattern, path: searchPath, case: args.caseInsensitive === true ? false : undefined, + skip: piGrepSkip(args.offset), }); return toolResultMessage; } @@ -401,6 +605,227 @@ export class CursorExecHandlers implements ICursorExecHandlers { }); return toolResultMessage; } + /** + * Modern Cursor CLI Pi tool frames (`ExecServerMessage` 45-51). + * + * These are a separate frame family from the legacy `read`/`shell`/... set, + * not aliases: different args, different result oneofs, and no `tool_call_id` + * (the provider mints one and passes it in `call.toolCallId`). Each maps onto + * the local tool with matching semantics, so the same approval, sandboxing + * and event plumbing applies as for a model-issued call. + */ + /** + * `offset`/`limit` are a 1-indexed start line plus a line count (verified + * against the reference `LocalPiReadExecutor`), which is exactly the local + * `read` tool's `:N+K` inline selector — the tool takes no range kwargs, so + * the range has to be composed onto the path or ranged reads silently + * return the whole file. + */ + async piRead(call: Parameters>[0]) { + const { path: readPath, offset, limit } = call.args; + const composed = piReadPath(readPath, offset, limit); + // A present `limit: 0` asks for zero lines. The reference slices an empty + // string for it; no `read` selector expresses that, so answer directly + // rather than falling back to a whole-file read. + if (composed === null) { + return createToolResultMessage(call.toolCallId, "read", { content: [{ type: "text", text: "" }] }, false); + } + return await executeTool(this.options, "read", call.toolCallId, { path: composed }); + } + + async piBash(call: Parameters>[0]) { + return await executeTool(this.options, "bash", call.toolCallId, { + command: call.args.command, + timeout: piTimeout(call.args.timeout), + }); + } + + /** + * `PiEditExecArgs` is the local `edit` tool's replace mode verbatim: a path + * plus `old_text`/`new_text` pairs. The tool's schema is snake_case, so the + * proto's camelCase accessors are mapped back on the way in. + * + * The replace-mode instance is requested explicitly rather than resolved + * from {@link CursorExecBridgeOptions.tools}: the registry's `edit` is in + * the session's configured mode, whose schema rejects these arguments. + */ + async piEdit(call: Parameters>[0]) { + return await executeTool( + this.options, + "edit", + call.toolCallId, + { + path: call.args.path, + edits: call.args.edits.map(edit => ({ old_text: edit.oldText, new_text: edit.newText })), + }, + this.options.getEditReplaceTool?.(), + ); + } + + async piWrite(call: Parameters>[0]) { + return await executeTool(this.options, "write", call.toolCallId, { + path: call.args.path, + content: call.args.content, + }); + } + + /** + * `literal` makes the pattern a fixed string; the local tool is regex-only, + * so the pattern is escaped on the way in (same translation the legacy pi + * shim does). + * + * `context` and `limit` are not expressible in the model-facing schema — + * context width comes from settings fixed at tool construction — so the + * frame's values are honored by building a per-call `grep` through + * {@link CursorExecBridgeOptions.createGrepTool}. Both are `optional int32`, + * so a present `0` context means "no context lines", not "use the default". + * Without the factory the shared instance runs with session defaults. + */ + async piGrep(call: Parameters>[0]) { + const { pattern, path, glob, ignoreCase, literal, context, limit } = call.args; + const scoped = + context !== undefined || limit !== undefined + ? this.options.createGrepTool?.({ context, totalMatchLimit: piLimit(limit) }) + : undefined; + // Same arg mapping as the legacy `grep` handler: the local tool takes one + // path spec, and its `case` flag is case-SENSITIVITY, the inverse of the + // frame's `ignore_case`. + return await executeTool( + this.options, + "grep", + call.toolCallId, + { + pattern: literal === true ? piEscapeRegexLiteral(pattern) : pattern, + path: glob ? piJoinPath(path, glob) : path || ".", + case: ignoreCase === true ? false : undefined, + }, + scoped, + ); + } + + /** + * `pi_find` is a filename search, which is the local `glob` tool — not + * `grep`. Its `pattern` is a glob, joined onto `path` because `glob` takes a + * single combined path spec. + * + * `limit` is `optional int32`, so `0` is present rather than unset; the + * reference clamps it with `Math.max(1, limit ?? 1000)`, and an unset limit + * leaves the local tool's own default in place. + */ + async piFind(call: Parameters>[0]) { + const { pattern, path, limit } = call.args; + return await executeTool(this.options, "glob", call.toolCallId, { + path: piJoinPath(path, pattern), + limit: piLimit(limit), + }); + } + + /** + * Redirected to `read`, which lists directories — same as the legacy `ls`. + * The frame's entry `limit` is not mapped; see {@link piLsPath}. + */ + async piLs(call: Parameters>[0]) { + return await executeTool(this.options, "read", call.toolCallId, { path: piLsPath(call.args.path) }); + } + + /** + * The resources this client's MCP servers advertise. + * + * Cursor addresses a later read by `server`, so every entry carries the name + * it came from. An absent `server` filter means "all of them". + */ + async listMcpResources({ server }: { server?: string }): Promise { + const mcp = this.options.mcpResources; + if (!mcp) return []; + const names = server ? [server] : mcp.serverNames(); + // Concurrently: each name may block on that server's first catalog load, + // and a slow server should not delay the rest of the listing. + const catalogs = await Promise.all(names.map(async name => [name, await mcp.getServerResources(name)] as const)); + const listed: CursorMcpResource[] = []; + for (const [name, catalog] of catalogs) { + for (const resource of catalog?.resources ?? []) { + listed.push({ + uri: resource.uri, + name: resource.name, + description: resource.description, + mimeType: resource.mimeType, + server: name, + }); + } + } + return listed; + } + + /** + * Read one resource, or `null` when the server or uri is unknown. + * + * MCP returns a list of content items; the wire carries exactly one text or + * blob. Text items are joined, since a multi-part text resource is one + * document; otherwise the first blob stands in. `blob` arrives base64 and + * the wire wants bytes. + * + * A `downloadPath` frame is a different contract: write the bytes to that + * workspace-relative path and answer with the path alone, so a large binary + * lands on disk instead of in the model's context. That makes it a workspace + * mutation reached without a registry tool, so it is gated exactly like the + * native `delete` frame — on the session actually granting a file-writing + * tool, and on the user's `write`-tier policy. The gate runs before the read + * so a refused download never fetches the resource either. + */ + async readMcpResource({ + server, + uri, + downloadPath, + }: { + server: string; + uri: string; + downloadPath?: string; + }): Promise { + if (downloadPath) { + if (this.options.allowDirectFileMutation === false) { + throw new Error('Tool "write" not available: this session cannot download resources to disk.'); + } + const refusal = refuseByWritePolicy(this.options, "write", downloadPath); + if (refusal) throw new Error(refusal); + } + const mcp = this.options.mcpResources; + if (!mcp) return null; + const read = await mcp.readServerResource(server, uri); + if (!read) return null; + // The mime type must describe the bytes actually sent, not whatever item + // happened to be first: an image blob followed by a text note would + // otherwise label the text `image/png` and mislead the model about what + // it is holding. Each branch below takes the type from its own producer. + const textItems = read.contents.filter(item => item.text !== undefined); + const texts = textItems.map(item => item.text as string); + const blobItem = read.contents.find(item => item.blob !== undefined); + const blob = blobItem?.blob; + const textMimeType = textItems[0]?.mimeType; + const blobMimeType = blobItem?.mimeType; + + if (downloadPath) { + // Text resources download as their own bytes; a blob decodes first. + const payload = + texts.length > 0 ? texts.join("\n") : blob !== undefined ? Buffer.from(blob, "base64") : undefined; + if (payload === undefined) return null; + // The path is workspace-relative BY CONTRACT, but it arrives from the + // server, and `resolveToCwd` deliberately honors absolute paths and + // `..` for user-authored tool input. Taking it at its word would let a + // frame write anywhere this process can reach, so confine it here + // rather than trusting the declaration. + const cwd = this.options.getCwd?.() ?? this.options.cwd; + const absolutePath = confineToWorkspace(downloadPath, cwd); + if (!absolutePath) throw new Error(`Refusing to download outside the workspace: ${downloadPath}`); + await writeWithoutFollowingLinks(absolutePath, payload); + // The path echoed back is the one the frame asked for; the model + // addresses it the same relative way. + return { uri, mimeType: texts.length > 0 ? textMimeType : blobMimeType, downloadPath }; + } + + if (texts.length > 0) return { uri, mimeType: textMimeType, text: texts.join("\n") }; + if (blob === undefined) return null; + return { uri, mimeType: blobMimeType, blob: Buffer.from(blob, "base64") }; + } /** * Settle a completed native Cursor todo call, mirroring its list when the @@ -510,4 +935,30 @@ export class CursorExecHandlers implements ICursorExecHandlers { const toolResultMessage = await executeTool(this.options, toolName, toolCallId, args); return toolResultMessage; } + + /** + * Resolve an MCP call's approval without running it. + * + * Same resolution the wrapper applies at execution time, minus the + * execution: an unknown tool is not approvable, and a `prompt` is not an + * approval — the frame has no way to carry an interactive question, and the + * user is asked for real when the call itself arrives. + */ + async mcpApprovalPreflight(call: CursorMcpCall) { + const toolName = call.toolName || call.name; + const tool = this.options.getExecutableTool?.(toolName) ?? this.options.tools.get(toolName); + if (!tool) return false; + const context = this.options.getToolContext?.(); + const settings = context?.settings; + const approvalMode: ApprovalMode = + context?.autoApprove === true ? "yolo" : (settings?.get("tools.approvalMode") ?? "yolo"); + const args = Object.keys(call.args ?? {}).length > 0 ? call.args : decodeMcpArgs(call.rawArgs ?? {}); + const approval = resolveApproval( + tool, + args, + approvalMode, + (settings?.get("tools.approval") ?? {}) as Record, + ); + return approval.policy === "allow"; + } } diff --git a/packages/coding-agent/src/edit/index.ts b/packages/coding-agent/src/edit/index.ts index 33a28c0b7..1320a35a4 100644 --- a/packages/coding-agent/src/edit/index.ts +++ b/packages/coding-agent/src/edit/index.ts @@ -399,14 +399,24 @@ export class EditTool implements AgentTool { readonly #editMode?: EditMode; readonly #deferredDiagnostics: DeferredDiagnostics; - constructor(private readonly session: ToolSession) { + /** + * `mode` pins the edit variant for this instance, for callers whose protocol + * fixes the shape of an edit. The Cursor `pi_edit` frame carries + * `old_text`/`new_text` pairs, which only `replace` accepts — under the + * default `hashline` mode those args do not match the schema at all. Left + * unset, the env/settings resolution applies as before. + */ + constructor( + private readonly session: ToolSession, + mode?: EditMode, + ) { const { PI_EDIT_FUZZY: editFuzzy = "auto", PI_EDIT_FUZZY_THRESHOLD: editFuzzyThreshold = "auto", PI_EDIT_VARIANT: envEditVariant = "auto", } = Bun.env; - this.#editMode = resolveConfiguredEditMode(envEditVariant); + this.#editMode = mode ?? resolveConfiguredEditMode(envEditVariant); this.#allowFuzzy = resolveAllowFuzzy(session, editFuzzy); this.#fuzzyThreshold = resolveFuzzyThreshold(session, editFuzzyThreshold); const deduplicateDiagnostics = diff --git a/packages/coding-agent/src/extensibility/extensions/runner.ts b/packages/coding-agent/src/extensibility/extensions/runner.ts index b6cbda891..4b4354f87 100644 --- a/packages/coding-agent/src/extensibility/extensions/runner.ts +++ b/packages/coding-agent/src/extensibility/extensions/runner.ts @@ -10,6 +10,7 @@ import type { Settings } from "../../config/settings"; import type { LocalProtocolOptions } from "../../internal-urls/local-protocol"; import type { MemoryRuntimeContext } from "../../memory-backend"; import { type Theme, theme } from "../../modes/theme/theme"; +import type { AsyncJobSnapshot } from "../../session/agent-session"; import type { SessionManager } from "../../session/session-manager"; import type { BranchHandler, NavigateTreeHandler, NewSessionHandler } from "../session-handler-types"; import { ManagedTimers } from "./managed-timers"; @@ -40,6 +41,7 @@ import type { ExtensionUIDialogOptions, InputEvent, InputEventResult, + McpNotificationEvent, MessageRenderer, RegisteredCommand, RegisteredTool, @@ -218,6 +220,14 @@ async function raceHandlerWithTimeout( const MAX_PENDING_CREDENTIAL_DISABLED = 32; +/** + * Buffer cap for `mcp_notification` events received before {@link ExtensionRunner.initialize} + * has run. Sized to match the manager-side buffer in `MCPManager.NOTIFICATION_BUFFER_CAP` so + * the two layers can't drop different amounts of the same burst — the pipe drains, or it + * spills, but it does so consistently at both ends. Drop-oldest under pressure. + */ +const MAX_PENDING_MCP_NOTIFICATIONS = 100; + /** * Events handled by the generic emit() method. * Events with dedicated emitXxx() methods are excluded for stronger type safety. @@ -327,6 +337,7 @@ export class ExtensionRunner { #getContextUsageFn: () => ContextUsage | undefined = () => undefined; #compactFn: (instructionsOrOptions?: string | CompactOptions) => Promise = async () => {}; #getSystemPromptFn: () => string[] = () => []; + #getAsyncJobSnapshotFn: () => AsyncJobSnapshot | null = () => null; #newSessionHandler: NewSessionHandler = async () => ({ cancelled: false }); #branchHandler: BranchHandler = async () => ({ cancelled: false }); #navigateTreeHandler: NavigateTreeHandler = async () => ({ cancelled: false }); @@ -345,6 +356,19 @@ export class ExtensionRunner { */ #pendingCredentialDisabled: CredentialDisabledEvent[] = []; + /** + * Buffer for `mcp_notification` events received via {@link emitMcpNotification} before + * {@link initialize} has run. Two-layer race: `MCPManager` also buffers frames until + * its first `addNotificationListener` subscriber attaches, but the sdk.ts bridge is + * registered inside `createAgentSession` — BEFORE the mode controller calls + * `ExtensionRunner.initialize()`. Without this second buffer, the manager's drain + * arrives at the bridge → the bridge calls `emitMcpNotification` → the runner drops + * the frame because `#initialized === false`, and the frame evaporates a second time. + * Bounded at {@link MAX_PENDING_MCP_NOTIFICATIONS}; oldest entries are dropped under + * pressure. Drained in {@link initialize} once the runtime/UI context is wired. + */ + #pendingMcpNotifications: Array> = []; + /** * Timers scheduled by extensions through the sanctioned `ctx.setInterval` / * `ctx.setTimeout` helpers. Callbacks run with the same isolation as handler @@ -392,9 +416,11 @@ export class ExtensionRunner { getMemory?: () => MemoryRuntimeContext | undefined, private readonly settings?: Settings, private readonly localProtocolOptions?: LocalProtocolOptions, + getAsyncJobSnapshot?: () => AsyncJobSnapshot | null, ) { this.#uiContext = noOpUIContext; this.#getMemoryFn = getMemory; + this.#getAsyncJobSnapshotFn = getAsyncJobSnapshot ?? (() => null); } initialize( @@ -457,6 +483,23 @@ export class ExtensionRunner { }); } }); + + // Drain events buffered by emitMcpNotification() before initialize ran, using the + // same deferred-microtask ordering as the credential-disabled drain above so any + // onError listener registered synchronously after initialize() still catches + // handler errors during flush. + const pendingMcp = this.#pendingMcpNotifications.splice(0); + queueMicrotask(() => { + for (const event of pendingMcp) { + this.emit({ type: "mcp_notification", ...event }).catch((error: unknown) => { + logger.warn("mcp_notification handler threw during initialize flush", { + server: event.server, + method: event.method, + error: error instanceof Error ? error.message : String(error), + }); + }); + } + }); } /** @@ -484,6 +527,32 @@ export class ExtensionRunner { await this.emit({ type: "credential_disabled", ...event }); } + /** + * Forward an MCP server notification to extension handlers. + * + * If {@link initialize} has not yet run, the notification is buffered and replayed + * once initialize wires the runtime/UI context. Matches the credential-disabled + * deferral above: the sdk.ts bridge registers `MCPManager.addNotificationListener` + * inside `createAgentSession` — BEFORE the mode controller calls `initialize()` on + * this runner — so notification frames drained by the manager (either fresh + * arrivals or replay from its own startup buffer) can reach us pre-init. Without + * this buffer they would evaporate for a second time here. + * + * Bounded at {@link MAX_PENDING_MCP_NOTIFICATIONS}; oldest entries drop under + * pressure. Never throws; per-handler errors are routed through {@link onError} + * via {@link emit}'s normal isolation. + */ + async emitMcpNotification(event: Omit): Promise { + if (!this.#initialized) { + if (this.#pendingMcpNotifications.length >= MAX_PENDING_MCP_NOTIFICATIONS) { + this.#pendingMcpNotifications.shift(); + } + this.#pendingMcpNotifications.push(event); + return; + } + await this.emit({ type: "mcp_notification", ...event }); + } + /** Emits a session stop pass that can be cancelled with the active settle signal. */ async emitSessionStop(event: Omit): Promise { if (event.signal.aborted) return undefined; @@ -666,6 +735,7 @@ export class ExtensionRunner { ui: this.#uiContext, getContextUsage: () => this.#getContextUsageFn(), compact: instructionsOrOptions => this.#compactFn(instructionsOrOptions), + getAsyncJobSnapshot: () => this.#getAsyncJobSnapshotFn(), hasUI: this.hasUI(), cwd: this.cwd, sessionManager: this.sessionManager, diff --git a/packages/coding-agent/src/extensibility/extensions/types.ts b/packages/coding-agent/src/extensibility/extensions/types.ts index 8bcd246bb..5ef2cb872 100644 --- a/packages/coding-agent/src/extensibility/extensions/types.ts +++ b/packages/coding-agent/src/extensibility/extensions/types.ts @@ -49,6 +49,7 @@ import type { LocalProtocolOptions } from "../../internal-urls/local-protocol"; import type { MemoryRuntimeContext } from "../../memory-backend"; import type { CustomEditor } from "../../modes/components/custom-editor"; import type { Theme } from "../../modes/theme/theme"; +import type { AsyncJobSnapshot } from "../../session/agent-session"; import type { CompactMode } from "../../session/compact-modes"; import type { CustomMessage, CustomMessagePayload } from "../../session/messages"; import type { ReadonlySessionManager, SessionManager } from "../../session/session-manager"; @@ -415,6 +416,8 @@ export interface ExtensionContext { ui: ExtensionUIContext; /** Get current context usage for the active model. */ getContextUsage(): ContextUsage | undefined; + /** Get a read-only snapshot of async jobs owned by this session. */ + getAsyncJobSnapshot(): AsyncJobSnapshot | null; /** Compact the session context (interactive mode shows UI). */ compact(instructionsOrOptions?: string | CompactOptions): Promise; /** Whether UI is available (false in print/RPC mode) */ @@ -713,6 +716,31 @@ export interface CredentialDisabledEvent { disabledCause: string; } +// ============================================================================ +// MCP Events +// ============================================================================ + +/** + * Fired for every JSON-RPC notification received from a connected MCP server, + * AFTER the runtime's own handling of known list/update methods. Unknown or + * server-custom methods are delivered too — extensions can bridge them into + * session behavior by inspecting `method`/`params` and injecting a follow-up + * via `pi.sendMessage(..., { deliverAs })` or `pi.sendUserMessage(...)`. + */ +export interface McpNotificationEvent { + type: "mcp_notification"; + /** + * Server name as declared in the MCP config (raw, unsanitized). Note this + * differs from the sanitized prefix used in `mcp___` + * tool names — filter by this raw name, not by tool-name prefix matching. + */ + server: string; + /** JSON-RPC method (e.g. `notifications/tools/list_changed`, or server-custom). */ + method: string; + /** JSON-RPC params, opaque to the runtime. */ + params: unknown; +} + // ============================================================================ // User Bash Events // ============================================================================ @@ -941,6 +969,7 @@ export type ExtensionEvent = | TodoReminderEvent | GoalUpdatedEvent | CredentialDisabledEvent + | McpNotificationEvent | UserBashEvent | UserPythonEvent | InputEvent @@ -1133,6 +1162,7 @@ export interface ExtensionAPI { on(event: "tool_result", handler: ExtensionHandler): void; on(event: "user_bash", handler: ExtensionHandler): void; on(event: "user_python", handler: ExtensionHandler): void; + on(event: "mcp_notification", handler: ExtensionHandler): void; // ========================================================================= // Tool Registration diff --git a/packages/coding-agent/src/extensibility/legacy-pi-coding-agent-shim.ts b/packages/coding-agent/src/extensibility/legacy-pi-coding-agent-shim.ts index 44a18115b..36e755eab 100644 --- a/packages/coding-agent/src/extensibility/legacy-pi-coding-agent-shim.ts +++ b/packages/coding-agent/src/extensibility/legacy-pi-coding-agent-shim.ts @@ -17,6 +17,7 @@ import * as fs from "node:fs"; import * as path from "node:path"; import type { AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core"; import { type AuthCredential, SqliteAuthCredentialStore, type TSchema } from "@oh-my-pi/pi-ai"; +import { piEscapeRegexLiteral, piJoinPath } from "@oh-my-pi/pi-ai/providers/cursor-pi-args"; import { getKeybindings, type Keybinding, Text } from "@oh-my-pi/pi-tui"; import { getAgentDbPath, @@ -298,16 +299,6 @@ function lineRangePath(readPath: string, offset: number | undefined, limit: numb return `${readPath}:${start}-${end}`; } -function escapeRegexLiteral(value: string): string { - return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); -} - -function joinLegacyGlob(searchPath: string, pattern: string): string { - if (path.isAbsolute(pattern)) return pattern; - if (!searchPath || searchPath === ".") return pattern; - return path.join(searchPath, pattern); -} - function normalizeLegacyLimit(limit: number | undefined, fallback: number): number { if (limit === undefined || !Number.isFinite(limit)) return fallback; return Math.max(1, Math.floor(limit)); @@ -512,7 +503,7 @@ export function createGrepToolDefinition(cwd: string, options?: GrepToolOptions) renderResult: legacyRenderResult, execute: (toolCallId, params, signal, onUpdate) => { const rawPattern = stringField(params, "pattern") ?? ""; - const pattern = booleanField(params, "literal") ? escapeRegexLiteral(rawPattern) : rawPattern; + const pattern = booleanField(params, "literal") ? piEscapeRegexLiteral(rawPattern) : rawPattern; const searchPath = stringField(params, "path") ?? "."; const glob = stringField(params, "glob"); const context = numberField(params, "context"); @@ -529,7 +520,7 @@ export function createGrepToolDefinition(cwd: string, options?: GrepToolOptions) toolCallId, { pattern, - path: glob ? joinLegacyGlob(searchPath, glob) : searchPath, + path: glob ? piJoinPath(searchPath, glob) : searchPath, case: booleanField(params, "ignoreCase") ? false : undefined, }, signal, @@ -587,7 +578,7 @@ export function createFindToolDefinition(cwd: string, options?: FindToolOptions) } return tool.execute( toolCallId, - { path: joinLegacyGlob(searchPath, pattern), hidden: true, gitignore: true, limit }, + { path: piJoinPath(searchPath, pattern), hidden: true, gitignore: true, limit }, signal, onUpdate, ); diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index 7b344e040..27221afe6 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -41,7 +41,7 @@ import { type ScopedModel, } from "./config/model-resolver"; import { ModelsConfigFile } from "./config/models-config"; -import { getDefault, type SettingPath, Settings, settings } from "./config/settings"; +import { getDefault, type SettingPath, Settings, type SettingValue, settings } from "./config/settings"; import { initializeWithSettings } from "./discovery"; import { clearPluginRootsAndCaches, @@ -90,14 +90,7 @@ import { createPersistedSubagentReviverFactory } from "./task/persisted-revive"; import { createTelemetryExportConfig, initTelemetryExport, isTelemetryExportEnabled } from "./telemetry-export"; import { concreteThinkingLevel, parseConfiguredThinkingLevel } from "./thinking"; import type { LspStartupServerInfo } from "./tools"; -import { - getChangelogPath, - parseChangelog, - parseChangelogVersion, - readLastChangelogVersion, - selectStartupChangelog, - writeLastChangelogVersion, -} from "./utils/changelog"; +import { getChangelogPath, resolveStartupChangelogForDisplay, type StartupChangelogSelection } from "./utils/changelog"; import { EventBus } from "./utils/event-bus"; import { withTimeoutSignal } from "./utils/fetch-timeout"; @@ -415,7 +408,7 @@ export function createAcpSessionFactory(args: AcpSessionFactoryOptions): AcpSess async function runInteractiveMode( session: AgentSession, version: string, - changelogMarkdown: string | undefined, + startupChangelog: StartupChangelogSelection | undefined, notifs: (InteractiveModeNotify | null)[], versionCheckPromise: Promise, initialMessages: string[], @@ -433,7 +426,7 @@ async function runInteractiveMode( const mode = new InteractiveMode( session, version, - changelogMarkdown, + startupChangelog, setExtensionUIContext, lspServers, mcpManager, @@ -679,33 +672,19 @@ async function resolveScopedModels( ); } -async function getChangelogForDisplay(parsed: Args): Promise { +async function getChangelogForDisplay( + parsed: Args, + mode: SettingValue<"startup.changelogMode">, +): Promise { if (parsed.continue || parsed.resume || isForeignSessionImport(parsed)) { return undefined; } - const lastVersion = await readLastChangelogVersion(); - const parsedLastVersion = parseChangelogVersion(lastVersion); - if (!parsedLastVersion) { - await writeLastChangelogVersion(VERSION); - return undefined; - } - if (lastVersion === VERSION) { - // Steady state: user already saw the current version's changelog. Skip the file read + parse. - return undefined; - } - - const changelogPath = getChangelogPath(); - const entries = await parseChangelog(changelogPath); - const startupChangelog = selectStartupChangelog(entries, lastVersion, VERSION); - if (startupChangelog.persistCurrentVersion) { - await writeLastChangelogVersion(VERSION); - } - if (startupChangelog.markdown) { - return startupChangelog.markdown; - } - - return undefined; + return resolveStartupChangelogForDisplay({ + mode, + currentVersion: VERSION, + changelogPath: getChangelogPath(), + }); } const SESSION_ID_ARG_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; @@ -1667,7 +1646,12 @@ export async function runRootCommand( await runRpcMode(session, mode === "rpc-ui" ? setToolUIContext : undefined, eventBus, rpcInput); } else if (isInteractive) { const versionCheckPromise = checkForNewVersion(VERSION).catch(() => undefined); - const changelogMarkdown = await logger.time("main:getChangelogForDisplay", getChangelogForDisplay, parsedArgs); + const startupChangelog = await logger.time( + "main:getChangelogForDisplay", + getChangelogForDisplay, + parsedArgs, + settingsInstance.get("startup.changelogMode"), + ); const modelScopeNotification = buildModelScopeNotification( scopedModels, @@ -1692,7 +1676,7 @@ export async function runRootCommand( await runInteractiveMode( session, VERSION, - changelogMarkdown, + startupChangelog, notifs, versionCheckPromise, initialArgs.messages, diff --git a/packages/coding-agent/src/mcp/manager.ts b/packages/coding-agent/src/mcp/manager.ts index 8316bbb00..33e5f476d 100644 --- a/packages/coding-agent/src/mcp/manager.ts +++ b/packages/coding-agent/src/mcp/manager.ts @@ -94,6 +94,14 @@ const STARTUP_TIMEOUT_MS = 250; const RECONNECT_BURST_WINDOW_MS = 30_000; const RECONNECT_BURST_LIMIT = 5; +/** + * Bounded buffer for notifications received before any listener attaches. + * Mirrors {@link IrcBus}'s `MAILBOX_CAP` — drop-oldest on overflow. Drained + * into the first {@link MCPManager.addNotificationListener} subscriber, then + * cleared; subsequent frames deliver directly to attached listeners. + */ +const NOTIFICATION_BUFFER_CAP = 100; + function trackPromise(promise: Promise): TrackedPromise { const tracked: TrackedPromise = { promise, status: "pending" }; promise.then( @@ -194,8 +202,14 @@ export class MCPManager { #sources = new Map(); #authStorage: AuthStorage | null = null; #authHandler?: MCPAuthHandler; - #onNotification?: (serverName: string, method: string, params: unknown) => void; - #onToolsChanged?: (tools: CustomTool[]) => void; + #notificationListeners = new Set<(serverName: string, method: string, params: unknown) => void>(); + /** + * Notifications received before any listener attached, to be drained on + * the first {@link addNotificationListener} call. Bounded by + * {@link NOTIFICATION_BUFFER_CAP}, drop-oldest on overflow. + */ + #pendingNotifications: Array<{ server: string; method: string; params: unknown }> = []; + #onToolsChanged?: (tools: CustomTool[]) => void | Promise; #onResourcesChanged?: (serverName: string, uri: string) => void; #onPromptsChanged?: (serverName: string) => void; #notificationsEnabled = false; @@ -219,16 +233,66 @@ export class MCPManager { ) {} /** - * Set a callback to receive all server notifications. + * Register a listener for server-initiated MCP notifications. + * + * The listener is called for every JSON-RPC notification received from any + * connected server, AFTER the manager's own handling of known methods + * (`notifications/tools/list_changed`, `notifications/resources/list_changed`, + * `notifications/resources/updated`, `notifications/prompts/list_changed`). + * For list-change methods the internal refresh promise is awaited before + * fanout, so listeners observe up-to-date manager and tool state. Unknown + * or server-custom methods are also delivered, letting consumers bridge + * server-initiated events into session-level behavior (e.g. an extension + * injecting a steer via `pi.sendMessage`). + * + * Notifications received before any listener attached are buffered + * (bounded FIFO, cap {@link NOTIFICATION_BUFFER_CAP}, drop-oldest) and + * drained into the first subscriber — matches {@link setOnPromptsChanged}'s + * replay-on-attach and {@link IrcBus}'s mailbox semantics. + * + * Returns an unsubscribe function; call it to remove the listener. + * + * Multiple listeners are allowed; each is invoked with independent error + * isolation — a listener that throws does not prevent other listeners from + * firing. */ - setOnNotification(handler: (serverName: string, method: string, params: unknown) => void): void { - this.#onNotification = handler; + addNotificationListener(listener: (serverName: string, method: string, params: unknown) => void): () => void { + const wasEmpty = this.#notificationListeners.size === 0; + this.#notificationListeners.add(listener); + + // Drain startup-buffered notifications into the first attaching listener. + if (wasEmpty && this.#pendingNotifications.length > 0) { + const pending = this.#pendingNotifications.splice(0); + for (const frame of pending) { + try { + listener(frame.server, frame.method, frame.params); + } catch (error) { + logger.debug("MCP notification listener threw during buffered drain", { + path: `mcp:${frame.server}`, + method: frame.method, + error, + }); + } + } + } + + return () => { + this.#notificationListeners.delete(listener); + }; } /** * Set a callback to fire when any server's tools change. + * + * May return a Promise; if so, {@link refreshServerTools} awaits it so that + * downstream consumers (e.g. `mcp_notification` listeners for + * `notifications/tools/list_changed`) observe not just the manager's + * refreshed tool set but also any session-level rebind driven by the + * handler (`session.refreshMCPTools`). Other callsites (initial connect, + * disconnect, reconnect) invoke the handler synchronously — their downstream + * chains don't need to serialize on the rebind. */ - setOnToolsChanged(handler: (tools: CustomTool[]) => void): void { + setOnToolsChanged(handler: (tools: CustomTool[]) => void | Promise): void { this.#onToolsChanged = handler; } @@ -488,7 +552,7 @@ export class MCPManager { this.reconnectServer(name, options); const customTools = MCPTool.fromTools(connection, serverTools, reconnect); this.#replaceServerTools(name, customTools); - this.#onToolsChanged?.(this.#tools); + void this.#onToolsChanged?.(this.#tools); void this.toolCache?.set(name, config, serverTools); onStatus?.({ type: "connected", serverName: name }); @@ -597,7 +661,7 @@ export class MCPManager { sortMCPToolsByName(this.#tools); } - #triggerNotificationRefresh(serverName: string, kind: "tools" | "resources" | "prompts"): void { + #triggerNotificationRefresh(serverName: string, kind: "tools" | "resources" | "prompts"): Promise { const refresh = (() => { switch (kind) { case "tools": @@ -608,22 +672,33 @@ export class MCPManager { return this.refreshServerPrompts(serverName); } })(); - void refresh.catch(error => { + return refresh.catch(error => { logger.debug("Failed MCP notification refresh", { path: `mcp:${serverName}`, kind, error }); }); } - #handleServerNotification(serverName: string, method: string, params: unknown): void { + async #handleServerNotification(serverName: string, method: string, params: unknown): Promise { logger.debug("MCP notification received", { path: `mcp:${serverName}`, method }); + // Only trigger refresh if the connection is already stored — during the + // initial connect handshake, notifications may arrive before + // `#connections.set()` completes, and `refreshServer*` would no-op + // anyway. Skipping the await in that case preserves arrival order + // across concurrently-dispatched notifications (an awaited refresh, + // even a no-op, yields a microtask that lets later frames overtake). + const connectionKnown = this.#connections.has(serverName); + let refreshPromise: Promise | undefined; switch (method) { case MCPNotificationMethods.TOOLS_LIST_CHANGED: - this.#triggerNotificationRefresh(serverName, "tools"); + if (connectionKnown) refreshPromise = this.#triggerNotificationRefresh(serverName, "tools"); break; case MCPNotificationMethods.RESOURCES_LIST_CHANGED: - this.#triggerNotificationRefresh(serverName, "resources"); + if (connectionKnown) refreshPromise = this.#triggerNotificationRefresh(serverName, "resources"); break; case MCPNotificationMethods.RESOURCES_UPDATED: { - const uri = (params as { uri?: string })?.uri; + const uri = + params && typeof params === "object" && "uri" in params && typeof params.uri === "string" + ? params.uri + : undefined; const subscribed = this.#subscribedResources.get(serverName); if (uri && subscribed?.has(uri)) { this.#onResourcesChanged?.(serverName, uri); @@ -631,13 +706,40 @@ export class MCPManager { break; } case MCPNotificationMethods.PROMPTS_LIST_CHANGED: - this.#triggerNotificationRefresh(serverName, "prompts"); + if (connectionKnown) refreshPromise = this.#triggerNotificationRefresh(serverName, "prompts"); break; default: break; } - this.#onNotification?.(serverName, method, params); + // Await internal refresh so listeners see the manager's post-refresh + // state (satisfies the documented "AFTER the manager's own handling" + // contract on `addNotificationListener` — otherwise an extension acting + // on `tools/list_changed` could hit stale `getTools()`). + if (refreshPromise) { + await refreshPromise; + } + + // Buffer for late-attaching subscribers when no listener exists yet. + if (this.#notificationListeners.size === 0) { + this.#pendingNotifications.push({ server: serverName, method, params }); + if (this.#pendingNotifications.length > NOTIFICATION_BUFFER_CAP) { + this.#pendingNotifications.shift(); + } + return; + } + + for (const listener of this.#notificationListeners) { + try { + listener(serverName, method, params); + } catch (error) { + logger.debug("MCP notification listener threw", { + path: `mcp:${serverName}`, + method, + error, + }); + } + } } /** Handle server-to-client JSON-RPC requests (e.g. ping, roots/list). */ @@ -781,7 +883,7 @@ export class MCPManager { // Remove tools from this server and notify consumers const hadTools = this.#tools.some(t => t.mcpServerName === name); this.#tools = this.#tools.filter(t => t.mcpServerName !== name); - if (hadTools) this.#onToolsChanged?.(this.#tools); + if (hadTools) void this.#onToolsChanged?.(this.#tools); // Notify prompt consumers so stale commands are cleared if (connection?.prompts?.length) this.#onPromptsChanged?.(name); @@ -1022,7 +1124,7 @@ export class MCPManager { const customTools = MCPTool.fromTools(connection, serverTools, reconnect); void this.toolCache?.set(name, config, serverTools); this.#replaceServerTools(name, customTools); - this.#onToolsChanged?.(this.#tools); + void this.#onToolsChanged?.(this.#tools); void this.#loadServerResourcesAndPrompts(name, connection); return connection; } catch (error) { @@ -1075,7 +1177,7 @@ export class MCPManager { // Replace tools from this server this.#replaceServerTools(name, customTools); - this.#onToolsChanged?.(this.#tools); + await this.#onToolsChanged?.(this.#tools); } /** diff --git a/packages/coding-agent/src/modes/acp/acp-agent.ts b/packages/coding-agent/src/modes/acp/acp-agent.ts index 7dc744065..72777f90d 100644 --- a/packages/coding-agent/src/modes/acp/acp-agent.ts +++ b/packages/coding-agent/src/modes/acp/acp-agent.ts @@ -1581,7 +1581,7 @@ export class AcpAgent implements Agent { #buildThinkingOptions(session: AgentSession): Array<{ value: string; name: string; description?: string }> { return [ { value: THINKING_OFF, name: "Off" }, - { value: AUTO_THINKING, name: "Auto", description: "Auto-detect per prompt (low–xhigh)" }, + { value: AUTO_THINKING, name: "Auto", description: "Auto-detect per prompt" }, ...session.getAvailableThinkingLevels().map(level => ({ value: level, name: level, diff --git a/packages/coding-agent/src/modes/components/codex-reset-fireworks.ts b/packages/coding-agent/src/modes/components/codex-reset-fireworks.ts new file mode 100644 index 000000000..10d7018c0 --- /dev/null +++ b/packages/coding-agent/src/modes/components/codex-reset-fireworks.ts @@ -0,0 +1,369 @@ +import { + type Component, + getSegmenter, + matchesKey, + type OverlayHandle, + type OverlayOptions, + truncateToWidth, + visibleWidth, +} from "@oh-my-pi/pi-tui"; +import { type ThemeColor, theme } from "../theme/theme"; + +const FRAME_INTERVAL_MS = 85; +const FRAME_COUNT = 34; + +const FIREWORK_THEME_COLORS = { + cyan: "mdLink", + dim: "dim", + gold: "warning", + green: "success", + pink: "accent", + violet: "thinkingXhigh", + white: "text", +} as const satisfies Record; + +type FireworkColor = keyof typeof FIREWORK_THEME_COLORS; + +/** The active Codex account fields retained between status refreshes. */ +export interface CodexResetUsageSnapshot { + /** When this usage report was observed, if supplied by the provider. */ + observedAt?: number; + /** Weekly usage, its quota identity, and its previously scheduled reset deadline. */ + sevenDay?: { percent: number; resetsAt?: number; tier?: string; plan?: string }; + savedResets?: number; +} + +/** A detected Codex quota event that can trigger the fireworks presentation. */ +export type CodexResetFireworksEvent = + | { kind: "unscheduled-weekly-reset" } + | { kind: "saved-reset-banked"; added: number; available: number }; + +interface CanvasCell { + glyph: string; + color: FireworkColor; + priority: number; +} + +interface FireworkBurst { + x: number; + y: number; + start: number; + color: FireworkColor; +} + +interface ActiveFireworks { + component: CodexResetFireworksComponent; + overlay: OverlayHandle; +} + +interface CodexResetFireworksHost { + ui: { + showOverlay(component: Component, options?: OverlayOptions): OverlayHandle; + setFocus(component: Component): void; + requestRender(): void; + readonly terminal: { readonly rows: number }; + }; +} + +const BURSTS: readonly FireworkBurst[] = [ + { x: 0.17, y: 0.46, start: 5, color: "pink" }, + { x: 0.48, y: 0.2, start: 9, color: "cyan" }, + { x: 0.78, y: 0.42, start: 13, color: "gold" }, + { x: 0.31, y: 0.24, start: 17, color: "violet" }, + { x: 0.65, y: 0.28, start: 21, color: "green" }, + { x: 0.88, y: 0.18, start: 25, color: "pink" }, +]; + +/** + * Compare consecutive reports for one Codex account. A saved-reset grant takes + * precedence when both changes arrive in the same report. A verified decrease, + * or a prior positive balance becoming unavailable, suppresses the weekly event + * because the user may have redeemed a credit. Other weekly usage drops are + * celebrated only before the previously scheduled reset deadline. + */ +export function detectCodexResetFireworks( + previous: CodexResetUsageSnapshot, + current: CodexResetUsageSnapshot, +): CodexResetFireworksEvent | undefined { + const previousSavedResets = previous.savedResets; + const currentSavedResets = current.savedResets; + if (previousSavedResets !== undefined) { + if (currentSavedResets === undefined) { + if (previousSavedResets > 0) return undefined; + } else { + if (currentSavedResets > previousSavedResets) { + return { + kind: "saved-reset-banked", + added: currentSavedResets - previousSavedResets, + available: currentSavedResets, + }; + } + if (currentSavedResets < previousSavedResets) return undefined; + } + } + + if (!previous.sevenDay || !current.sevenDay) return undefined; + if (previous.sevenDay.tier !== current.sevenDay.tier || previous.sevenDay.plan !== current.sevenDay.plan) { + return undefined; + } + const previousWeeklyPercent = Math.round(Math.max(0, Math.min(100, previous.sevenDay.percent))); + const currentWeeklyPercent = Math.round(Math.max(0, Math.min(100, current.sevenDay.percent))); + if (previousWeeklyPercent === 0 || currentWeeklyPercent >= previousWeeklyPercent) return undefined; + + const scheduledResetAt = previous.sevenDay.resetsAt; + if ( + scheduledResetAt === undefined || + !Number.isFinite(scheduledResetAt) || + typeof current.observedAt !== "number" || + !Number.isFinite(current.observedAt) || + current.observedAt >= scheduledResetAt + ) { + return undefined; + } + return { kind: "unscheduled-weekly-reset" }; +} + +function setCell( + canvas: Array>, + x: number, + y: number, + glyph: string, + color: FireworkColor, + priority: number, +): void { + const row = canvas[y]; + if (!row || x < 0 || x >= row.length) return; + const current = row[x]; + if (!current || priority >= current.priority) row[x] = { glyph, color, priority }; +} + +function drawText( + canvas: Array>, + x: number, + y: number, + text: string, + color: FireworkColor, + priority: number, +): void { + let column = x; + for (const { segment } of getSegmenter().segment(text)) { + const width = visibleWidth(segment); + if (width <= 0) continue; + setCell(canvas, column, y, segment, color, priority); + for (let continuation = 1; continuation < width; continuation++) { + setCell(canvas, column + continuation, y, "", color, priority); + } + column += width; + } +} + +function drawBanner( + canvas: Array>, + left: number, + artWidth: number, + height: number, + event: CodexResetFireworksEvent, +): void { + if (height < 3 || artWidth < 8) return; + const panelWidth = Math.min(62, artWidth); + const panelLeft = left + Math.floor((artWidth - panelWidth) / 2); + const top = height - 3; + const innerWidth = panelWidth - 2; + const titleText = + event.kind === "unscheduled-weekly-reset" ? " O P E N A I R E S E T " : " S A V E D R E S E T "; + const subtitleText = + event.kind === "unscheduled-weekly-reset" + ? "Weekly usage cleared early · ESC to return" + : event.added === 1 + ? `New reset banked · ${event.available} available · ESC to return` + : `${event.added} resets banked · ${event.available} available · ESC to return`; + const title = truncateToWidth(titleText, innerWidth, ""); + const subtitle = truncateToWidth(subtitleText, innerWidth, ""); + const titleOffset = Math.floor((innerWidth - visibleWidth(title)) / 2); + const subtitleOffset = Math.floor((innerWidth - visibleWidth(subtitle)) / 2); + + drawText(canvas, panelLeft, top, `╭${"─".repeat(innerWidth)}╮`, "violet", 20); + drawText(canvas, panelLeft + 1 + titleOffset, top, title, "gold", 21); + drawText(canvas, panelLeft, top + 1, `│${" ".repeat(innerWidth)}│`, "violet", 20); + drawText(canvas, panelLeft + 1 + subtitleOffset, top + 1, subtitle, "cyan", 21); + drawText(canvas, panelLeft, top + 2, `╰${"─".repeat(innerWidth)}╯`, "violet", 20); +} + +function drawStars( + canvas: Array>, + left: number, + artWidth: number, + skyHeight: number, + frame: number, +): void { + if (skyHeight <= 0) return; + const count = Math.min(26, Math.max(5, Math.floor(artWidth / 3))); + for (let index = 0; index < count; index++) { + const x = left + ((index * 37 + 11) % artWidth); + const y = (index * 7 + 2) % skyHeight; + const bright = (index + Math.floor(frame / 3)) % 5 === 0; + setCell(canvas, x, y, bright ? "+" : ".", bright ? "white" : "dim", bright ? 2 : 1); + } +} + +function drawBurst( + canvas: Array>, + burst: FireworkBurst, + left: number, + artWidth: number, + skyHeight: number, + frame: number, +): void { + if (skyHeight <= 1) return; + const centerX = left + Math.round((artWidth - 1) * burst.x); + const centerY = Math.max(0, Math.min(skyHeight - 2, Math.round((skyHeight - 1) * burst.y))); + const age = frame - burst.start; + + if (age >= -6 && age < 0) { + const progress = (age + 6) / 6; + const y = skyHeight - 1 - Math.round(progress * (skyHeight - 1 - centerY)); + setCell(canvas, centerX, y, "^", "white", 8); + setCell(canvas, centerX, y + 1, "|", burst.color, 7); + setCell(canvas, centerX, y + 2, ".", "gold", 6); + return; + } + if (age < 0 || age > 8) return; + + const radius = age === 0 ? 0 : 0.8 + age * 0.92; + const gravity = Math.floor((age * age) / 22); + const glyphs = ["@", "*", "*", "+", "o", "o", ".", ".", "."] as const; + const particleColor: FireworkColor = age <= 5 ? burst.color : age <= 7 ? "gold" : "dim"; + + for (let particle = 0; particle < 20; particle++) { + const angle = (particle / 20) * Math.PI * 2 + burst.start * 0.17; + const x = centerX + Math.round(Math.cos(angle) * radius * 1.75); + const y = centerY + Math.round(Math.sin(angle) * radius * 0.58 + gravity); + setCell(canvas, x, y, glyphs[age], particleColor, 10); + if (age >= 2 && age <= 6) { + const trailRadius = Math.max(0, radius - 1.4); + const trailX = centerX + Math.round(Math.cos(angle) * trailRadius * 1.75); + const trailY = centerY + Math.round(Math.sin(angle) * trailRadius * 0.58 + gravity); + setCell(canvas, trailX, trailY, ".", "dim", 5); + } + } + if (age <= 2) setCell(canvas, centerX, centerY, age === 0 ? "@" : "+", "white", 12); +} + +function renderCanvas(canvas: Array>): string[] { + return canvas.map(row => { + let output = ""; + let run = ""; + let runColor: FireworkColor | undefined; + for (const cell of row) { + if (cell && cell.color !== runColor) { + if (run) output += runColor ? theme.fg(FIREWORK_THEME_COLORS[runColor], run) : run; + run = ""; + runColor = cell.color; + } + run += cell?.glyph ?? " "; + } + if (run) output += runColor ? theme.fg(FIREWORK_THEME_COLORS[runColor], run) : run; + return output; + }); +} + +/** Render one deterministic animation frame for the top-third overlay. */ +function renderCodexResetFireworks( + width: number, + height: number, + frame: number, + event: CodexResetFireworksEvent, +): string[] { + const safeWidth = Math.max(1, Math.floor(width)); + const safeHeight = Math.max(1, Math.floor(height)); + const artWidth = Math.min(96, safeWidth); + const left = Math.floor((safeWidth - artWidth) / 2); + const skyHeight = Math.max(0, safeHeight - 3); + const canvas = Array.from({ length: safeHeight }, () => new Array(safeWidth)); + + drawStars(canvas, left, artWidth, skyHeight, frame); + for (const burst of BURSTS) drawBurst(canvas, burst, left, artWidth, skyHeight, frame); + drawBanner(canvas, left, artWidth, safeHeight, event); + return renderCanvas(canvas); +} + +class CodexResetFireworksComponent implements Component { + #timer: NodeJS.Timeout | undefined; + #done = Promise.withResolvers(); + #frame = 0; + #disposed = false; + + constructor( + readonly host: CodexResetFireworksHost, + readonly event: CodexResetFireworksEvent, + ) {} + + run(): Promise { + this.#timer ??= setInterval(() => { + if (this.#disposed) return; + this.#frame = (this.#frame + 1) % FRAME_COUNT; + this.host.ui.requestRender(); + }, FRAME_INTERVAL_MS); + this.host.ui.requestRender(); + return this.#done.promise; + } + + dispose(): void { + if (this.#disposed) return; + this.#disposed = true; + if (this.#timer) { + clearInterval(this.#timer); + this.#timer = undefined; + } + this.#done.resolve(); + } + + handleInput(data: string): void { + if (matchesKey(data, "escape") || matchesKey(data, "esc")) this.#done.resolve(); + } + + render(width: number): readonly string[] { + const height = Math.max(1, Math.floor(this.host.ui.terminal.rows * 0.33)); + return renderCodexResetFireworks(width, height, this.#frame, this.event); + } +} + +/** Owns the at-most-one modal celebration lifecycle for an interactive session. */ +export class CodexResetFireworksController { + #active: ActiveFireworks | undefined; + + constructor(private readonly host: CodexResetFireworksHost) {} + + /** Present a celebration unless another one already owns the modal overlay. */ + show(event: CodexResetFireworksEvent): boolean { + if (this.#active) return false; + const component = new CodexResetFireworksComponent(this.host, event); + const overlay = this.host.ui.showOverlay(component, { + anchor: "top-center", + width: "100%", + maxHeight: "33%", + margin: 0, + }); + this.#active = { component, overlay }; + this.host.ui.setFocus(component); + void component.run().then(() => this.#finish(component)); + return true; + } + + /** Stop the active celebration and release its overlay, if present. */ + dispose(): void { + const active = this.#active; + if (!active) return; + this.#active = undefined; + active.component.dispose(); + active.overlay.hide(); + } + + #finish(component: CodexResetFireworksComponent): void { + const active = this.#active; + if (!active || active.component !== component) return; + this.#active = undefined; + component.dispose(); + active.overlay.hide(); + } +} diff --git a/packages/coding-agent/src/modes/components/status-line/component.ts b/packages/coding-agent/src/modes/components/status-line/component.ts index 34145ea17..e14cbae7f 100644 --- a/packages/coding-agent/src/modes/components/status-line/component.ts +++ b/packages/coding-agent/src/modes/components/status-line/component.ts @@ -15,6 +15,11 @@ import { getSessionAccentAnsi, getSessionAccentHex } from "../../../utils/sessio import { calculateTokensPerSecond } from "../../../utils/token-rate"; import { sanitizeStatusText } from "../../shared"; import { theme } from "../../theme/theme"; +import { + type CodexResetFireworksEvent, + type CodexResetUsageSnapshot, + detectCodexResetFireworks, +} from "../codex-reset-fireworks"; import { canReuseCachedPr, createPrCacheContext, isSamePrCacheContext, type PrCacheContext } from "./git-utils"; import { getPreset } from "./presets"; import { renderSegment, type SegmentContext } from "./segments"; @@ -30,6 +35,36 @@ import type { const JJ_REFRESH_TTL_MS = 5000; const WATCHER_FAILURE_POLL_TTL_MS = 5000; +function normalizeCodexIdentityValue(value: unknown): string | undefined { + return typeof value === "string" && value.trim() ? value.trim().toLowerCase() : undefined; +} + +/** + * Fireworks are stateful, so their report match must be stricter than the + * status display's fallback matching: every known credential identifier must + * be present and equal or a workspace sibling can mutate this account's + * baseline. + */ +function codexReportMatchesExactIdentity(report: UsageReport, identity: OAuthAccountIdentity | undefined): boolean { + if (!identity) return false; + const accountId = normalizeCodexIdentityValue(identity.accountId); + const email = normalizeCodexIdentityValue(identity.email); + const projectId = normalizeCodexIdentityValue(identity.projectId); + const orgId = normalizeCodexIdentityValue(identity.orgId); + if (!accountId && !email && !projectId && !orgId) return false; + + const metadata = report.metadata ?? {}; + const reportAccountId = + normalizeCodexIdentityValue(metadata.accountId) ?? normalizeCodexIdentityValue(metadata.account_id); + const reportProjectId = + normalizeCodexIdentityValue(metadata.projectId) ?? normalizeCodexIdentityValue(metadata.project_id); + if (accountId && reportAccountId !== accountId) return false; + if (email && normalizeCodexIdentityValue(metadata.email) !== email) return false; + if (projectId && reportProjectId !== projectId) return false; + if (orgId && normalizeCodexIdentityValue(metadata.orgId) !== orgId) return false; + return true; +} + // ═══════════════════════════════════════════════════════════════════════════ // Context-usage memo // ═══════════════════════════════════════════════════════════════════════════ @@ -367,6 +402,12 @@ export class StatusLineComponent implements Component { #usageFetchedAt = 0; #usageInFlight = false; #usageStartTimer: Timer | null = null; + // A timed-out request may still resolve. Its result remains eligible only + // until a newer request has applied. + #usageRefreshSequence = 0; + #latestAppliedUsageRefreshSequence = 0; + #codexResetSnapshots = new Map(); + #onCodexResetFireworks: ((event: CodexResetFireworksEvent) => void) | undefined; // Context-usage memo. The status line redraws on every agent event, so the // hot path must not recompute context tokens unless an input changed. // `getContextUsage()` anchors on the last assistant's real prompt-token @@ -585,6 +626,11 @@ export class StatusLineComponent implements Component { this.#collabStatus = status; } + /** Set the callback that presents detected Codex reset celebrations, or clear it with `undefined`. */ + setCodexResetFireworksHandler(handler: ((event: CodexResetFireworksEvent) => void) | undefined): void { + this.#onCodexResetFireworks = handler; + } + setHookStatus(key: string, text: string | undefined): void { if (text === undefined) { this.#hookStatuses.delete(key); @@ -660,6 +706,8 @@ export class StatusLineComponent implements Component { this.#resetJjRequests(); this.#onBranchChange = null; this.#clearUsageStartTimer(); + this.#onCodexResetFireworks = undefined; + this.#codexResetSnapshots.clear(); this.#retireGitWatcher(); } @@ -1125,10 +1173,8 @@ export class StatusLineComponent implements Component { return this.#vibeWorkerTokenRate?.() ?? null; } - #getUsageContextKey(session: AgentSession): string { - const activeProvider = session.state.model?.provider ?? session.model?.provider ?? ""; + #formatUsageContextKey(activeProvider: string | undefined, identity: OAuthAccountIdentity | undefined): string { if (!activeProvider) return ""; - const identity = session.modelRegistry?.authStorage?.getOAuthAccountIdentity(activeProvider, session.sessionId); // orgId is part of the key: rotating between two same-email Anthropic // subscriptions must invalidate the cached usage immediately instead of // showing the previous org's quota for the rest of the cache TTL. @@ -1141,6 +1187,14 @@ export class StatusLineComponent implements Component { ].join("\0"); } + #getUsageContextKey(session: AgentSession): string { + const activeProvider = session.state.model?.provider ?? session.model?.provider; + const identity = activeProvider + ? session.modelRegistry?.authStorage?.getOAuthAccountIdentity(activeProvider, session.sessionId) + : undefined; + return this.#formatUsageContextKey(activeProvider, identity); + } + /** * Startup redraws only arm a short-delayed task; timeout releases the render * cadence while a late successful fetch can still refresh the cached segment. @@ -1170,40 +1224,60 @@ export class StatusLineComponent implements Component { this.#usageInFlight = false; return; } + const sequence = ++this.#usageRefreshSequence; const signal = AbortSignal.timeout(STATUS_USAGE_REFRESH_TIMEOUT_MS); let reportsPromise: Promise | undefined; try { reportsPromise = fetcher.call(session, signal); - this.#applyUsageRefreshReports(session, await this.#raceUsageRefreshWithSignal(reportsPromise, signal)); + this.#applyUsageRefreshReports( + session, + await this.#raceUsageRefreshWithSignal(reportsPromise, signal), + sequence, + ); } catch { if (this.session !== session) return; this.#usageFetchedAt = Date.now(); if (signal.aborted && reportsPromise) { - this.#observeLateUsageRefresh(session, reportsPromise); + this.#observeLateUsageRefresh(session, reportsPromise, sequence); } } finally { if (this.session === session) this.#usageInFlight = false; } } - #applyUsageRefreshReports(session: AgentSession, reports: unknown): void { - if (this.#disposed || this.session !== session) return; + #applyUsageRefreshReports(session: AgentSession, reports: unknown, sequence: number): void { + if (this.#disposed || this.session !== session || sequence < this.#latestAppliedUsageRefreshSequence) { + return; + } + this.#latestAppliedUsageRefreshSequence = sequence; const activeProvider = session.state.model?.provider ?? session.model?.provider; const activeIdentity = activeProvider && session.modelRegistry?.authStorage ? session.modelRegistry.authStorage.getOAuthAccountIdentity(activeProvider, session.sessionId) : undefined; - this.#cachedUsage = this.#normalizeUsageReports(reports, activeProvider, activeIdentity); + const normalized = this.#normalizeUsageReports(reports, activeProvider, activeIdentity); + const resetSnapshot = + activeProvider === "openai-codex" ? this.#normalizeCodexResetSnapshot(reports, activeIdentity) : null; + this.#cachedUsage = normalized; this.#usageFetchedAt = Date.now(); + if (!resetSnapshot) return; + const contextKey = this.#formatUsageContextKey(activeProvider, activeIdentity); + const previous = this.#codexResetSnapshots.get(contextKey); + this.#codexResetSnapshots.set(contextKey, resetSnapshot); + if (!previous || !settings.get("tui.codexResetFireworks")) return; + const event = detectCodexResetFireworks(previous, resetSnapshot); + if (event) this.#onCodexResetFireworks?.(event); } - #observeLateUsageRefresh(session: AgentSession, reportsPromise: Promise): void { + #observeLateUsageRefresh(session: AgentSession, reportsPromise: Promise, sequence: number): void { void reportsPromise .then(reports => { - this.#applyUsageRefreshReports(session, reports); + this.#applyUsageRefreshReports(session, reports, sequence); }) .catch(() => { - if (this.#disposed || this.session !== session) return; + if (this.#disposed || this.session !== session || sequence < this.#latestAppliedUsageRefreshSequence) { + return; + } this.#usageFetchedAt = Date.now(); }); } @@ -1220,6 +1294,72 @@ export class StatusLineComponent implements Component { } } + #normalizeCodexResetSnapshot( + reports: unknown, + activeIdentity: OAuthAccountIdentity | undefined, + ): CodexResetUsageSnapshot | null { + if (!Array.isArray(reports)) return null; + let matchingReport: UsageReport | undefined; + for (const report of reports) { + if (!report || typeof report !== "object") continue; + if ( + !("provider" in report) || + report.provider !== "openai-codex" || + !("limits" in report) || + !Array.isArray(report.limits) + ) { + continue; + } + // The report boundary above validates the fields this extractor iterates; + // optional metadata and credit fields are narrowed again before use. + const usageReport = report as UsageReport; + if (!codexReportMatchesExactIdentity(usageReport, activeIdentity)) continue; + matchingReport = usageReport; + break; + } + if (!matchingReport) return null; + + const plan = + typeof matchingReport.metadata?.planType === "string" && matchingReport.metadata.planType + ? matchingReport.metadata.planType + : undefined; + let sevenDay: CodexResetUsageSnapshot["sevenDay"]; + let sevenDayTier: string | undefined; + for (const limit of matchingReport.limits) { + if (!limit || typeof limit !== "object") continue; + const candidate = limit as { + scope?: { windowId?: string; tier?: string }; + window?: { resetsAt?: number }; + amount?: { usedFraction?: number }; + }; + const fraction = candidate.amount?.usedFraction; + if (candidate.scope?.windowId !== "7d" || typeof fraction !== "number" || !Number.isFinite(fraction)) { + continue; + } + const tier = + typeof candidate.scope?.tier === "string" && candidate.scope.tier ? candidate.scope.tier : undefined; + if (sevenDay && (sevenDayTier === undefined || tier)) continue; + const resetsAt = candidate.window?.resetsAt; + sevenDay = { + percent: fraction * 100, + resetsAt: typeof resetsAt === "number" && Number.isFinite(resetsAt) ? resetsAt : undefined, + tier, + plan, + }; + sevenDayTier = tier; + } + + const fetchedAt = matchingReport.fetchedAt; + const availableCount = matchingReport.resetCredits?.availableCount; + const observedAt = typeof fetchedAt === "number" && Number.isFinite(fetchedAt) ? fetchedAt : undefined; + const savedResets = + typeof availableCount === "number" && Number.isFinite(availableCount) + ? Math.max(0, Math.trunc(availableCount)) + : undefined; + if (!sevenDay && savedResets === undefined) return null; + return { observedAt, sevenDay, savedResets }; + } + #normalizeUsageReports( reports: unknown, activeProvider?: string, @@ -1241,12 +1381,10 @@ export class StatusLineComponent implements Component { if (activeProvider && provider !== activeProvider) continue; const limits = (report as { limits?: unknown }).limits; if (!Array.isArray(limits)) continue; + const usageReport = report as UsageReport; for (const limit of limits) { if (!limit || typeof limit !== "object") continue; - if ( - activeIdentity && - !limitMatchesActiveAccount(report as UsageReport, limit as UsageLimit, activeIdentity) - ) { + if (activeIdentity && !limitMatchesActiveAccount(usageReport, limit as UsageLimit, activeIdentity)) { continue; } const l = limit as { diff --git a/packages/coding-agent/src/modes/components/tool-execution.ts b/packages/coding-agent/src/modes/components/tool-execution.ts index 3a9534f01..061f890ed 100644 --- a/packages/coding-agent/src/modes/components/tool-execution.ts +++ b/packages/coding-agent/src/modes/components/tool-execution.ts @@ -203,6 +203,13 @@ class SafeToolRendererComponent implements Component { */ export interface TranscriptLiveRegionProbe { isBlockInLiveRegion(component: Component): boolean; + /** + * Whether none of the block's rows have entered native scrollback (see + * `TranscriptContainer.isBlockUncommitted`). Optional: standalone hosts + * without commit tracking omit it, and blocks treat their rows as + * uncommitted. + */ + isBlockUncommitted?(component: Component): boolean; } /** Minimal TUI surface ToolExecutionComponent uses to schedule repaints and share image budget. */ @@ -337,10 +344,21 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac // transcript, e.g. in tests): whether this block is still repaintable. #liveRegion?: TranscriptLiveRegionProbe; // One-way latch for a detached (`async.state === "running"`) task block - // that left the transcript live region: its rows are commit-eligible - // history, so progress renders static gray and further partial snapshots are - // dropped (see #maybeFreezeBackgroundTask). + // whose rows became native-scrollback history — it left the transcript + // live region, or its head rows were committed while it was still the + // live tail. Further partial snapshots are dropped so committed rows are + // never mutated (see #maybeFreezeBackgroundTask). #backgroundTaskFrozen = false; + // Whether the freeze may restyle the progress rows static gray. Set only + // when the latch fired while no row was committed: a recolor of rows + // already on the tape would itself diverge immutable history and force an + // erase-replay (or, with scrollback rebuild off, a duplicate slab). + #backgroundTaskFrozenStyled = false; + // Wall clock captured at each repaintable rebuild of a task card and + // reused verbatim once the card freezes or any of its rows commit, so + // time-derived rows (current-tool elapsed, retry countdown) cannot drift + // a committed byte on later rebuilds (theme epoch, image toggles). + #taskRenderNowMs = Date.now(); // Set on each `render()` when the last painted pending shape must be // replayed wholesale when the first result arrives. Reset gates key off // these so a topology-changing update that lands before the shape reaches @@ -559,13 +577,17 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac _toolCallId?: string, ): void { // A detached task spawn keeps streaming progress snapshots after the - // block froze (left the transcript live region). Drop them: the rows are - // static gray history now, and repainting would rewrite rows the engine - // may already have committed to native scrollback. The terminal snapshot - // (async completed/failed → isPartial=false) still applies so a block - // that is still on screen settles on real results. - if (isPartial && this.#toolName === "task" && this.#maybeFreezeBackgroundTask()) { - return; + // block froze (left the transcript live region, or its rows entered + // native scrollback). Drop them: repainting would rewrite rows the + // engine may already have committed. The terminal snapshot (async + // completed/failed → isPartial=false) still settles a card that is + // wholly uncommitted (still on screen); once any row is on the tape + // the card is immutable history — replacing it would re-commit the + // whole slab below the stale copy — so the settlement is dropped and + // the job's result surfaces through its own delivery message. + if (this.#toolName === "task" && this.#maybeFreezeBackgroundTask()) { + if (isPartial) return; + if (!(this.#liveRegion?.isBlockUncommitted?.(this) ?? true)) return; } const hadNoResult = this.#result === undefined; const wasPartialResult = this.#result !== undefined && this.#isPartial; @@ -713,22 +735,30 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac } /** - * Freeze a detached (`async.state === "running"`) task block once it leaves - * the transcript's live region. Past that seam its rows are commit-eligible - * native-scrollback history: repaint the progress rows static gray and drop - * further partial snapshots. One-way — blocks never re-enter the live - * region. Returns whether the block is frozen. + * Freeze a detached (`async.state === "running"`) task block once its rows + * become native-scrollback history: the block left the transcript's live + * region (a later block streams below it), or — while it is still the + * live tail — its head rows were committed because the frame outgrew the + * viewport. Committed rows are immutable, so from that point every further + * partial snapshot is dropped. Rows restyle static gray only when nothing + * is committed yet; otherwise the bytes stay exactly as painted. One-way — + * blocks never re-enter the live region. Returns whether the block is + * frozen. */ #maybeFreezeBackgroundTask(): boolean { if (this.#backgroundTaskFrozen) return true; if (this.#toolName !== "task" || this.#liveRegion === undefined) return false; const asyncState = (this.#result?.details as { async?: { state?: string } } | undefined)?.async?.state; if (asyncState !== "running") return false; - if (this.#liveRegion.isBlockInLiveRegion(this)) return false; + const uncommitted = this.#liveRegion.isBlockUncommitted?.(this) ?? true; + if (uncommitted && this.#liveRegion.isBlockInLiveRegion(this)) return false; this.#backgroundTaskFrozen = true; this.#updateSpinnerAnimation(); - this.#updateDisplay(); - this.#ui.requestRender(); + if (uncommitted) { + this.#backgroundTaskFrozenStyled = true; + this.#updateDisplay(); + this.#ui.requestRender(); + } return true; } @@ -784,16 +814,16 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac /** * Keeps in-flight TV-wall frames out of immutable native scrollback: the - * `vibe_wait` wall and displaceable snapshots (`hub` waiting polls, `todo` - * lists). Their frames replace each other rather than append, and their - * rows mutate every spinner tick — an unpinned commit records a per-tick - * frozen snapshot AND force-seals the block (see TranscriptContainer's - * committed-snapshot seal), so the next poll stacks a new frame instead of - * displacing this one. + * `vibe_wait` wall, displaceable snapshots (`hub` waiting polls, `todo` + * lists), and live `task` calls. Their frames replace each other rather + * than append — task progress rows rewrite in place on every snapshot — + * so an unpinned commit records a per-tick frozen snapshot (and for + * displaceable blocks force-seals them, stacking the next poll below). + * The finalized frame commits exactly once when the pin lifts. */ isNativeScrollbackLiveRegionPinned(): boolean { if (this.isTranscriptBlockFinalized()) return false; - return this.#toolName === "vibe_wait" || this.#displaceableByToolName !== undefined; + return this.#toolName === "vibe_wait" || this.#toolName === "task" || this.#displaceableByToolName !== undefined; } /** @@ -827,8 +857,12 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac this.#sealed = true; this.#displaceableByToolName = undefined; // A sealed detached task is abandoned history: settle its progress rows - // on static gray. + // on static gray — but only while none of them are committed; a recolor + // on the tape would diverge immutable history. this.#backgroundTaskFrozen = true; + if (this.#liveRegion?.isBlockUncommitted?.(this) ?? true) { + this.#backgroundTaskFrozenStyled = true; + } this.stopAnimation(); this.#updateDisplay(); this.#ui.requestRender(); @@ -888,7 +922,7 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac // TUI startup, so a result rendered before it lands must re-shape once it // does (it gates Image children vs text fallback in #rebuildDisplay); keyed // here for the same reason markdown.ts keys its render cache on it. - const key = `${this.#resultVersion}|${this.#expanded}|${this.#isPartial}|${this.#spinnerFrame ?? "-"}|${this.#showImages}|${getThemeEpoch()}|${this.#displayInputVersion}|${this.#backgroundTaskFrozen}|${TERMINAL.imageProtocol ?? "-"}|${this.#imageSizeKey()}`; + const key = `${this.#resultVersion}|${this.#expanded}|${this.#isPartial}|${this.#spinnerFrame ?? "-"}|${this.#showImages}|${getThemeEpoch()}|${this.#displayInputVersion}|${this.#backgroundTaskFrozenStyled}|${TERMINAL.imageProtocol ?? "-"}|${this.#imageSizeKey()}`; if (key === this.#lastDisplayKey && this.#displayBuilt) return; this.#lastDisplayKey = key; @@ -1292,9 +1326,17 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac // draws every dispatched agent as a progress/result line, so tell // `renderCall` to drop its duplicate streaming preview list. context.hasResult = Boolean(this.#result); - // Out of the transcript live region: progress rows render static gray - // (see task/render.ts). - context.frozen = this.#backgroundTaskFrozen; + // Settled as history (out of the live region, before any row entered + // the tape): progress rows render static gray (see task/render.ts). + context.frozen = this.#backgroundTaskFrozenStyled; + // Freeze the render clock alongside the latch — and independently the + // moment any row commits, closing the window between a commit paint + // and the next snapshot where a settings-triggered rebuild could + // re-derive elapsed/countdown bytes under committed rows. + if (!this.#backgroundTaskFrozen && (this.#liveRegion?.isBlockUncommitted?.(this) ?? true)) { + this.#taskRenderNowMs = Date.now(); + } + context.nowMs = this.#taskRenderNowMs; } else if (isEditLikeToolName(this.#toolName)) { context.editMode = this.#editMode; const previews = this.#editDiffPreview; diff --git a/packages/coding-agent/src/modes/interactive-mode.ts b/packages/coding-agent/src/modes/interactive-mode.ts index be28c0f27..97ef79cc9 100644 --- a/packages/coding-agent/src/modes/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive-mode.ts @@ -128,6 +128,7 @@ import { } from "../tools/todo"; import { vocalizer } from "../tts/vocalizer"; import { renderTreeList } from "../tui/tree-list"; +import { formatStartupChangelogSummary, type StartupChangelogSelection } from "../utils/changelog"; import { copyToClipboard } from "../utils/clipboard"; import type { EventBus } from "../utils/event-bus"; import { getEditorCommand, openInEditor } from "../utils/external-editor"; @@ -149,6 +150,7 @@ import { import type { AssistantMessageComponent } from "./components/assistant-message"; import type { BashExecutionComponent } from "./components/bash-execution"; import { ChatBlock, type ChatBlockHost } from "./components/chat-block"; +import { CodexResetFireworksController } from "./components/codex-reset-fireworks"; import { CustomEditor } from "./components/custom-editor"; import { DynamicBorder } from "./components/dynamic-border"; import { ErrorBannerComponent } from "./components/error-banner"; @@ -556,7 +558,7 @@ export class InteractiveMode implements InteractiveModeContext { #cleanupUnsubscribe?: () => void; #signalTeardown?: SessionTeardown; readonly #version: string; - readonly #changelogMarkdown: string | undefined; + readonly #startupChangelog: StartupChangelogSelection | undefined; #planModePreviousTools: string[] | undefined; #goalModePreviousTools: string[] | undefined; #vibeModePreviousTools: string[] | undefined; @@ -582,6 +584,7 @@ export class InteractiveMode implements InteractiveModeContext { mcpManager?: MCPManager; readonly #toolUiContextSetter: (uiContext: ExtensionUIContext, hasUI: boolean) => void; + readonly #codexResetFireworksController: CodexResetFireworksController; readonly #btwController: BtwController; readonly #tanCommandController: TanCommandController; readonly #omfgController: OmfgController; @@ -664,7 +667,7 @@ export class InteractiveMode implements InteractiveModeContext { constructor( session: AgentSession, version: string, - changelogMarkdown: string | undefined = undefined, + startupChangelog: StartupChangelogSelection | undefined = undefined, setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void = () => {}, lspServers: LspStartupServerInfo[] | undefined = undefined, mcpManager?: MCPManager, @@ -676,7 +679,7 @@ export class InteractiveMode implements InteractiveModeContext { this.keybindings = KeybindingsManager.inMemory(); this.agent = session.agent; this.#version = version; - this.#changelogMarkdown = changelogMarkdown; + this.#startupChangelog = startupChangelog; this.#toolUiContextSetter = setToolUIContext; this.lspServers = lspServers; this.mcpManager = mcpManager; @@ -752,6 +755,10 @@ export class InteractiveMode implements InteractiveModeContext { this.editorContainer.addChild(this.editor); this.statusLine = new StatusLineComponent(session); this.statusLine.setAutoCompactEnabled(session.autoCompactionEnabled); + this.#codexResetFireworksController = new CodexResetFireworksController(this); + this.statusLine.setCodexResetFireworksHandler(event => { + this.#codexResetFireworksController.show(event); + }); // Vibe worker tok/s aggregator — keeps the status-line render layer off // the heavy vibe/task dependency graph. The director is often idle while // workers stream, so without this the tok/s badge would show a stale @@ -943,19 +950,20 @@ export class InteractiveMode implements InteractiveModeContext { } // Add changelog if provided - if (this.#changelogMarkdown) { + if (this.#startupChangelog && settings.get("startup.changelogMode") !== "hidden") { this.ui.addChild(new DynamicBorder()); - if (settings.get("collapseChangelog")) { - const versionMatch = this.#changelogMarkdown.match(/##\s+\[?(\d+\.\d+\.\d+)\]?/); - const latestVersion = versionMatch ? versionMatch[1] : this.#version; - const condensedText = `Updated to v${latestVersion}. Use ${theme.bold("/changelog")} to view full changelog.`; - this.ui.addChild(new Text(condensedText, 1, 0)); + this.ui.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0)); + this.ui.addChild(new Spacer(1)); + if (settings.get("startup.changelogMode") === "summary") { + const summary = formatStartupChangelogSummary(this.#startupChangelog).replace( + /\/changelog(?: full)?/g, + command => theme.bold(command), + ); + this.ui.addChild(new Text(summary, 1, 0)); } else { - this.ui.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0)); - this.ui.addChild(new Spacer(1)); - this.ui.addChild(new Markdown(this.#changelogMarkdown.trim(), 1, 0, getMarkdownTheme())); - this.ui.addChild(new Spacer(1)); + this.ui.addChild(new Markdown(this.#startupChangelog.markdown?.trim() ?? "", 1, 0, getMarkdownTheme())); } + this.ui.addChild(new Spacer(1)); this.ui.addChild(new DynamicBorder()); } } @@ -3947,6 +3955,7 @@ export class InteractiveMode implements InteractiveModeContext { this.#agentRegistryUnsubscribe = undefined; this.#agentRegistrySubscriptionTarget = undefined; this.#eventController.dispose(); + this.#codexResetFireworksController.dispose(); this.statusLine.dispose(); if (this.#resizeHandler) { process.stdout.removeListener("resize", this.#resizeHandler); diff --git a/packages/coding-agent/src/modes/rpc/rpc-client.ts b/packages/coding-agent/src/modes/rpc/rpc-client.ts index 111df3d11..c4182bf5b 100644 --- a/packages/coding-agent/src/modes/rpc/rpc-client.ts +++ b/packages/coding-agent/src/modes/rpc/rpc-client.ts @@ -598,6 +598,23 @@ export class RpcClient { */ async getState(): Promise { const response = await this.#send({ type: "get_state" }); + const state = this.#getData(response); + return { + ...state, + fastModeEnabled: state.fastModeEnabled === true, + fastModeActive: state.fastModeActive === true, + tokensPerSecond: + typeof state.tokensPerSecond === "number" && Number.isFinite(state.tokensPerSecond) + ? state.tokensPerSecond + : null, + }; + } + + /** + * Enable or disable fast mode for the active model family. + */ + async setFastMode(enabled: boolean): Promise<{ enabled: boolean; active: boolean }> { + const response = await this.#send({ type: "set_fast_mode", enabled }); return this.#getData(response); } diff --git a/packages/coding-agent/src/modes/rpc/rpc-mode.ts b/packages/coding-agent/src/modes/rpc/rpc-mode.ts index 19b74c64c..952079365 100644 --- a/packages/coding-agent/src/modes/rpc/rpc-mode.ts +++ b/packages/coding-agent/src/modes/rpc/rpc-mode.ts @@ -32,6 +32,7 @@ import { executeAcpBuiltinSlashCommand } from "../../slash-commands/acp-builtins import { buildAvailableSlashCommands } from "../../slash-commands/available-commands"; import { defaultLoadModeForToolName } from "../../tools/essential-tools"; import type { EventBus } from "../../utils/event-bus"; +import { calculateTokensPerSecond } from "../../utils/token-rate"; import { initializeExtensions } from "../runtime-init"; import { isRpcHostToolResult, isRpcHostToolUpdate, RpcHostToolBridge } from "./host-tools"; import { isRpcHostUriResult, RpcHostUriBridge } from "./host-uris"; @@ -1084,9 +1085,12 @@ export async function runRpcMode( sessionId: session.sessionId, sessionName: session.sessionName, autoCompactionEnabled: session.autoCompactionEnabled, - messageCount: session.messages.length, queuedMessageCount: session.queuedMessageCount, todoPhases: session.getTodoPhases(), + fastModeEnabled: session.isFastModeEnabled(), + tokensPerSecond: calculateTokensPerSecond(session.messages, session.isStreaming), + fastModeActive: session.isFastModeActive(), + messageCount: session.messages.length, systemPrompt: session.systemPrompt, dumpTools: session.agent.state.tools.map(tool => ({ name: tool.name, @@ -1099,6 +1103,17 @@ export async function runRpcMode( return success(id, "get_state", state); } + case "set_fast_mode": { + const supported = session.setFastMode(command.enabled); + if (command.enabled && !supported) { + return error(id, "set_fast_mode", "Fast mode is unavailable for the current model."); + } + return success(id, "set_fast_mode", { + enabled: session.isFastModeEnabled(), + active: session.isFastModeActive(), + }); + } + case "get_available_commands": { return success(id, "get_available_commands", { commands: await getAvailableCommands() }); } diff --git a/packages/coding-agent/src/modes/rpc/rpc-types.ts b/packages/coding-agent/src/modes/rpc/rpc-types.ts index ab409a959..74e862987 100644 --- a/packages/coding-agent/src/modes/rpc/rpc-types.ts +++ b/packages/coding-agent/src/modes/rpc/rpc-types.ts @@ -39,6 +39,7 @@ export type RpcCommand = // State | { id?: string; type: "get_state" } + | { id?: string; type: "set_fast_mode"; enabled: boolean } | { id?: string; type: "get_available_commands" } | { id?: string; type: "set_todos"; phases: TodoPhase[] } | { id?: string; type: "set_host_tools"; tools: RpcHostToolDefinition[] } @@ -107,6 +108,9 @@ export interface RpcSessionState { sessionId: string; sessionName?: string; autoCompactionEnabled: boolean; + fastModeEnabled: boolean; + fastModeActive: boolean; + tokensPerSecond: number | null; messageCount: number; queuedMessageCount: number; todoPhases: TodoPhase[]; @@ -209,6 +213,13 @@ export type RpcResponse = // State | { id?: string; type: "response"; command: "get_state"; success: true; data: RpcSessionState } + | { + id?: string; + type: "response"; + command: "set_fast_mode"; + success: true; + data: { enabled: boolean; active: boolean }; + } | { id?: string; type: "response"; diff --git a/packages/coding-agent/src/prompts/system/auto-thinking-difficulty.md b/packages/coding-agent/src/prompts/system/auto-thinking-difficulty.md index 141956353..2baa723e9 100644 --- a/packages/coding-agent/src/prompts/system/auto-thinking-difficulty.md +++ b/packages/coding-agent/src/prompts/system/auto-thinking-difficulty.md @@ -1,6 +1,6 @@ You are a difficulty classifier for a coding agent. Read the user's request and decide how much reasoning effort the agent should spend on it this turn. -Reply with exactly one word — one of: `low`, `medium`, `high`, `xhigh`. No punctuation, no explanation, no other text. +Reply with exactly one word — one of: `low`, `medium`, `high`, `xhigh`{{#if allowMax}}, `max`{{/if}}. No punctuation, no explanation, no other text. Levels: @@ -8,5 +8,7 @@ Levels: - `medium` — A localized change that needs some reasoning. A small self-contained feature, a straightforward bug fix in one place, or explaining a moderate piece of code. - `high` — A non-trivial change. Spans multiple files or callers, requires real debugging, a moderate design decision, or a refactor with several moving parts. - `xhigh` — Deep or open-ended. Subtle concurrency or algorithmic problems, cross-system reasoning, ambiguous requirements, large or risky refactors, or hard root-cause debugging. +{{#if allowMax}}- `max` — Everything `xhigh` covers, and at least one of: there is no reproduction to work from, the operation is irreversible or can lose data, or a live cutover has to stay correct while it runs. Requires the `xhigh` bar first — difficulty alone is not enough. +{{/if}} -Judge the inherent difficulty of the task, not how politely or verbosely it is phrased. When torn between two levels, choose the lower one. +Judge the inherent difficulty of the task, not how politely or verbosely it is phrased. When torn between two levels, choose the lower one{{#if allowMax}} — except between `xhigh` and `max`, where a request that meets the `max` conditions takes `max`{{/if}}. diff --git a/packages/coding-agent/src/prompts/tools/checkpoint.md b/packages/coding-agent/src/prompts/tools/checkpoint.md index 8eca2f59b..dd4044367 100644 --- a/packages/coding-agent/src/prompts/tools/checkpoint.md +++ b/packages/coding-agent/src/prompts/tools/checkpoint.md @@ -5,7 +5,7 @@ Use this when you need to investigate with many intermediate tool calls (read/gr Rules: - You MUST call `rewind` before yielding after starting a checkpoint. - You NEVER call `checkpoint` while another checkpoint is active. -- Not available in subagents. +- Disabled by default in subagents. To enable, list `checkpoint` or `rewind` in the agent definition's `tools:` frontmatter (the sister tool is auto-included; requires `checkpoint.enabled` setting). Typical flow: 1. `checkpoint(goal: …)` diff --git a/packages/coding-agent/src/sdk.ts b/packages/coding-agent/src/sdk.ts index 150651478..ef35ff0a3 100644 --- a/packages/coding-agent/src/sdk.ts +++ b/packages/coding-agent/src/sdk.ts @@ -62,7 +62,8 @@ import { loadPromptTemplates as loadPromptTemplatesInternal, type PromptTemplate import { applyProviderGlobalsFromSettings } from "./config/provider-globals"; import { buildServiceTierByFamily } from "./config/service-tier"; import { Settings, type SkillsSettings } from "./config/settings"; -import { CursorExecHandlers } from "./cursor"; +import { CursorExecHandlers, type CursorMcpResourceAdapter } from "./cursor"; +import { createBridgeEditTool, createBridgeGrepFactory } from "./cursor-bridge-tools"; import "./discovery"; import { initializeWithSettings } from "./discovery"; import { disposeAllJuliaKernelSessions, disposeJuliaKernelSessionsByOwner } from "./eval/jl/executor"; @@ -2536,6 +2537,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} () => (hasSession ? createSessionMemoryRuntimeContext(session, agentDir, cwd) : undefined), settings, localProtocolOptions, + () => (hasSession ? session.getAsyncJobSnapshot() : null), ); credentialDisabledTarget = extensionRunner; @@ -2606,6 +2608,43 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} for (const tool of toolRegistry.values()) { toolRegistry.set(tool.name, new ExtensionToolWrapper(tool, extensionRunner)); } + // Cursor's own client owns file edits, so `edit` is not advertised to the + // model (commit 8ba0498eb: full-file `write` is used instead). The exec + // bridge is a different consumer: the server sends native `pi_edit` + // frames regardless of the advertised catalog, and answering them needs + // a real tool. + // + // It must be a `replace`-mode instance. `PiEditExecArgs` carries + // `old_text`/`new_text` pairs, which is exactly `replace`'s schema and + // nothing else's — under the default `hashline` mode the frame's args do + // not match the tool's parameters at all. The registry instance follows + // the session's configured mode, so the bridge builds its own. + // + // The grant is captured HERE, before the Cursor branch below deletes + // `edit` from the registry, and independently of the session's provider: + // a session that starts on another provider can switch to Cursor later, + // and the roster is built once, at session creation. Reading the registry + // at frame time would see the switched-to state, not the grant. + const editWasGranted = toolRegistry.has("edit"); + // Built on first use rather than eagerly: a session that never reaches + // Cursor never constructs it. + let cursorBridgeEditTool: AgentTool | undefined; + const getCursorBridgeEditTool = (): AgentTool | undefined => { + // Only when the session actually granted `edit`. `createTools` omits + // it entirely for a restricted tool set, and the bridge answers native + // frames that arrive regardless of the advertised catalog — so + // building one unconditionally would hand a read-only agent a + // mutating tool it was denied (the issue #5680 escalation). + if (!editWasGranted) return undefined; + cursorBridgeEditTool ??= createBridgeEditTool(toolSession, extensionRunner); + return cursorBridgeEditTool; + }; + // Whether this session granted a file-writing tool. Same capture-early + // reasoning, plus `write` may be auto-registered further down as an xdev + // transport. The exec bridge answers native `delete` and + // resource-download frames that mutate files without running a registry + // tool, so it needs the grant as the session actually made it. + const cursorCanMutateFiles = editWasGranted || toolRegistry.has("write"); if (model?.provider === "cursor") { toolRegistry.delete("edit"); builtInRegistryToolNames.delete("edit"); @@ -2648,15 +2687,52 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} if (!state) return undefined; return resolveMountedXdevExecutable(state, name); }; + // Cursor's resource frames ask what THIS client's servers advertise; only + // live connections have any. Built once: the advisor bridges answer from + // the same connections the primary does. + const cursorMcpResources: CursorMcpResourceAdapter | undefined = mcpManager && { + serverNames: () => mcpManager.getConnectedServers(), + getServerResources: async name => { + // The manager registers a server's tools before its background + // resource load finishes, so a frame arriving in that window + // would read an empty cache and report "advertises nothing". + await mcpManager.ensureServerResources(name); + return mcpManager.getServerResources(name); + }, + readServerResource: (name, uri) => mcpManager.readServerResource(name, uri), + }; const cursorExecHandlers = new CursorExecHandlers({ cwd, + // The session's cwd moves (`/cd`, resume, branch restore) while this + // bridge is built once at startup. Path-confining frames — the native + // `delete` and a `download_path` resource read — resolve against + // whichever of the two they are given, so without the live resolver the + // primary would write into the workspace the session has left while + // reporting success for the path the server asked about. The advisor + // bridge already passes one. + getCwd: () => sessionManager.getCwd(), tools: toolRegistry, getExecutableTool: resolveDeviceTool, + // `pi_edit` needs the `replace`-mode instance specifically, and the + // registry may still hold the session's own `edit` (any mode) when + // this session did not start on Cursor. + getEditReplaceTool: getCursorBridgeEditTool, getToolContext: () => toolContextStore.getContext(), + mcpResources: cursorMcpResources, emitEvent: event => cursorEventEmitter?.(event), getTodoPhases: () => session.getTodoPhases(), setTodoPhases: phases => session.setTodoPhases(phases), persistTodoPhases: phases => sessionManager.appendCustomEntry(USER_TODO_EDIT_CUSTOM_TYPE, { phases }), + // `pi_grep` carries its own context width and match cap, which the + // shared grep instance fixed at construction cannot express. Gated on + // the grant: the factory builds a fresh tool and `executeTool` prefers + // it over the registry, so installing it unconditionally would let a + // session without `grep` search anyway. + createGrepTool: toolRegistry.has("grep") ? createBridgeGrepFactory(toolSession, extensionRunner) : undefined, + // The native `delete` and resource-download frames mutate files + // without running a registry tool, so this grant is the only thing + // standing between a restricted session and a workspace write. + allowDirectFileMutation: cursorCanMutateFiles, }); // Resolve the inline-descriptors setting against the session-start model. @@ -2818,6 +2894,19 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} } } } + // Checkpoint and rewind are a pair: `createTools` auto-includes the sister + // tool in the registry, but an explicit `toolNames` list would otherwise + // drop it from the ACTIVE set — leaving the agent able to checkpoint but + // unable to rewind (or vice versa). Mirror the pairing here. Unlike the + // manage_skill/learn mirror above, this is a safety pairing — it applies + // to restricted sessions too. + if (explicitlyRequestedToolNames) { + if (builtInToolNames.includes("checkpoint") && !explicitlyRequestedToolNames.includes("rewind")) { + explicitlyRequestedToolNames.push("rewind"); + } else if (builtInToolNames.includes("rewind") && !explicitlyRequestedToolNames.includes("checkpoint")) { + explicitlyRequestedToolNames.push("checkpoint"); + } + } const requestedToolNames = explicitlyRequestedToolNames ?? toolNamesFromRegistry; const normalizedRequested = requestedToolNames.filter(name => toolRegistry.has(name)); const defaultInactiveToolNames = new Set( @@ -3137,7 +3226,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} advisorToolBuilds.push(BUILTIN_TOOLS[name as keyof typeof BUILTIN_TOOLS](advisorToolSession)); } const built = await Promise.all(advisorToolBuilds); - const advisorTools: Tool[] = built.filter((tool): tool is Tool => tool != null).map(wrapToolWithMetaNotice); + // Wrapped like every registry tool: `ExtensionToolWrapper` is where the + // approval mode, per-tool `tools.approval.` policies and + // `autoApprove` are enforced. The advisor's loop and its Cursor exec + // bridge both run these instances directly, so a raw one would execute a + // `bash`/`write` the user configured as `ask` or `deny`. Meta-notice + // first, matching the registry's wrap order. + const advisorTools: Tool[] = built + .filter((tool): tool is Tool => tool != null) + .map(tool => new ExtensionToolWrapper(wrapToolWithMetaNotice(tool), extensionRunner) as Tool); const advisorWatchdogPrompts = [...watchdogFiles]; if (activeRepoContext) { @@ -3247,6 +3344,20 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} providerPromptCacheKeySource, parentEvalSessionId: options.parentEvalSessionId, advisorTools, + // Same per-call `grep` seam the primary bridge gets, built against the + // advisor's own tool session so a `pi_grep` frame's context width and + // match cap are honored there too. + advisorCreateGrepTool: createBridgeGrepFactory(advisorToolSession, extensionRunner), + // Same `replace`-mode requirement as the primary bridge; the advisor + // path gates it on the advisor's own `edit` grant. + advisorCreateEditTool: () => createBridgeEditTool(advisorToolSession, extensionRunner), + // The advisor's bridge tools are wrapped for approval, but the wrapper + // reads the mode and per-tool policies only from the execute-time + // context — the primary bridge passes the same store. + advisorGetToolContext: () => toolContextStore.getContext(), + // Same live connections the primary bridge reads; an advisor's + // resource frame would otherwise report every server as empty. + advisorMcpResources: cursorMcpResources, titleSystemPrompt: options.titleSystemPrompt, }); hasSession = true; @@ -3274,6 +3385,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} throw new Error(`Agent "${resolvedAgentId}" was replaced during session initialization.`); } hasRegistered = true; + // MCP notification bridge cleanup — assigned when the bridge is wired below, + // invoked from the dispose wrapper AND registered as a postmortem so both + // explicit-dispose (SDK embedders that reuse the process across sessions) and + // process-exit paths tear the listener down. Nulled after use so the closure + // graph (`extensionRunner`, `session`) can be GC'd instead of retained by the + // process-global postmortem list. + let unsubscribeMcpNotifications: (() => void) | undefined; + let unregisterMcpPostmortem: (() => void) | undefined; + { const originalDispose = session.dispose.bind(session); session.dispose = async () => { @@ -3304,6 +3424,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} } finally { unregisterUnlessParked(); unsubscribeCredentialDisabled?.(); + unsubscribeMcpNotifications?.(); + unregisterMcpPostmortem?.(); + // Drop refs so the process-global postmortem list doesn't retain + // the bridge closure past explicit dispose. + unsubscribeMcpNotifications = undefined; + unregisterMcpPostmortem = undefined; } }; } @@ -3480,19 +3606,24 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} } } - // Wire MCP manager callbacks to session for reactive tool updates. - // Skip when reusing a parent's manager — the parent owns the callbacks. + // MCP manager wiring has two ownership models: + // * Single-slot callbacks (tools/prompts/resources changed) — exactly one + // owner per manager. When reusing a parent's manager (subagent path, + // see task/executor.ts), the parent already owns these slots so we + // MUST NOT overwrite them. Guarded by `!options.mcpManager`. + // * Notification listener — multi-listener by design. Every session with + // an MCP manager (fresh OR reused) needs its own bridge to its own + // `extensionRunner` so extensions loaded in that session receive frames. + // Guarded only by `mcpManager` (see the second `if` below). if (mcpManager && !options.mcpManager) { - mcpManager.setOnToolsChanged(tools => { - void (async () => { - try { - await session.refreshMCPTools(tools); - } catch (error) { - logger.warn("MCP tool refresh failed", { - error: error instanceof Error ? error.message : String(error), - }); - } - })(); + mcpManager.setOnToolsChanged(async tools => { + try { + await session.refreshMCPTools(tools); + } catch (error) { + logger.warn("MCP tool refresh failed", { + error: error instanceof Error ? error.message : String(error), + }); + } }); // Wire prompt refresh → rebuild MCP prompt slash commands mcpManager.setOnPromptsChanged(serverName => { @@ -3525,6 +3656,29 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} }); } + if (mcpManager) { + // Bridge server-initiated notifications to this session's extension + // handlers. Multi-listener registration: fresh-manager and reused-manager + // sessions both install their own listener here, so a subagent's + // extensions get frames even though the parent owns the single-slot + // tool/prompt/resource callbacks above. MCPManager fires known + // list/update refreshes internally, then invokes all registered + // listeners with (server, method, params) for every frame (including + // server-custom methods). Two-layer buffering protects the startup + // race: MCPManager buffers frames received before the first + // `addNotificationListener` subscriber (drains here); ExtensionRunner + // buffers frames received before `initialize()` and drains them on + // init. Both drop-oldest under pressure at cap 100. + unsubscribeMcpNotifications = mcpManager.addNotificationListener((server, method, params) => { + void extensionRunner.emitMcpNotification({ server, method, params }); + }); + // postmortem.register returns a cancel function; capture it so explicit + // session.dispose can remove this from the global list (see finally above). + unregisterMcpPostmortem = postmortem.register("mcp-notification-listener-cleanup", () => + unsubscribeMcpNotifications?.(), + ); + } + startDeferredMCPDiscovery?.(session); return { diff --git a/packages/coding-agent/src/session/agent-session-types.ts b/packages/coding-agent/src/session/agent-session-types.ts index 75828db4f..12846d10c 100644 --- a/packages/coding-agent/src/session/agent-session-types.ts +++ b/packages/coding-agent/src/session/agent-session-types.ts @@ -1,4 +1,11 @@ -import type { Agent, AgentMessage, AgentTool, StreamFn, ThinkingLevel } from "@oh-my-pi/pi-agent-core"; +import type { + Agent, + AgentMessage, + AgentTool, + AgentToolContext, + StreamFn, + ThinkingLevel, +} from "@oh-my-pi/pi-agent-core"; import type { Context, Effort, @@ -17,6 +24,7 @@ import type { AsyncJob, AsyncJobDeliveryState, AsyncJobManager } from "../async" import type { ModelRegistry } from "../config/model-registry"; import type { PromptTemplate } from "../config/prompt-templates"; import type { Settings, SkillsSettings } from "../config/settings"; +import type { CursorMcpResourceAdapter } from "../cursor"; import type { RawSseDebugBuffer } from "../debug/raw-sse-buffer"; import type { TtsrManager } from "../export/ttsr"; import type { LoadedCustomCommand } from "../extensibility/custom-commands"; @@ -209,6 +217,35 @@ export interface AgentSessionConfig { providerPromptCacheKeySource?: "explicit" | "fork"; /** Full advisor toolset built against an advisor-scoped tool session. */ advisorTools?: AgentTool[]; + /** + * Build a `grep` honoring a Cursor `pi_grep` frame's own context width and + * match cap, against the advisor-scoped tool session. Without it an advisor + * running on Cursor silently drops both fields. + */ + advisorCreateGrepTool?(options: { context?: number; totalMatchLimit?: number }): AgentTool | undefined; + /** + * Build the `replace`-mode `edit` a Cursor `pi_edit` frame needs, against the + * advisor-scoped tool session. The advisor's ordinary instance follows the + * configured `edit.mode` and rejects the frame's `old_text`/`new_text` pairs. + */ + advisorCreateEditTool?(): AgentTool | undefined; + /** + * The execute-time context the advisor's bridge tools resolve approval from. + * + * `ExtensionToolWrapper` reads `tools.approvalMode`, per-tool + * `tools.approval.` policies and `autoApprove` only from this context; + * with none it defaults to `yolo` with empty policies, so a bridge tool would + * run a native frame the user configured `ask` or `deny` for. + */ + advisorGetToolContext?: () => AgentToolContext | undefined; + /** + * The live MCP connections the advisor's Cursor resource frames answer from. + * + * Advisors share the session's connections and may be granted tools from + * those same servers; without this their `list_mcp_resources` reports an + * empty catalog and every `read_mcp_resource` a `not_found`. + */ + advisorMcpResources?: CursorMcpResourceAdapter; /** Preloaded watchdog prompt content for the advisor. */ advisorWatchdogPrompt?: string; /** Shared advisor instructions loaded from WATCHDOG.yml. */ diff --git a/packages/coding-agent/src/session/agent-session.ts b/packages/coding-agent/src/session/agent-session.ts index 21722bf1b..4117c17aa 100644 --- a/packages/coding-agent/src/session/agent-session.ts +++ b/packages/coding-agent/src/session/agent-session.ts @@ -398,6 +398,9 @@ type SetSessionNameWithTrigger = ( trigger?: SessionNameTrigger, ) => Promise; +const kPersistedSessionEntryId = Symbol("persistedSessionEntryId"); +type PersistedAssistantMessage = AssistantMessage & { [kPersistedSessionEntryId]?: string }; + export class AgentSession { readonly agent: Agent; readonly sessionManager: SessionManager; @@ -956,6 +959,7 @@ export class AgentSession { scheduleAgentContinue: options => this.#scheduleAgentContinue(options), waitForSessionMessagePersistence: message => this.#waitForSessionMessagePersistence(message), appendSessionMessage: message => this.#appendSessionMessage(message), + persistedAssistantEntryId: message => (message as PersistedAssistantMessage)[kPersistedSessionEntryId], sessionMessageAlreadyPersisted: message => this.#sessionMessageAlreadyPersisted(message), setModelWithProviderSessionReset: model => this.#setModelWithProviderSessionReset(model), resetCurrentResponsesProviderSession: reason => this.#resetCurrentResponsesProviderSession(reason), @@ -1290,6 +1294,10 @@ export class AgentSession { this.#advisors = new SessionAdvisors(advisorsHost, { enabled: this.settings.get("advisor.enabled"), tools: config.advisorTools, + createGrepTool: config.advisorCreateGrepTool, + createEditTool: config.advisorCreateEditTool, + getToolContext: config.advisorGetToolContext, + mcpResources: config.advisorMcpResources, watchdogPrompt: config.advisorWatchdogPrompt, sharedInstructions: config.advisorSharedInstructions, contextPrompt: config.advisorContextPrompt, @@ -2065,6 +2073,9 @@ export class AgentSession { const cache = this.#persistedMessageKeys; const wasFresh = cache !== undefined && cache.anchor === this.#persistedMessageKeysAnchor(); const entryId = this.sessionManager.appendMessage(message); + if (message.role === "assistant") { + (message as PersistedAssistantMessage)[kPersistedSessionEntryId] = entryId; + } const key = sessionMessagePersistenceKey(message); if (wasFresh && cache && key) { cache.keys.add(key); @@ -5180,6 +5191,7 @@ export class AgentSession { void this.dispose().finally(() => process.exit(0)); }, getContextUsage: () => this.getContextUsage(), + getAsyncJobSnapshot: () => this.getAsyncJobSnapshot(), waitForIdle: () => this.waitForIdle(), newSession: async options => { const success = await this.newSession({ parentSession: options?.parentSession }); diff --git a/packages/coding-agent/src/session/codex-session-store.ts b/packages/coding-agent/src/session/codex-session-store.ts index 5faa8d8f5..6c9952229 100644 --- a/packages/coding-agent/src/session/codex-session-store.ts +++ b/packages/coding-agent/src/session/codex-session-store.ts @@ -14,7 +14,7 @@ import type { import { isRecord } from "@oh-my-pi/pi-utils"; import { readForeignJsonRecords } from "./foreign-session-jsonl"; import type { ForeignSessionInfo, ForeignSessionStore } from "./foreign-session-store"; -import type { ModelChangeEntry, SessionEntry, SessionMessageEntry } from "./session-entries"; +import type { CompactionEntry, ModelChangeEntry, SessionEntry, SessionMessageEntry } from "./session-entries"; import { SessionManager } from "./session-manager"; interface CodexThreadRow { @@ -33,6 +33,12 @@ interface CodexIndexRow { updated_at: string; } +interface CodexCompaction { + summary: string; + replacementHistory?: Array>; + compactionItem?: Record; +} + interface ConvertedRecord { message?: UserMessage | AssistantMessage | ToolResultMessage; followingMessage?: ToolResultMessage; @@ -40,6 +46,7 @@ interface ConvertedRecord { rollbackTurns?: number; title?: string; timestamp?: number; + compaction?: CodexCompaction; } const EMPTY_USAGE: AssistantMessage["usage"] = { @@ -577,6 +584,28 @@ export class CodexSessionStore implements ForeignSessionStore { canonical.toolCalls, toolNames, ); + } else if (record.type === "compacted") { + const sourceSummary = stringField(record.payload, "message")?.trim(); + const rawReplacementHistory = record.payload.replacement_history; + const replacementHistory = + Array.isArray(rawReplacementHistory) && rawReplacementHistory.every(isRecord) + ? rawReplacementHistory + : undefined; + if (sourceSummary || replacementHistory) { + const compactionItem = replacementHistory?.findLast( + candidate => + (candidate.type === "compaction" && typeof candidate.encrypted_content === "string") || + candidate.type === "compaction_summary", + ); + item = { + compaction: { + summary: sourceSummary || "Context compacted by Codex.", + replacementHistory, + compactionItem, + }, + timestamp, + }; + } } if (!item) continue; if (item.rollbackTurns) rollback(converted, item.rollbackTurns); @@ -609,6 +638,30 @@ export class CodexSessionStore implements ForeignSessionStore { model: `openai-codex/${item.model}`, }; entry = modelEntry; + } else if (item.compaction) { + let preserveData: Record | undefined; + if (item.compaction.replacementHistory) { + const remoteCompaction: Record = { + provider: "openai-codex", + replacementHistory: item.compaction.replacementHistory, + }; + if (item.compaction.compactionItem) { + remoteCompaction.compactionItem = item.compaction.compactionItem; + } + preserveData = { openaiRemoteCompaction: remoteCompaction }; + } + const compactionEntry: CompactionEntry = { + type: "compaction", + id, + parentId, + timestamp, + summary: item.compaction.summary, + shortSummary: "Imported Codex compaction", + firstKeptEntryId: id, + tokensBefore: 0, + preserveData, + }; + entry = compactionEntry; } if (!entry) continue; manager.ingestReplicatedEntry(entry); diff --git a/packages/coding-agent/src/session/model-controls.ts b/packages/coding-agent/src/session/model-controls.ts index 4f195d03c..57198dc4d 100644 --- a/packages/coding-agent/src/session/model-controls.ts +++ b/packages/coding-agent/src/session/model-controls.ts @@ -3,6 +3,7 @@ import type { Model, ProviderSessionState, ServiceTier, ServiceTierByFamily, Ser import { clearAnthropicFastModeFallback, Effort, + isAnthropicFastModeFallbackDisabled, realizesPriorityServiceTier, resolveModelServiceTier, serviceTierFamily, @@ -596,8 +597,8 @@ export class ModelControls { let resolved: Effort | undefined; if (this.#host.magicKeywordEnabled("ultrathink") && containsUltrathink(promptText)) { // The user explicitly asked for maximum thinking; bypass the classifier - // (and its xhigh auto ceiling) and jump straight to the highest - // supported level for this model. + // (and the `providers.autoThinkingMaxEffort` ceiling) and jump straight + // to the highest supported level for this model. resolved = clampAutoThinkingEffort(model, Effort.Max); } else { const controller = new AbortController(); @@ -666,7 +667,11 @@ export class ModelControls { */ isFastModeActive(): boolean { const model = this.#model; - return !!model && realizesPriorityServiceTier(this.effectiveServiceTier(model), model); + if (!model || !realizesPriorityServiceTier(this.effectiveServiceTier(model), model)) return false; + if (model.provider === "anthropic") { + return !isAnthropicFastModeFallbackDisabled(this.#host.providerSessionState, model); + } + return true; } /** @@ -731,6 +736,9 @@ export class ModelControls { if (this.#serviceTierByFamily[family] === "priority") this.setServiceTierFamily(family, undefined); return true; } + if (family === "anthropic" && this.#serviceTierByFamily.anthropic === "priority") { + clearAnthropicFastModeFallback(this.#host.providerSessionState); + } this.setServiceTierFamily(family, "priority"); return true; } diff --git a/packages/coding-agent/src/session/session-advisors.ts b/packages/coding-agent/src/session/session-advisors.ts index b18d7db08..97e6751ed 100644 --- a/packages/coding-agent/src/session/session-advisors.ts +++ b/packages/coding-agent/src/session/session-advisors.ts @@ -2,6 +2,7 @@ import { Agent, type AgentMessage, type AgentTool, + type AgentToolContext, AppendOnlyContextManager, type CompactionSummaryMessage, countTokens, @@ -16,9 +17,11 @@ import { compactionContextTokens, createCompactionSummaryMessage, estimateTokens, + NativeCompactionError, prepareCompaction, type SessionMessageEntry, shouldCompact, + shouldUseProviderNativeCompaction, } from "@oh-my-pi/pi-agent-core/compaction"; import type { AssistantMessage, @@ -67,7 +70,8 @@ import { import { MODEL_ROLES } from "../config/model-roles"; import { serviceTierForAllFamilies, serviceTierSettingToTier } from "../config/service-tier"; import type { Settings } from "../config/settings"; -import { CursorExecHandlers } from "../cursor"; +import { CursorExecHandlers, type CursorMcpResourceAdapter } from "../cursor"; +import { bridgeToolMap } from "../cursor-bridge-tools"; import { estimateToolSchemaTokens } from "../modes/utils/context-usage"; import type { PlanModeState } from "../plan-mode/state"; import advisorSystemPrompt from "../prompts/advisor/system.md" with { type: "text" }; @@ -177,6 +181,36 @@ interface AdvisorRuntimeDescriptor { export interface SessionAdvisorsOptions { enabled: boolean; tools?: AgentTool[]; + /** + * Build a `grep` honoring a Cursor `pi_grep` frame's own context width and + * match cap. The advisor's tools are fixed instances carrying session + * defaults, so without this an advisor running against Cursor silently + * drops both fields — the same gap the primary bridge closes. + */ + createGrepTool?(options: { context?: number; totalMatchLimit?: number }): AgentTool | undefined; + /** + * Build the `replace`-mode `edit` a Cursor `pi_edit` frame needs. The + * advisor's own instance follows the configured `edit.mode` (`hashline` by + * default), whose schema the frame's `old_text`/`new_text` pairs do not + * match, so without this every native advisor edit fails validation. + */ + createEditTool?(): AgentTool | undefined; + /** + * The execute-time context the bridge's tools resolve approval from. + * + * `ExtensionToolWrapper` reads the approval mode, per-tool policies and + * `autoApprove` only from here; with none it falls back to `yolo` and empty + * policies, so a native frame would run past a configured `ask` or `deny`. + */ + getToolContext?: () => AgentToolContext | undefined; + /** + * The live MCP connections Cursor's resource frames answer from. + * + * Advisors share the session's connections and may hold tools from those + * same servers, so without this their frames report that every server + * advertises nothing. + */ + mcpResources?: CursorMcpResourceAdapter; watchdogPrompt?: string; sharedInstructions?: string; contextPrompt?: string; @@ -250,6 +284,10 @@ export class SessionAdvisors { readonly #host: SessionAdvisorsHost; #advisorEnabled: boolean; #advisorTools: AgentTool[] | undefined; + #advisorCreateGrepTool: SessionAdvisorsOptions["createGrepTool"]; + #advisorCreateEditTool: SessionAdvisorsOptions["createEditTool"]; + #advisorGetToolContext: SessionAdvisorsOptions["getToolContext"]; + #advisorMcpResources: SessionAdvisorsOptions["mcpResources"]; #advisorWatchdogPrompt: string | undefined; #advisorSharedInstructions: string | undefined; #advisorContextPrompt: string | undefined; @@ -272,6 +310,10 @@ export class SessionAdvisors { this.#host = host; this.#advisorEnabled = options.enabled; this.#advisorTools = options.tools; + this.#advisorCreateGrepTool = options.createGrepTool; + this.#advisorCreateEditTool = options.createEditTool; + this.#advisorGetToolContext = options.getToolContext; + this.#advisorMcpResources = options.mcpResources; this.#advisorWatchdogPrompt = options.watchdogPrompt; this.#advisorSharedInstructions = options.sharedInstructions; this.#advisorContextPrompt = options.contextPrompt; @@ -718,11 +760,29 @@ export class SessionAdvisors { // to delete workspace files it was never granted (issue #5680 review). const advisorCanMutateFiles = advisorToolMap.has("write") || advisorToolMap.has("edit"); if (advisorCanMutateFiles) availableAdvisorToolNames.add("delete"); + // `pi_edit` speaks `replace`'s `old_text`/`new_text` schema, which the + // advisor's ordinary `EditTool` (built at the session's configured + // `edit.mode`, `hashline` by default) does not accept. The bridge map + // swaps in a `replace` instance for the exec channel only — the + // advisor's own loop keeps the tool it was given — and only when + // `edit` was actually granted. const advisorCursorExecHandlers = new CursorExecHandlers({ cwd: this.#host.sessionManager.getCwd(), getCwd: () => this.#host.sessionManager.getCwd(), - tools: advisorToolMap, - allowNativeDelete: advisorCanMutateFiles, + tools: bridgeToolMap(advisorToolMap, this.#advisorCreateEditTool), + // Approval mode, per-tool policies and `autoApprove` live only on + // this context; without it every bridge tool resolves as `yolo`. + getToolContext: this.#advisorGetToolContext, + allowDirectFileMutation: advisorCanMutateFiles, + // Gated on the advisor's own grant: the factory builds a fresh + // tool, so handing it over unconditionally would give a roster + // without `grep` a search tool it was denied. + createGrepTool: advisorToolMap.has("grep") ? this.#advisorCreateGrepTool : undefined, + // Advisors share the session's live MCP connections, so their + // resource frames answer from the same catalog the primary sees. + // Not gated on a tool grant: reading what a server advertises is + // not the same permission as calling one of its tools. + mcpResources: this.#advisorMcpResources, }); const baseAdvisorStreamFn = this.#advisorStreamFn ?? streamSimple; const advisorStreamFn: StreamFn = (requestModel, context, options) => @@ -816,6 +876,7 @@ export class SessionAdvisors { maintainContext: (incomingTokens, signal) => this.#maintainAdvisorContext(advisorRef, incomingTokens, signal), obfuscator: this.#host.obfuscator, + getModelIdentity: () => formatModelString(advisorRef.agent.state.model), beginAdvisorUpdate: () => advisorRef.emissionGuard.beginUpdate(), onTurnError: (error, failedMessages, signal) => this.#recoverAdvisorTurn(advisorRef, error, failedMessages, signal), @@ -1341,6 +1402,7 @@ export class SessionAdvisors { let compactResult: CompactionResult | undefined; let lastError: unknown; + let nativeCompactionFailure: { error: NativeCompactionError; provider: string } | undefined; // Instrument the advisor's overflow-compaction one-shot like the primary // compaction path so the advisor model's maintenance call also emits spans. const telemetry = resolveTelemetry(agent.telemetry, advisorProviderSessionId); @@ -1354,6 +1416,14 @@ export class SessionAdvisors { for (const candidate of candidates) { const apiKey = await this.#host.modelRegistry.getApiKey(candidate, advisorProviderSessionId, { signal }); if (!apiKey) continue; + if ( + nativeCompactionFailure && + (candidate.provider !== nativeCompactionFailure.provider || + !shouldUseProviderNativeCompaction(candidate, compactionSettings)) + ) { + throw nativeCompactionFailure.error; + } + // The advisor overflow-compaction one-shot bypasses the advisor `Agent`, // so its installed metadata resolver never runs. Emit the same // `metadata.user_id` identity here (resolved per candidate provider, @@ -1385,10 +1455,18 @@ export class SessionAdvisors { break; } catch (error) { if (signal.aborted) throw error; + const id = AIError.classify(error, candidate.api); + if (error instanceof NativeCompactionError && !AIError.is(id, AIError.Flag.AuthFailed)) { + nativeCompactionFailure ??= { error, provider: candidate.provider }; + lastError = nativeCompactionFailure.error; + continue; + } lastError = error; } } + if (!compactResult && nativeCompactionFailure) throw nativeCompactionFailure.error; + if (!compactResult) { logger.warn("Advisor compaction failed, falling back to re-prime", { error: String(lastError) }); return true; diff --git a/packages/coding-agent/src/session/session-maintenance.ts b/packages/coding-agent/src/session/session-maintenance.ts index df65f9a32..6d2ee5db1 100644 --- a/packages/coding-agent/src/session/session-maintenance.ts +++ b/packages/coding-agent/src/session/session-maintenance.ts @@ -26,6 +26,7 @@ import { DEFAULT_SHAKE_CONFIG, effectiveReserveTokens, estimateTokens, + NativeCompactionError, prepareCompaction, resolveBudgetReserveTokens, resolveThresholdTokens, @@ -34,6 +35,7 @@ import { type SummaryOptions, shouldCompact, shouldUseOpenAiRemoteCompaction, + shouldUseProviderNativeCompaction, } from "@oh-my-pi/pi-agent-core/compaction"; import { DEFAULT_PRUNE_CONFIG, @@ -1458,10 +1460,18 @@ export class SessionMaintenance { const candidates = precomputedCandidates ?? this.#getCompactionModelCandidates(this.#host.modelRegistry.getAvailable()); const telemetry = resolveTelemetry(this.#host.agent.telemetry, this.#host.sessionId()); + let nativeCompactionFailure: { error: NativeCompactionError; provider: string } | undefined; for (const candidate of candidates) { const apiKey = await this.#host.modelRegistry.getApiKey(candidate, this.#host.sessionId()); if (!apiKey) continue; + if ( + nativeCompactionFailure && + (candidate.provider !== nativeCompactionFailure.provider || + !shouldUseProviderNativeCompaction(candidate, preparation.settings)) + ) { + throw nativeCompactionFailure.error; + } try { return await compact( @@ -1499,12 +1509,17 @@ export class SessionMaintenance { }, ); } catch (error) { - if (!AIError.is(AIError.classify(error, candidate.api), AIError.Flag.AuthFailed)) { - throw error; + const id = AIError.classify(error instanceof NativeCompactionError ? error.cause : error, candidate.api); + if (AIError.is(id, AIError.Flag.AuthFailed)) continue; + if (error instanceof NativeCompactionError) { + nativeCompactionFailure ??= { error, provider: candidate.provider }; + continue; } + throw error; } } + if (nativeCompactionFailure) throw nativeCompactionFailure.error; throw this.#buildCompactionAuthError(); } @@ -2477,6 +2492,7 @@ export class SessionMaintenance { const telemetry = resolveTelemetry(this.#host.agent.telemetry, this.#host.sessionId()); let compactResult: CompactionResult | undefined; let lastError: unknown; + let nativeCompactionFailure: { error: NativeCompactionError; provider: string } | undefined; codexCompaction = createCodexCompactionContext({ trigger: "auto", reason: "context_limit", @@ -2490,6 +2506,13 @@ export class SessionMaintenance { const hasMoreCandidates = candidateIndex < candidates.length - 1; const apiKey = await this.#host.modelRegistry.getApiKey(candidate, this.#host.sessionId()); if (!apiKey) continue; + if ( + nativeCompactionFailure && + (candidate.provider !== nativeCompactionFailure.provider || + !shouldUseProviderNativeCompaction(candidate, preparation.settings)) + ) { + throw nativeCompactionFailure.error; + } let attempt = 0; while (true) { @@ -2527,22 +2550,33 @@ export class SessionMaintenance { } const message = error instanceof Error ? error.message : String(error); - const id = AIError.classify(error, candidate.api); + const id = AIError.classify( + error instanceof NativeCompactionError ? error.cause : error, + candidate.api, + ); if (AIError.is(id, AIError.Flag.AuthFailed)) { - lastError = this.#buildCompactionAuthError(); + if (!nativeCompactionFailure) lastError = this.#buildCompactionAuthError(); break; } if (AIError.is(id, AIError.Flag.Timeout)) { + const nativeFailure = error instanceof NativeCompactionError; logger.warn( - hasMoreCandidates - ? "Auto-compaction summarization timed out, trying next model" - : "Auto-compaction summarization timed out, not retrying same model", + nativeFailure + ? "Provider-native auto-compaction timed out, preserving native failure" + : hasMoreCandidates + ? "Auto-compaction summarization timed out, trying next model" + : "Auto-compaction summarization timed out, not retrying same model", { error: message, model: `${candidate.provider}/${candidate.id}`, }, ); - lastError = error; + if (nativeFailure) { + nativeCompactionFailure ??= { error, provider: candidate.provider }; + lastError = nativeCompactionFailure.error; + } else { + lastError = error; + } break; } @@ -2554,7 +2588,12 @@ export class SessionMaintenance { AIError.is(id, AIError.Flag.Transient) || AIError.is(id, AIError.Flag.UsageLimit)); if (!shouldRetry) { - lastError = error; + if (error instanceof NativeCompactionError) { + nativeCompactionFailure ??= { error, provider: candidate.provider }; + lastError = nativeCompactionFailure.error; + } else { + lastError = error; + } break; } @@ -2564,6 +2603,11 @@ export class SessionMaintenance { // If retry delay is too long (>30s), try next candidate instead of waiting const maxAcceptableDelayMs = 30_000; if (delayMs > maxAcceptableDelayMs && hasMoreCandidates) { + if (error instanceof NativeCompactionError) { + nativeCompactionFailure ??= { error, provider: candidate.provider }; + lastError = nativeCompactionFailure.error; + break; + } logger.warn("Auto-compaction retry delay too long, trying next model", { delayMs, retryAfterMs, diff --git a/packages/coding-agent/src/session/settings-stream-fn.ts b/packages/coding-agent/src/session/settings-stream-fn.ts index 5df2c33f2..be58243ed 100644 --- a/packages/coding-agent/src/session/settings-stream-fn.ts +++ b/packages/coding-agent/src/session/settings-stream-fn.ts @@ -58,6 +58,7 @@ export function createSettingsAwareStreamFn(settings: Settings, base: StreamFn = textVerbosity: streamOptions?.textVerbosity ?? textVerbosity, streamFirstEventTimeoutMs: streamOptions?.streamFirstEventTimeoutMs ?? streamFirstEventTimeoutMs, streamIdleTimeoutMs: streamOptions?.streamIdleTimeoutMs ?? streamIdleTimeoutMs, + maxRetryDelayMs: streamOptions?.maxRetryDelayMs ?? settings.get("retry.maxDelayMs"), maxInFlightRequests: validateProviderMaxInFlightRequests( streamOptions?.maxInFlightRequests ?? settings.get("providers.maxInFlightRequests"), ), diff --git a/packages/coding-agent/src/session/turn-recovery.ts b/packages/coding-agent/src/session/turn-recovery.ts index 10e0166bf..8bc25054f 100644 --- a/packages/coding-agent/src/session/turn-recovery.ts +++ b/packages/coding-agent/src/session/turn-recovery.ts @@ -117,6 +117,7 @@ export interface TurnRecoveryHost { scheduleAgentContinue(options: { delayMs?: number; generation?: number; onError?: (error: unknown) => void }): void; waitForSessionMessagePersistence(message: AssistantMessage): Promise; appendSessionMessage(message: AssistantMessage): void; + persistedAssistantEntryId(message: AssistantMessage): string | undefined; sessionMessageAlreadyPersisted(message: AssistantMessage): boolean; setModelWithProviderSessionReset(model: Model): Promise; resetCurrentResponsesProviderSession(reason: string): void; @@ -770,16 +771,24 @@ export class TurnRecovery { discardAssistantTurn(assistantMessage: AssistantMessage): void { this.removeAssistantMessageFromActiveContext(assistantMessage); - const branchEntry = this.#host.sessionManager - .getBranch() - .slice() - .reverse() - .find( - entry => - entry.type === "message" && - entry.message.role === "assistant" && - this.#isSameAssistantMessage(entry.message as AssistantMessage, assistantMessage), - ); + const branch = this.#host.sessionManager.getBranch(); + const persistedEntryId = this.#host.persistedAssistantEntryId(assistantMessage); + const branchEntry = + (persistedEntryId === undefined + ? undefined + : branch.find( + entry => + entry.id === persistedEntryId && entry.type === "message" && entry.message.role === "assistant", + )) ?? + branch + .slice() + .reverse() + .find( + entry => + entry.type === "message" && + entry.message.role === "assistant" && + this.#isSameAssistantMessage(entry.message as AssistantMessage, assistantMessage), + ); if (!branchEntry) { return; } @@ -798,7 +807,9 @@ export class TurnRecovery { (left.timestamp === right.timestamp && left.provider === right.provider && left.model === right.model && - left.stopReason === right.stopReason) + left.stopReason === right.stopReason && + left.errorMessage === right.errorMessage && + Bun.hash(JSON.stringify(left.content)) === Bun.hash(JSON.stringify(right.content))) ); } diff --git a/packages/coding-agent/src/task/render.ts b/packages/coding-agent/src/task/render.ts index 2045a2174..585e4e740 100644 --- a/packages/coding-agent/src/task/render.ts +++ b/packages/coding-agent/src/task/render.ts @@ -49,6 +49,13 @@ interface TaskRenderContext { * commit-eligible rows do not repaint after entering native scrollback. */ frozen?: boolean; + /** + * Wall clock for time-derived rows (current-tool elapsed, retry countdown). + * The component freezes it once the block settles or any of its rows enter + * native scrollback, so identical-input rebuilds stay byte-identical with + * committed history. Absent: render with the live clock. + */ + nowMs?: number; } type TaskRenderOptions = RenderResultOptions & { renderContext?: TaskRenderContext }; @@ -888,6 +895,7 @@ function renderAgentProgress( frozen = false, seenNestedTasks?: WeakSet, nestedDepth = 0, + nowMs = Date.now(), ): string[] { const lines: string[] = []; @@ -964,7 +972,7 @@ function renderAgentProgress( toolLine += `: ${theme.fg("dim", previewLine(sanitizeText(toolDetail), 40))}`; } if (progress.currentToolStartMs) { - const elapsed = Date.now() - progress.currentToolStartMs; + const elapsed = nowMs - progress.currentToolStartMs; if (elapsed > 5000) { toolLine += `${theme.sep.dot}${theme.fg("warning", formatDuration(elapsed))}`; } @@ -986,7 +994,7 @@ function renderAgentProgress( // long until the next attempt. Without this, the parent UI would just // keep spinning while a child sleeps on a 3-hour provider rate-limit. if (progress.retryState && progress.status === "running") { - const remainingMs = Math.max(0, progress.retryState.startedAtMs + progress.retryState.delayMs - Date.now()); + const remainingMs = Math.max(0, progress.retryState.startedAtMs + progress.retryState.delayMs - nowMs); const waitLabel = remainingMs > 0 ? `in ${formatDuration(remainingMs)}` : "now"; const summary = `retrying ${progress.retryState.attempt}/${progress.retryState.maxAttempts} ${waitLabel}: ` + @@ -1079,6 +1087,7 @@ function renderAgentProgress( frozen, seenNestedTasks, nestedDepth, + nowMs, ); for (const line of nestedLines) { lines.push(`${continuePrefix}${line}`); @@ -1560,6 +1569,7 @@ export function renderResult( return framedBlock(theme, width => { const { expanded, isPartial, spinnerFrame } = options; const frozen = options.renderContext?.frozen === true; + const nowMs = options.renderContext?.nowMs ?? Date.now(); const lines: string[] = []; // Result rows win once any exist; progress rows for spawns without a @@ -1577,7 +1587,9 @@ export function renderResult( lines.push(formatHiddenProgressLine(ordered.slice(0, ordered.length - visible.length), theme)); } for (const progress of visible) { - lines.push(...renderAgentProgress(progress, "", " ", expanded, theme, spinnerFrame, frozen)); + lines.push( + ...renderAgentProgress(progress, "", " ", expanded, theme, spinnerFrame, frozen, undefined, 0, nowMs), + ); } } else if (details.results && details.results.length > 0) { const ordered = orderResultsForDisplay(details.results); @@ -1602,7 +1614,9 @@ export function renderResult( ) : []; for (const progress of supplementalProgress) { - lines.push(...renderAgentProgress(progress, "", " ", expanded, theme, spinnerFrame, frozen)); + lines.push( + ...renderAgentProgress(progress, "", " ", expanded, theme, spinnerFrame, frozen, undefined, 0, nowMs), + ); } const summaryParts: string[] = []; @@ -1742,6 +1756,7 @@ function renderNestedTaskTree( frozen = false, seen: WeakSet = new WeakSet(), depth = 0, + nowMs = Date.now(), ): string[] { const lines: string[] = []; for (const details of detailsList) { @@ -1788,6 +1803,7 @@ function renderNestedTaskTree( frozen, seen, depth + 1, + nowMs, ), ); }); diff --git a/packages/coding-agent/src/thinking.ts b/packages/coding-agent/src/thinking.ts index 4138779fc..565c62060 100644 --- a/packages/coding-agent/src/thinking.ts +++ b/packages/coding-agent/src/thinking.ts @@ -191,7 +191,7 @@ export interface ConfiguredThinkingLevelMetadata { const AUTO_THINKING_METADATA: ConfiguredThinkingLevelMetadata = { value: AUTO_THINKING, label: "auto", - description: "Auto-detect per prompt (low–xhigh)", + description: "Auto-detect per prompt", }; /** @@ -236,6 +236,12 @@ export function parseCliThinkingLevel(value: string | null | undefined): Configu * above Low (falling back to the full supported set only when the model maxes * out below Low). Within that pool the request snaps to the highest level not * exceeding it, or the pool minimum when the request is below the pool. + * `ceiling` bounds the pool from above, so a policy ceiling survives the model + * clamp: a sparse ladder such as `["max"]` must not snap an `xhigh` request up + * to `max`. The Low floor is resolved against the model's own ladder *before* + * the ceiling applies — a ceiling that hides every tier at or above Low means + * there is nothing legal to pick (`undefined`), not a licence to fall through + * to a sub-Low tier the model happens to expose. * * Returns `undefined` for reasoning-capable models without a controllable * effort surface (`thinking.efforts` empty — e.g. devin-agent models, where @@ -244,12 +250,19 @@ export function parseCliThinkingLevel(value: string | null | undefined): Configu * forward a concrete effort that would then trip {@link requireSupportedEffort} * downstream. */ -export function clampAutoThinkingEffort(model: Model | undefined, effort: Effort): Effort | undefined { +export function clampAutoThinkingEffort( + model: Model | undefined, + effort: Effort, + ceiling: Effort = Effort.Max, +): Effort | undefined { const supported = model ? getSupportedEfforts(model) : THINKING_EFFORTS; if (supported.length === 0) return undefined; const lowIndex = THINKING_EFFORTS.indexOf(Effort.Low); - const eligible = supported.filter(level => THINKING_EFFORTS.indexOf(level) >= lowIndex); - const pool = eligible.length > 0 ? eligible : supported; + const ceilingIndex = THINKING_EFFORTS.indexOf(ceiling); + const atOrAboveLow = supported.filter(level => THINKING_EFFORTS.indexOf(level) >= lowIndex); + const floored = atOrAboveLow.length > 0 ? atOrAboveLow : supported; + const pool = floored.filter(level => THINKING_EFFORTS.indexOf(level) <= ceilingIndex); + if (pool.length === 0) return undefined; const requestedIndex = THINKING_EFFORTS.indexOf(effort); let chosen = pool[0]; for (const candidate of pool) { @@ -351,14 +364,20 @@ export function modelSupportsEffortCeiling(model: Model, ceiling: Effort): boole /** * The provisional concrete level shown while `auto` is configured but before a - * turn has been classified. Prefers the model's `defaultLevel`, otherwise High, - * clamped into the auto range. Auto never provisions {@link Effort.Max} (the - * classifier ceiling is XHigh; only an explicit user request reaches Max), so a - * `defaultLevel` of `max` is capped at XHigh before clamping. Returns - * `undefined` for non-reasoning models. + * turn has been classified, and the fallback when classification fails. Prefers + * the model's `defaultLevel`, otherwise High, clamped into the auto range. + * + * Deliberately stays below {@link Effort.Max}: the placeholder must not bill the + * top tier for a turn nobody classified, so XHigh is passed as a hard ceiling + * rather than only capping the preferred level — otherwise a sparse `["max"]` + * ladder would snap straight back up. A model whose ladder offers nothing at or + * below XHigh therefore has no provisional level, and `auto` leaves the current + * one in place. Classification itself may still resolve Max on models that + * expose the tier when the user opts in. Returns `undefined` for non-reasoning + * models. */ export function resolveProvisionalAutoLevel(model: Model | undefined): Effort | undefined { if (!model?.reasoning) return undefined; const preferred = model.thinking?.defaultLevel ?? Effort.High; - return clampAutoThinkingEffort(model, preferred === Effort.Max ? Effort.XHigh : preferred); + return clampAutoThinkingEffort(model, preferred === Effort.Max ? Effort.XHigh : preferred, Effort.XHigh); } diff --git a/packages/coding-agent/src/tools/browser.ts b/packages/coding-agent/src/tools/browser.ts index 92f358fde..eda99d7ef 100644 --- a/packages/coding-agent/src/tools/browser.ts +++ b/packages/coding-agent/src/tools/browser.ts @@ -95,6 +95,11 @@ function resolveBrowserKind(params: BrowserParams, session: ToolSession): Browse const exe = resolveToCwd(app.path, session.cwd); return { kind: "spawned", path: exe }; } + // A configured endpoint is a default, not an override: explicit app options win. + const configuredCdpUrl = (session.settings.get("browser.cdpUrl") as string | undefined)?.trim(); + if (configuredCdpUrl) { + return { kind: "connected", cdpUrl: configuredCdpUrl.replace(/\/+$/, "") }; + } const cmuxKind = resolveCmuxKind({ settingEnabled: session.settings.get("browser.cmux") as boolean | undefined, }); diff --git a/packages/coding-agent/src/tools/checkpoint.ts b/packages/coding-agent/src/tools/checkpoint.ts index 9a485fdda..ba8ccfe33 100644 --- a/packages/coding-agent/src/tools/checkpoint.ts +++ b/packages/coding-agent/src/tools/checkpoint.ts @@ -50,11 +50,6 @@ export interface RewindToolDetails { meta?: OutputMeta; } -function isTopLevelSession(session: ToolSession): boolean { - const depth = session.taskDepth; - return depth === undefined || depth === 0; -} - export class CheckpointTool implements AgentTool { readonly name = "checkpoint"; readonly approval = "read" as const; @@ -71,7 +66,6 @@ export class CheckpointTool implements AgentTool, _context?: AgentToolContext, ): Promise> { - if (!isTopLevelSession(this.session)) { - throw new ToolError("Checkpoint not available in subagents."); - } if (this.session.getCheckpointState?.()) { throw new ToolError("Checkpoint already active."); } @@ -117,7 +108,6 @@ export class RewindTool implements AgentTool, _context?: AgentToolContext, ): Promise> { - if (!isTopLevelSession(this.session)) { - throw new ToolError("Checkpoint not available in subagents."); - } if (!this.session.getCheckpointState?.()) { if (this.session.getLastCompletedRewind?.()) { throw new ToolError( diff --git a/packages/coding-agent/src/tools/grep.ts b/packages/coding-agent/src/tools/grep.ts index e28756a14..353d5b951 100644 --- a/packages/coding-agent/src/tools/grep.ts +++ b/packages/coding-agent/src/tools/grep.ts @@ -884,6 +884,22 @@ export interface GrepToolDetails { type SearchParams = typeof searchSchema.infer; +/** + * Construction-time overrides for callers that are not the model. + * + * The model-facing schema deliberately does not grow these: they exist for + * wire bridges (the Cursor `pi_grep` frame) whose protocol carries an explicit + * context width and total match cap, and which would otherwise have to drop + * them. Unset means "use the session settings / built-in caps" — the behavior + * every model-issued call keeps. + */ +export interface GrepToolOptions { + /** Overrides `grep.contextBefore`/`grep.contextAfter` for every call on this instance. */ + context?: number; + /** Caps total surfaced matches. Applied on top of the built-in per-file and file-window caps, never above them. */ + totalMatchLimit?: number; +} + export class GrepTool implements AgentTool { readonly name = "grep"; readonly approval = (args: unknown): ToolTier => { @@ -897,7 +913,17 @@ export class GrepTool implements AgentTool readonly parameters = searchSchema; readonly strict = true; - constructor(private readonly session: ToolSession) { + readonly #contextOverride?: number; + readonly #totalMatchLimit?: number; + + constructor( + private readonly session: ToolSession, + options?: GrepToolOptions, + ) { + const context = options?.context; + this.#contextOverride = context !== undefined ? Math.max(0, Math.floor(context)) : undefined; + const total = options?.totalMatchLimit; + this.#totalMatchLimit = total !== undefined ? Math.max(1, Math.floor(total)) : undefined; const displayMode = resolveFileDisplayMode(session); this.description = prompt.render(grepDescription, { IS_HL_MODE: displayMode.hashLines, @@ -978,8 +1004,8 @@ export class GrepTool implements AgentTool `or pass a UTF-8 text member.`, ); } - const normalizedContextBefore = this.session.settings.get("grep.contextBefore"); - const normalizedContextAfter = this.session.settings.get("grep.contextAfter"); + const normalizedContextBefore = this.#contextOverride ?? this.session.settings.get("grep.contextBefore"); + const normalizedContextAfter = this.#contextOverride ?? this.session.settings.get("grep.contextAfter"); const ignoreCase = !(caseSensitive ?? true); const useGitignore = gitignore ?? true; const patternHasNewline = normalizedPattern.includes("\n") || normalizedPattern.includes("\\n"); @@ -1297,9 +1323,22 @@ export class GrepTool implements AgentTool // Single-file scopes can't paginate — there is one file by definition. const canPaginate = isMultiScope; const skipFiles = canPaginate ? Math.min(normalizedSkip, totalFiles) : 0; - const windowFiles = canPaginate ? fileOrder.slice(skipFiles, skipFiles + DEFAULT_FILE_LIMIT) : fileOrder; - const fileLimitReached = canPaginate && totalFiles > skipFiles + DEFAULT_FILE_LIMIT; + // A caller with a total match cap is not paginating: the cap bounds the + // output, and the only consumer that sets one (`pi_grep`) has no `skip` + // field to follow a "use skip=N" suggestion with. Windowing it to the + // first 20 files would silently return fewer matches than it asked for + // while reporting the cap as unreached. + // + // The window is cap+1 files, not cap: with one match per file, a cap + // of N over exactly N files is complete, while over N+1 files it is + // clipped — and only reading that extra file distinguishes the two. + // The cap below then does the trimming and records that it bit, so + // `match_limit_reached` reaches the frame set. + const fileWindow = this.#totalMatchLimit !== undefined ? this.#totalMatchLimit + 1 : DEFAULT_FILE_LIMIT; + const windowFiles = canPaginate ? fileOrder.slice(skipFiles, skipFiles + fileWindow) : fileOrder; + const fileLimitReached = canPaginate && totalFiles > skipFiles + fileWindow; const selectedMatches: GrepMatch[] = []; + let totalMatchLimitReached = false; if (windowFiles.length > 0) { const lists = windowFiles.map(file => matchesByPath.get(file) ?? []); const cursors = new Array(lists.length).fill(0); @@ -1313,6 +1352,14 @@ export class GrepTool implements AgentTool } } } + // Round-robin above interleaves files for diversity, so the cap is + // applied after selection rather than as a per-list bound: trimming + // mid-rotation would silently favour whichever files sort first. + const cap = this.#totalMatchLimit; + if (cap !== undefined && selectedMatches.length > cap) { + selectedMatches.length = cap; + totalMatchLimitReached = true; + } } const nextSkip = skipFiles + windowFiles.length; const limitMessage = fileLimitReached @@ -1503,7 +1550,12 @@ export class GrepTool implements AgentTool const output = truncation.content; const displayText = displayLines.join("\n"); const truncated = Boolean( - fileLimitReached || perFileLimitReached || result.limitReached || truncation.truncated || linesTruncated, + fileLimitReached || + perFileLimitReached || + totalMatchLimitReached || + result.limitReached || + truncation.truncated || + linesTruncated, ); const details: GrepToolDetails = { scopePath, @@ -1517,8 +1569,12 @@ export class GrepTool implements AgentTool count: fileMatchCounts.get(path) ?? 0, })), truncated, - fileLimitReached: fileLimitReached ? DEFAULT_FILE_LIMIT : undefined, - perFileLimitReached: perFileLimitReached ? perFileMatchCap : undefined, + fileLimitReached: fileLimitReached ? fileWindow : undefined, + perFileLimitReached: totalMatchLimitReached + ? this.#totalMatchLimit + : perFileLimitReached + ? perFileMatchCap + : undefined, displayContent: displayText, missingPaths: missingPaths.length > 0 ? missingPaths : undefined, }; diff --git a/packages/coding-agent/src/tools/index.ts b/packages/coding-agent/src/tools/index.ts index 51531e593..21c38dff6 100644 --- a/packages/coding-agent/src/tools/index.ts +++ b/packages/coding-agent/src/tools/index.ts @@ -509,6 +509,18 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P // unreachable, in which case eval dispatches exclusively to the others. const allowEval = effectivePythonAllowed || allowJs || effectiveRubyAllowed || effectiveJuliaAllowed; + // Checkpoint and rewind are a pair: listing one without the other strands + // the agent (it can checkpoint but not rewind, or vice versa). Auto-include + // the sister tool so a one-sided frontmatter `tools:` entry still works. + // Unlike the AST/auto-learn convenience auto-includes below, this is a + // safety pairing — it applies to restricted sessions too. + if (requestedTools && session.settings.get("checkpoint.enabled")) { + if (requestedTools.includes("checkpoint") && !requestedTools.includes("rewind")) { + requestedTools.push("rewind"); + } else if (requestedTools.includes("rewind") && !requestedTools.includes("checkpoint")) { + requestedTools.push("checkpoint"); + } + } // Auto-include AST counterparts when their text-based sibling is present. // Restricted callers own the active list and must not have it widened. if (requestedTools && !restrictToolNames) { @@ -582,7 +594,11 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P if (name === "ask") return session.settings.get("ask.enabled"); if (name === "browser") return session.settings.get("browser.enabled"); if (name === "computer") return session.settings.get("computer.enabled"); - if (name === "checkpoint" || name === "rewind") return session.settings.get("checkpoint.enabled"); + if (name === "checkpoint" || name === "rewind") + return ( + session.settings.get("checkpoint.enabled") && + ((session.taskDepth ?? 0) === 0 || requestedTools !== undefined) + ); if (name === "hub") { return ( !restrictToolNames && session.enableIrc !== false && isIrcEnabled(session.settings, session.taskDepth ?? 0) @@ -592,11 +608,15 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P return ["hindsight", "mnemopi"].includes(session.settings.get("memory.backend") ?? ""); } if (name === "memory_edit") return session.settings.get("memory.backend") === "mnemopi"; - if (name === "manage_skill") return session.settings.get("autolearn.enabled") && (session.taskDepth ?? 0) === 0; + if (name === "manage_skill") + return ( + session.settings.get("autolearn.enabled") && + ((session.taskDepth ?? 0) === 0 || requestedTools !== undefined) + ); if (name === "learn") { return ( session.settings.get("autolearn.enabled") && - (session.taskDepth ?? 0) === 0 && + ((session.taskDepth ?? 0) === 0 || requestedTools !== undefined) && ["hindsight", "mnemopi", "local"].includes(session.settings.get("memory.backend") ?? "") ); } diff --git a/packages/coding-agent/src/tools/path-utils.ts b/packages/coding-agent/src/tools/path-utils.ts index 8a8dc72ed..aea44a068 100644 --- a/packages/coding-agent/src/tools/path-utils.ts +++ b/packages/coding-agent/src/tools/path-utils.ts @@ -523,6 +523,94 @@ export function resolveToCwd(filePath: string, cwd: string): string { return path.resolve(cwd, expanded); } +/** + * Resolve a path that MUST stay inside `cwd`, or `null` when it would escape. + * + * {@link resolveToCwd} deliberately honors absolute paths, `~`, and `..` — + * correct for a path a user typed, wrong for one a remote peer supplied. + * Callers handling untrusted input (Cursor's `download_path`) use this instead: + * only a non-empty relative path landing under the live cwd is accepted, so + * neither `/etc/passwd` nor `../../escape` can be written through. + * + * The lexical check alone is not containment: a symlink inside the workspace + * can point anywhere, so `out/config` under a `ws/out -> /elsewhere` link is + * relative, `..`-free, and still writes outside. Both the target and its + * deepest existing ancestor are therefore realpath-resolved — the ancestor + * because a download names a file that does not exist yet, so the link in its + * path is the only thing that can be resolved before the write. + * + * The cwd itself is rejected: a download names a file, never the directory. + */ +export function confineToWorkspace(filePath: string, cwd: string): string | null { + if (!filePath || path.isAbsolute(filePath)) return null; + // `~` expands to an absolute path, and an internal URL is not a filesystem + // target at all; neither is a relative workspace path. + if (filePath.startsWith("~") || isInternalUrlPath(filePath)) return null; + const root = path.resolve(cwd); + const resolved = path.resolve(root, filePath); + if (!isUnderRootLexical(resolved, root)) return null; + + // A workspace reached through a link of its own is legitimate (/tmp on + // macOS), so the real root is the comparison basis. An unresolvable root is + // not a workspace to contain anything in. + const realRoot = tryRealpath(root); + if (!realRoot) return null; + + // An existing target is authoritative: resolve it outright. + const realTarget = tryRealpath(resolved); + if (realTarget) return isUnderRootLexical(realTarget, realRoot) ? resolved : null; + + // `realpath` also fails on a *dangling* link, and a write follows that link + // to wherever it points. Chasing the chain to decide would mean + // reimplementing symlink resolution (multi-hop, relative hops, loops, and + // a TOCTOU window against a link that can be re-pointed between the check + // and the write). A download names a file to create, so a path that is + // already an unresolvable link is refused outright — the one shape where + // "cannot tell where this lands" is the whole answer. + if (isSymlink(resolved)) return null; + + // Otherwise walk up to the deepest ancestor that does exist and check that, + // then re-apply the segments below it. Those segments are `..`-free by the + // lexical check above, so they cannot climb back out. + let ancestor = path.dirname(resolved); + const tail: string[] = [path.basename(resolved)]; + for (;;) { + const real = tryRealpath(ancestor); + if (real) { + return isUnderRootLexical(path.join(real, ...tail.reverse()), realRoot) ? resolved : null; + } + const parent = path.dirname(ancestor); + // Ran past the root without finding anything real: the workspace itself + // resolved above, so this cannot happen unless it vanished mid-check. + if (parent === ancestor || !isUnderRootLexical(ancestor, root)) return null; + tail.push(path.basename(ancestor)); + ancestor = parent; + } +} + +/** Whether `target` is a strict descendant of `root`, ignoring symlinks. */ +function isUnderRootLexical(target: string, root: string): boolean { + const relative = path.relative(root, target); + return !!relative && !relative.startsWith("..") && !path.isAbsolute(relative); +} + +function tryRealpath(target: string): string | null { + try { + return fs.realpathSync.native(target); + } catch { + return null; + } +} + +/** Whether the path itself is a symlink, without following it. */ +function isSymlink(target: string): boolean { + try { + return fs.lstatSync(target).isSymbolicLink(); + } catch { + return false; + } +} + export function formatPathRelativeToCwd( filePath: string, cwd: string, diff --git a/packages/coding-agent/src/tools/read.ts b/packages/coding-agent/src/tools/read.ts index d6f0e0bee..95cd116a7 100644 --- a/packages/coding-agent/src/tools/read.ts +++ b/packages/coding-agent/src/tools/read.ts @@ -735,6 +735,8 @@ export interface ReadToolDetails { method?: string; notes?: string[]; meta?: OutputMeta; + /** Full on-disk byte size recorded before applying a file range. */ + fileSize?: number; /** Raw text + start line for user-visible TUI rendering, set when content is text-like. * Mirrors the same lines the model receives but without hashline/line-number prefixes, * so the TUI can render the file content with its own gutter without re-parsing the formatted text. */ @@ -2959,6 +2961,7 @@ export class ReadTool implements AgentTool { } } + details.fileSize = fileSize; this.#markMarkdownContentType(details, absolutePath); if (suffixResolution) { details.suffixResolution = suffixResolution; diff --git a/packages/coding-agent/src/utils/changelog.ts b/packages/coding-agent/src/utils/changelog.ts index b018fda00..456fc23f2 100644 --- a/packages/coding-agent/src/utils/changelog.ts +++ b/packages/coding-agent/src/utils/changelog.ts @@ -1,6 +1,7 @@ import * as path from "node:path"; import { getLastChangelogVersionPath, isEnoent, logger } from "@oh-my-pi/pi-utils"; import bundledChangelogPath from "../../CHANGELOG.md" with { type: "file" }; +import type { SettingValue } from "../config/settings"; export interface ChangelogEntry { major: number; @@ -28,6 +29,101 @@ export interface StartupChangelogSelection { persistCurrentVersion: boolean; truncated: boolean; selectedEntries: number; + totalUnseenEntries: number; + latestVersion: string | undefined; + changeCount: number; + categoryCounts: Record; +} + +const CHANGELOG_CATEGORY_ORDER = [ + "Breaking Changes", + "Added", + "Changed", + "Deprecated", + "Removed", + "Fixed", + "Security", +] as const; + +function emptyStartupSelection(persistCurrentVersion: boolean): StartupChangelogSelection { + return { + markdown: undefined, + persistCurrentVersion, + truncated: false, + selectedEntries: 0, + totalUnseenEntries: 0, + latestVersion: undefined, + changeCount: 0, + categoryCounts: {}, + }; +} + +function summarizeChangelogEntries(entries: readonly ChangelogEntry[]): { + changeCount: number; + categoryCounts: Record; +} { + const categoryCounts: Record = {}; + let changeCount = 0; + + for (const entry of entries) { + let category: string | undefined; + for (const line of entry.content.split("\n")) { + const heading = line.match(/^###\s+(.+?)\s*$/); + if (heading) { + category = heading[1]; + continue; + } + if (!category || !/^-\s+\S/.test(line)) continue; + categoryCounts[category] = (categoryCounts[category] ?? 0) + 1; + changeCount++; + } + } + + return { changeCount, categoryCounts }; +} + +function categoryLabel(category: string, count: number): string { + if (category === "Breaking Changes") { + return count === 1 ? "breaking change" : "breaking changes"; + } + return category.toLowerCase(); +} + +/** Format the compact, deterministic startup update notice. */ +export function formatStartupChangelogSummary(selection: StartupChangelogSelection): string { + const latestVersion = selection.latestVersion; + if (!latestVersion || selection.selectedEntries === 0) { + return "Updated omp. Use /changelog for recent changes."; + } + + const releaseCount = selection.selectedEntries; + const changeCount = selection.changeCount; + const releaseWord = releaseCount === 1 ? "release" : "releases"; + const changeWord = changeCount === 1 ? "change" : "changes"; + const firstLine = + releaseCount === 1 + ? `Updated to v${latestVersion} · ${changeCount} ${changeWord} in 1 release` + : `Updated to v${latestVersion} · ${changeCount} ${changeWord} across ${releaseCount} ${releaseWord}`; + + const orderedCategories = [ + ...CHANGELOG_CATEGORY_ORDER.filter(category => selection.categoryCounts[category]), + ...Object.keys(selection.categoryCounts) + .filter(category => !(CHANGELOG_CATEGORY_ORDER as readonly string[]).includes(category)) + .sort(), + ]; + const breakdown = orderedCategories + .map( + category => + `${selection.categoryCounts[category]} ${categoryLabel(category, selection.categoryCounts[category])}`, + ) + .join(" · "); + const omittedReleases = selection.totalUnseenEntries - selection.selectedEntries; + const detailHint = + omittedReleases > 0 + ? `+${omittedReleases} earlier ${omittedReleases === 1 ? "release" : "releases"} · Use /changelog full for history.` + : "Use /changelog for details."; + + return breakdown ? `${firstLine}\n${breakdown} · ${detailHint}` : `${firstLine}\n${detailHint}`; } /** @@ -177,16 +273,17 @@ export function selectStartupChangelog( ): StartupChangelogSelection { const parsedLastVersion = parseChangelogVersion(lastVersion); if (!parsedLastVersion) { - return { markdown: undefined, persistCurrentVersion: true, truncated: false, selectedEntries: 0 }; + return emptyStartupSelection(true); } const markerVersion = lastVersion ?? ""; if (markerVersion === currentVersion) { - return { markdown: undefined, persistCurrentVersion: false, truncated: false, selectedEntries: 0 }; + return emptyStartupSelection(false); } - const newEntries = getNewEntries(entries, markerVersion).slice(0, RECENT_CHANGELOG_ENTRY_LIMIT); + const allNewEntries = getNewEntries(entries, markerVersion); + const newEntries = allNewEntries.slice(0, RECENT_CHANGELOG_ENTRY_LIMIT); if (newEntries.length === 0) { - return { markdown: undefined, persistCurrentVersion: false, truncated: false, selectedEntries: 0 }; + return emptyStartupSelection(false); } const rendered = renderChangelogEntries(newEntries, { @@ -194,14 +291,57 @@ export function selectStartupChangelog( truncationHint: STARTUP_CHANGELOG_FULL_HINT, oldestFirst: false, }); + const summary = summarizeChangelogEntries(newEntries); + const latestEntry = newEntries[0]; return { markdown: rendered.markdown, persistCurrentVersion: true, truncated: rendered.truncated, selectedEntries: newEntries.length, + totalUnseenEntries: allNewEntries.length, + latestVersion: latestEntry ? `${latestEntry.major}.${latestEntry.minor}.${latestEntry.patch}` : undefined, + ...summary, }; } +/** + * Resolve and persist the automatic startup changelog decision. + * + * Hidden mode advances the marker only for an upgrade, so downgrades do not + * erase knowledge of a newer version the user has already seen. + */ +export async function resolveStartupChangelogForDisplay(options: { + mode: SettingValue<"startup.changelogMode">; + currentVersion: string; + changelogPath?: string; + agentDir?: string; +}): Promise { + const lastVersion = await readLastChangelogVersion(options.agentDir); + const parsedLastVersion = parseChangelogVersion(lastVersion); + if (!parsedLastVersion) { + await writeLastChangelogVersion(options.currentVersion, options.agentDir); + return undefined; + } + if (lastVersion === options.currentVersion) { + // Steady state: skip the changelog file read and parse. + return undefined; + } + if (options.mode === "hidden") { + const currentVersion = parseChangelogVersion(options.currentVersion); + if (currentVersion && compareVersions(currentVersion, parsedLastVersion) > 0) { + await writeLastChangelogVersion(options.currentVersion, options.agentDir); + } + return undefined; + } + + const entries = await parseChangelog(options.changelogPath); + const startupChangelog = selectStartupChangelog(entries, lastVersion, options.currentVersion); + if (startupChangelog.persistCurrentVersion) { + await writeLastChangelogVersion(options.currentVersion, options.agentDir); + } + return startupChangelog.markdown ? startupChangelog : undefined; +} + // Re-export getChangelogPath from paths.ts for convenience export { getChangelogPath } from "../config"; diff --git a/packages/coding-agent/src/web/search/types.ts b/packages/coding-agent/src/web/search/types.ts index efa3ce5ed..f830cc06b 100644 --- a/packages/coding-agent/src/web/search/types.ts +++ b/packages/coding-agent/src/web/search/types.ts @@ -32,7 +32,7 @@ export const SEARCH_PROVIDER_OPTIONS = [ }, { value: "xai", label: "xAI", description: "Grok web search via xAI Responses API (requires XAI_API_KEY)" }, { value: "zai", label: "Z.AI", description: "Calls Z.AI webSearchPrime MCP" }, - { value: "exa", label: "Exa", description: "Uses Exa API when EXA_API_KEY is set; falls back to Exa MCP" }, + { value: "exa", label: "Exa", description: "API via /login exa or EXA_API_KEY; explicit keyless fallback via MCP" }, { value: "tinyfish", label: "TinyFish", description: "Requires TINYFISH_API_KEY" }, { value: "jina", label: "Jina", description: "Requires JINA_API_KEY" }, { value: "kagi", label: "Kagi", description: "Requires KAGI_API_KEY and Kagi Search API beta access" }, diff --git a/packages/coding-agent/test/advisor-context-maintenance.test.ts b/packages/coding-agent/test/advisor-context-maintenance.test.ts index 423565e7e..a9755db38 100644 --- a/packages/coding-agent/test/advisor-context-maintenance.test.ts +++ b/packages/coding-agent/test/advisor-context-maintenance.test.ts @@ -1,8 +1,10 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test"; import { Agent, type AgentMessage, type CompactionSummaryMessage, countTokens } from "@oh-my-pi/pi-agent-core"; +import * as compactionModule from "@oh-my-pi/pi-agent-core/compaction"; import { calculateContextTokens, estimateTokens, resolveThresholdTokens } from "@oh-my-pi/pi-agent-core/compaction"; import type { AssistantMessage } from "@oh-my-pi/pi-ai"; import { createMockModel, type MockModel, registerMockApi } from "@oh-my-pi/pi-ai/providers/mock"; +import { getBundledModel } from "@oh-my-pi/pi-catalog/models"; import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { estimateToolSchemaTokens } from "@oh-my-pi/pi-coding-agent/modes/utils/context-usage"; @@ -122,6 +124,63 @@ describe("AgentSession advisor context maintenance", () => { }; } + function createAdvisorFallbackHarness(options?: { sameProviderNativeEnabled?: boolean }) { + const primaryMock = createMockModel({ + provider: "anthropic", + responses: [{ content: ["primary complete"] }], + }); + const advisorMock = createMockModel({ + provider: "openai", + responses: [{ content: ["advisor reviewed current update"] }], + }); + const nativeModel = getBundledModel("openai", "gpt-5"); + const sameProviderBase = getBundledModel("openai", "gpt-5-mini"); + const sameProviderModel = + sameProviderBase && options?.sameProviderNativeEnabled === false + ? { ...sameProviderBase, remoteCompaction: { ...sameProviderBase.remoteCompaction, enabled: false } } + : sameProviderBase; + const crossProviderModel = getBundledModel("anthropic", "claude-sonnet-4-5"); + if (!nativeModel || !sameProviderModel || !crossProviderModel) { + throw new Error("Expected bundled compaction models"); + } + + authStorage.setRuntimeApiKey(nativeModel.provider, "openai-key"); + const modelRegistry = new ModelRegistry(authStorage, tempDir.join("models.yml")); + const settings = Settings.isolated({ + "advisor.syncBacklog": "1", + "compaction.enabled": true, + "compaction.strategy": "context-full", + "contextPromotion.enabled": false, + }); + settings.setModelRole("advisor", `${nativeModel.provider}/${nativeModel.id}`); + settings.setModelRole("smol", `${sameProviderModel.provider}/${sameProviderModel.id}`); + settings.setModelRole("slow", `${crossProviderModel.provider}/${crossProviderModel.id}`); + const agent = new Agent({ + getApiKey: () => "test-key", + initialState: { model: primaryMock, systemPrompt: [], tools: [] }, + streamFn: primaryMock.stream, + }); + session = new AgentSession({ + agent, + sessionManager: SessionManager.inMemory(), + settings, + modelRegistry, + advisorTools: [], + advisorStreamFn: advisorMock.stream, + }); + expect(session.setAdvisorEnabled(true)).toBe(true); + const advisor = session.getAdvisorAgent(); + if (!advisor) throw new Error("Expected advisor agent to be active"); + advisor.setModel(nativeModel); + const apiKeySpy = vi.spyOn(modelRegistry, "getApiKey").mockResolvedValue("test-key"); + vi.spyOn(modelRegistry, "getAvailable").mockReturnValue([nativeModel, sameProviderModel, crossProviderModel]); + advisor.state.messages.push( + usageAnchor(advisorMock, Date.now() - 2_000), + usageAnchor(advisorMock, Date.now() - 1_000), + ); + return { advisor, apiKeySpy, crossProviderModel, nativeModel, sameProviderModel, settings }; + } + it("maintains a 371,200-token cached advisor context before the 372,000-token window", async () => { const { advisor, advisorMock, settings } = createHarness(); const anchor = usageAnchor(advisorMock, Date.now() - 1_000, 0.5); @@ -353,4 +412,144 @@ describe("AgentSession advisor context maintenance", () => { expect((JSON.parse(userId) as { session_id?: string }).session_id).toBe(advisor.sessionId); } }); + + it("continues same-provider advisor candidates but stops before crossing providers on non-auth failure", async () => { + const { advisor, crossProviderModel, nativeModel, sameProviderModel } = createAdvisorFallbackHarness(); + const compactSpy = vi.spyOn(compactionModule, "compact").mockImplementation(async (preparation, model) => { + if (model.provider === nativeModel.provider || model.provider === sameProviderModel.provider) { + throw new compactionModule.NativeCompactionError(new Error("V2 native compaction transport failed")); + } + if (model.provider !== crossProviderModel.provider || model.id !== crossProviderModel.id) { + throw new Error(`Unexpected compaction model ${model.provider}/${model.id}`); + } + return { + summary: "cross-provider summary", + shortSummary: "cross-provider", + firstKeptEntryId: preparation.firstKeptEntryId, + tokensBefore: 42, + }; + }); + + await session.prompt("small current update"); + + expect(compactSpy.mock.calls.map(([, model]) => `${model.provider}/${model.id}`)).toEqual([ + `${nativeModel.provider}/${nativeModel.id}`, + `${sameProviderModel.provider}/${sameProviderModel.id}`, + ]); + expect(JSON.stringify(advisor.state.messages)).toContain("prior advisor output"); + }); + + it("applies a successful same-provider native advisor fallback", async () => { + const { advisor, crossProviderModel, nativeModel, sameProviderModel } = createAdvisorFallbackHarness(); + const compactSpy = vi.spyOn(compactionModule, "compact").mockImplementation(async (preparation, model) => { + if (model.provider === nativeModel.provider && model.id === nativeModel.id) { + throw new compactionModule.NativeCompactionError(new Error("V2 native compaction transport failed")); + } + if (model.provider === sameProviderModel.provider && model.id === sameProviderModel.id) { + return { + summary: "same-provider native summary", + shortSummary: "same-provider native", + firstKeptEntryId: preparation.firstKeptEntryId, + tokensBefore: 42, + }; + } + throw new Error( + `Unexpected cross-provider compaction ${crossProviderModel.provider}/${crossProviderModel.id}`, + ); + }); + + await session.prompt("small current update"); + + expect(compactSpy.mock.calls.map(([, model]) => `${model.provider}/${model.id}`)).toEqual([ + `${nativeModel.provider}/${nativeModel.id}`, + `${sameProviderModel.provider}/${sameProviderModel.id}`, + ]); + expect(JSON.stringify(advisor.state.messages)).toContain("same-provider native summary"); + }); + + it("skips unauthenticated advisor candidates before enforcing the native boundary", async () => { + const { advisor, apiKeySpy, crossProviderModel, nativeModel, sameProviderModel, settings } = + createAdvisorFallbackHarness(); + settings.setModelRole("smol", `${crossProviderModel.provider}/${crossProviderModel.id}`); + settings.setModelRole("slow", `${sameProviderModel.provider}/${sameProviderModel.id}`); + apiKeySpy.mockImplementation(async model => + model.provider === crossProviderModel.provider && model.id === crossProviderModel.id ? undefined : "test-key", + ); + const compactSpy = vi.spyOn(compactionModule, "compact").mockImplementation(async (preparation, model) => { + if (model.provider === nativeModel.provider && model.id === nativeModel.id) { + throw new compactionModule.NativeCompactionError(new Error("V2 native compaction transport failed")); + } + if (model.provider === sameProviderModel.provider && model.id === sameProviderModel.id) { + return { + summary: "authenticated same-provider advisor summary", + shortSummary: "authenticated same-provider advisor", + firstKeptEntryId: preparation.firstKeptEntryId, + tokensBefore: 42, + }; + } + throw new Error(`Unexpected advisor compaction model ${model.provider}/${model.id}`); + }); + + await session.prompt("small current update"); + + expect(compactSpy.mock.calls.map(([, model]) => `${model.provider}/${model.id}`)).toEqual([ + `${nativeModel.provider}/${nativeModel.id}`, + `${sameProviderModel.provider}/${sameProviderModel.id}`, + ]); + expect(JSON.stringify(advisor.state.messages)).toContain("authenticated same-provider advisor summary"); + }); + + it("stops before a same-provider advisor candidate with native compaction disabled", async () => { + const { advisor, nativeModel, sameProviderModel } = createAdvisorFallbackHarness({ + sameProviderNativeEnabled: false, + }); + const compactSpy = vi.spyOn(compactionModule, "compact").mockImplementation(async (preparation, model) => { + if (model.provider === nativeModel.provider && model.id === nativeModel.id) { + throw new compactionModule.NativeCompactionError(new Error("V2 native compaction transport failed")); + } + return { + summary: "generic same-provider summary", + shortSummary: "generic same-provider", + firstKeptEntryId: preparation.firstKeptEntryId, + tokensBefore: 42, + }; + }); + + await session.prompt("small current update"); + + expect(compactSpy.mock.calls.map(([, model]) => `${model.provider}/${model.id}`)).toEqual([ + `${nativeModel.provider}/${nativeModel.id}`, + ]); + expect(JSON.stringify(advisor.state.messages)).not.toContain("generic same-provider summary"); + expect(sameProviderModel.remoteCompaction?.enabled).toBe(false); + }); + + it("allows advisor compaction to cross providers after auth-classified native failures", async () => { + const { advisor, crossProviderModel, nativeModel, sameProviderModel } = createAdvisorFallbackHarness(); + const compactSpy = vi.spyOn(compactionModule, "compact").mockImplementation(async (preparation, model) => { + if (model.provider === nativeModel.provider || model.provider === sameProviderModel.provider) { + throw new compactionModule.NativeCompactionError( + Object.assign(new Error("native compaction authentication failed"), { status: 401 }), + ); + } + if (model.provider !== crossProviderModel.provider || model.id !== crossProviderModel.id) { + throw new Error(`Unexpected compaction model ${model.provider}/${model.id}`); + } + return { + summary: "authenticated fallback summary", + shortSummary: "authenticated fallback", + firstKeptEntryId: preparation.firstKeptEntryId, + tokensBefore: 42, + }; + }); + + await session.prompt("small current update"); + + expect(compactSpy.mock.calls.map(([, model]) => `${model.provider}/${model.id}`)).toEqual([ + `${nativeModel.provider}/${nativeModel.id}`, + `${sameProviderModel.provider}/${sameProviderModel.id}`, + `${crossProviderModel.provider}/${crossProviderModel.id}`, + ]); + expect(JSON.stringify(advisor.state.messages)).toContain("authenticated fallback summary"); + }); }); diff --git a/packages/coding-agent/test/advisor/advisor.test.ts b/packages/coding-agent/test/advisor/advisor.test.ts index 9ebf05c48..66d76114a 100644 --- a/packages/coding-agent/test/advisor/advisor.test.ts +++ b/packages/coding-agent/test/advisor/advisor.test.ts @@ -3475,6 +3475,193 @@ describe("advisor", () => { expect(runtime.backlog).toBe(0); }); + it("strips echoed thinking after a classifier refusal and succeeds without a notice", async () => { + const promptInputs: string[] = []; + const failures: unknown[] = []; + const state: { messages: AgentMessage[]; error?: string } = { messages: [] }; + let promptCalls = 0; + const agent: AdvisorAgent = { + prompt: async input => { + promptInputs.push(input); + promptCalls++; + if (promptCalls === 1) { + state.error = "Refusal (reasoning_extraction): reasoning may not be echoed"; + state.messages.push({ + role: "assistant", + content: [], + stopReason: "error", + stopDetails: { type: "refusal", category: "reasoning_extraction" }, + errorMessage: state.error, + timestamp: 2, + } as unknown as AgentMessage); + return; + } + state.error = undefined; + state.messages.push({ + role: "assistant", + content: [], + stopReason: "stop", + timestamp: 3, + } as unknown as AgentMessage); + }, + abort: () => {}, + reset: () => {}, + rollbackTo: count => { + state.messages.length = count; + state.error = undefined; + }, + state, + }; + const messages = [ + { + role: "assistant", + content: [ + { type: "thinking", thinking: "private reasoning" }, + { type: "text", text: "answer" }, + ], + timestamp: 1, + } as AgentMessage, + ]; + const runtime = new AdvisorRuntime( + agent, + { + snapshotMessages: () => messages, + enqueueAdvice: () => {}, + notifyFailure: error => failures.push(error), + }, + 0, + ); + + runtime.onTurnEnd(messages); + await settleUntil(() => runtime.backlog === 0); + + expect(promptInputs).toHaveLength(2); + expect(promptInputs[0]).toContain("private reasoning"); + expect(promptInputs[1]).not.toContain("private reasoning"); + expect(promptInputs[1]).toContain("answer"); + expect(failures).toEqual([]); + }); + + it("surfaces a persistent classifier refusal after one stripped resend", async () => { + const promptInputs: string[] = []; + const failures: unknown[] = []; + const state: { messages: AgentMessage[]; error?: string } = { messages: [] }; + const agent: AdvisorAgent = { + prompt: async input => { + promptInputs.push(input); + state.error = "Refusal (reasoning_extraction): reasoning may not be echoed"; + state.messages.push({ + role: "assistant", + content: [], + stopReason: "error", + stopDetails: { type: "refusal", category: "reasoning_extraction" }, + errorMessage: state.error, + timestamp: promptInputs.length + 1, + } as unknown as AgentMessage); + }, + abort: () => {}, + reset: () => {}, + rollbackTo: count => { + state.messages.length = count; + state.error = undefined; + }, + state, + }; + const messages = [ + { + role: "assistant", + content: [ + { type: "thinking", thinking: "private reasoning" }, + { type: "text", text: "answer" }, + ], + timestamp: 1, + } as AgentMessage, + ]; + const runtime = new AdvisorRuntime( + agent, + { + snapshotMessages: () => messages, + enqueueAdvice: () => {}, + notifyFailure: error => failures.push(error), + }, + 0, + ); + + runtime.onTurnEnd(messages); + await settleUntil(() => failures.length === 1 && runtime.backlog === 0); + + expect(promptInputs).toHaveLength(2); + expect(promptInputs[0]).toContain("private reasoning"); + expect(promptInputs[1]).not.toContain("private reasoning"); + expect(failures).toHaveLength(1); + }); + + it("degrades on a category-less refusal", async () => { + const promptInputs: string[] = []; + const failures: unknown[] = []; + const state: { messages: AgentMessage[]; error?: string } = { messages: [] }; + let promptCalls = 0; + const agent: AdvisorAgent = { + prompt: async input => { + promptInputs.push(input); + promptCalls++; + if (promptCalls === 1) { + state.error = "Refusal: reasoning may not be echoed"; + state.messages.push({ + role: "assistant", + content: [], + stopReason: "error", + stopDetails: { type: "refusal" }, + errorMessage: state.error, + timestamp: 2, + } as unknown as AgentMessage); + return; + } + state.error = undefined; + state.messages.push({ + role: "assistant", + content: [], + stopReason: "stop", + timestamp: 3, + } as unknown as AgentMessage); + }, + abort: () => {}, + reset: () => {}, + rollbackTo: count => { + state.messages.length = count; + state.error = undefined; + }, + state, + }; + const messages = [ + { + role: "assistant", + content: [ + { type: "thinking", thinking: "private reasoning" }, + { type: "text", text: "answer" }, + ], + timestamp: 1, + } as AgentMessage, + ]; + const runtime = new AdvisorRuntime( + agent, + { + snapshotMessages: () => messages, + enqueueAdvice: () => {}, + notifyFailure: error => failures.push(error), + }, + 0, + ); + + runtime.onTurnEnd(messages); + await settleUntil(() => runtime.backlog === 0); + + expect(promptInputs).toHaveLength(2); + expect(promptInputs[0]).toContain("private reasoning"); + expect(promptInputs[1]).not.toContain("private reasoning"); + expect(failures).toEqual([]); + }); + it("calls onTurnError with state.error before retrying the batch", async () => { const promptInputs: string[] = []; const turnErrors: unknown[] = []; diff --git a/packages/coding-agent/test/agent-session-empty-stop-guard.test.ts b/packages/coding-agent/test/agent-session-empty-stop-guard.test.ts index 3a0370d19..605cb94c3 100644 --- a/packages/coding-agent/test/agent-session-empty-stop-guard.test.ts +++ b/packages/coding-agent/test/agent-session-empty-stop-guard.test.ts @@ -80,13 +80,19 @@ function signedThinkingOnlyStop(): MockResponse { async function createHarness( responses: MockResponse[], settingsOverrides: SettingsOverrides = {}, - options: { persistSession?: boolean; extensionRunner?: ExtensionRunner } = {}, + options: { + persistSession?: boolean; + extensionRunner?: ExtensionRunner; + provider?: string; + id?: string; + } = {}, ): Promise { const tempDir = TempDir.createSync("@pi-empty-stop-guard-"); const authStorage = await AuthStorage.create(path.join(tempDir.path(), "auth.db")); authStorage.setRuntimeApiKey("mock", "test-key"); - const mock = createMockModel({ responses }); + const mock = createMockModel({ provider: options.provider, id: options.id, responses }); + authStorage.setRuntimeApiKey(mock.provider, "test-key"); const modelRegistry = new ModelRegistry(authStorage, path.join(tempDir.path(), "models.yml")); const settings = Settings.isolated({ "compaction.enabled": false, @@ -493,6 +499,46 @@ describe("AgentSession empty stop guard", () => { expect(reminderMessages(session.agent.state.messages)).toHaveLength(1); }); + it("preserves Codex commentary when discarding a colliding empty final stop", async () => { + const timestamp = 1_725_287_000_000; + vi.spyOn(Date, "now").mockReturnValue(timestamp); + const commentary = "Codex commentary before the empty final answer."; + const recovered = "Recovered after the empty final-answer retry."; + const { session, mock } = await createHarness( + [{ content: [commentary], stopReason: "stop" }, emptyStop(), { content: [recovered], stopReason: "stop" }], + {}, + { provider: "openai-codex", id: "gpt-5.5-codex" }, + ); + + await session.prompt("produce commentary"); + await session.waitForIdle(); + // Persisted identity must win regardless of branch enumeration order. The + // coarse matcher otherwise selects the commentary when it is encountered first. + const getBranch = session.sessionManager.getBranch.bind(session.sessionManager); + const branchSpy = vi + .spyOn(session.sessionManager, "getBranch") + .mockImplementation(() => getBranch().slice().reverse()); + await session.followUp("continue after commentary"); + await session.waitForIdle(); + branchSpy.mockRestore(); + + const assistantTexts = (messages: AgentMessage[]): string[] => + messages + .filter((message): message is Extract => message.role === "assistant") + .flatMap(message => message.content.flatMap(block => (block.type === "text" ? [block.text] : []))); + + expect(mock.calls).toHaveLength(3); + expect(assistantTexts(session.agent.state.messages)).toEqual([commentary, recovered]); + expect(emptyAssistantStops(session.agent.state.messages)).toHaveLength(0); + + const persistedMessages = session.sessionManager + .getBranch() + .filter(entry => entry.type === "message") + .map(entry => entry.message as AgentMessage); + expect(assistantTexts(persistedMessages)).toEqual([commentary, recovered]); + expect(emptyAssistantStops(persistedMessages)).toHaveLength(0); + }); + it("does not retry normal stop or tool-use turns", async () => { const normal = await createHarness([{ content: ["already done"], stopReason: "stop" }]); diff --git a/packages/coding-agent/test/agent-session-handoff.test.ts b/packages/coding-agent/test/agent-session-handoff.test.ts index 4c8031068..abb8e84d6 100644 --- a/packages/coding-agent/test/agent-session-handoff.test.ts +++ b/packages/coding-agent/test/agent-session-handoff.test.ts @@ -846,6 +846,31 @@ describe("AgentSession handoff", () => { expect(fallbackCandidateKey).toBeDefined(); expect(promptSpy).toHaveBeenCalledTimes(1); }); + + it("does not switch providers after provider-native auto-compaction fails", async () => { + session.settings.set("compaction.strategy", "context-full"); + session.settings.set("compaction.thresholdTokens", 50); + session.settings.set("compaction.keepRecentTokens", 1); + session.settings.set("contextPromotion.enabled", false); + + const attemptedCandidates: string[] = []; + vi.spyOn(compactionModule, "compact").mockImplementation(async (_preparation, candidate) => { + attemptedCandidates.push(`${candidate.provider}/${candidate.id}`); + throw new compactionModule.NativeCompactionError(new Error("native compaction transport failed")); + }); + + await session.prompt("pending prompt ".repeat(120)); + await waitFor(() => + events.some( + event => + event.type === "auto_compaction_end" && + event.errorMessage?.includes("native compaction transport failed") === true, + ), + ); + + expect(attemptedCandidates.length).toBeGreaterThan(0); + expect(new Set(attemptedCandidates.map(candidate => candidate.split("/", 1)[0]))).toHaveLength(1); + }); it("keeps pre-prompt context-full checks aligned with provider-anchored usage", async () => { await session.dispose(); authStorage.setRuntimeApiKey("openai", "test-key"); diff --git a/packages/coding-agent/test/auto-thinking-classifier.test.ts b/packages/coding-agent/test/auto-thinking-classifier.test.ts index b4ed909e2..3e0eea5f0 100644 --- a/packages/coding-agent/test/auto-thinking-classifier.test.ts +++ b/packages/coding-agent/test/auto-thinking-classifier.test.ts @@ -78,11 +78,12 @@ describe("auto thinking classifier helpers", () => { expect(parseCliThinkingLevel("bogus")).toBeUndefined(); }); - it("maps online 4-way classifier labels to effort levels", () => { + it("maps online level labels to effort levels", () => { expect(parseDifficultyLevel("x-high")).toBe(Effort.XHigh); expect(parseDifficultyLevel("The answer is HIGH.")).toBe(Effort.High); expect(parseDifficultyLevel("med")).toBe(Effort.Medium); expect(parseDifficultyLevel("low")).toBe(Effort.Low); + expect(parseDifficultyLevel("max")).toBe(Effort.Max); expect(parseDifficultyLevel("unknown")).toBeUndefined(); }); @@ -115,6 +116,34 @@ describe("auto thinking classifier helpers", () => { } }); + it("keeps the local classifier capped at xhigh even when opted in to max", async () => { + // The local backend only ever emits trivial/moderate/hard, so a sparse + // ladder must not let the opt-in ceiling snap `hard` up to a tier the + // on-device model never selected. `max` is the model's only tier at or + // above Low, and the local ceiling hides it, so nothing is eligible — + // falling through to `minimal` would breach the Low floor. + const fixture = await createLocalClassifierFixture("qwen3-1.7b"); + const sparse = buildLadderModel("mock-minimal-max", [Effort.Minimal, Effort.Max]); + vi.spyOn(tinyModelClient, "complete").mockResolvedValue("hard"); + + try { + const settings = Settings.isolated({ + "providers.autoThinkingModel": "qwen3-1.7b", + "providers.autoThinkingMaxEffort": "max", + }); + + expect( + await classifyDifficulty("cut over the storage layer", { + settings, + registry: fixture.registry, + model: sparse, + }), + ).toBeUndefined(); + } finally { + fixture.cleanup(); + } + }); + it("uses a larger local non-reasoning classifier floor", async () => { let maxTokens: number | undefined; const fixture = await createLocalClassifierFixture("qwen2.5-1.5b"); @@ -202,6 +231,155 @@ describe("auto thinking classifier helpers", () => { expect(options).toMatchObject({ disableReasoning: true, maxTokens: 1024 }); }); + function createOnlineFixture(targetModel: Model, answer: string, maxEffort: "xhigh" | "max" = "xhigh") { + const classifierModel = getBundledModel("anthropic", "claude-sonnet-4-6"); + if (!classifierModel) throw new Error("Expected bundled Claude Sonnet 4.6 model"); + const settings = { + get(path: string) { + if (path === "providers.autoThinkingModel") return "online"; + return path === "providers.autoThinkingMaxEffort" ? maxEffort : undefined; + }, + getModelRole(role: string) { + return role === "smol" ? `${classifierModel.provider}/${classifierModel.id}` : undefined; + }, + getStorage() { + return undefined; + }, + } as never; + const registry = { + getAvailable: () => [classifierModel], + getApiKey: async () => "test-key", + resolver: () => async () => "test-key", + } as never; + const completeSimpleMock = vi.spyOn(ai, "completeSimple").mockResolvedValue({ + stopReason: "stop", + content: [{ type: "text", text: answer }], + } as never); + return { deps: { settings, registry, model: targetModel }, completeSimpleMock }; + } + + function buildLadderModel(id: string, efforts: Effort[]): Model { + return buildModel({ + id, + name: id, + api: "openai-completions", + provider: "mock", + baseUrl: "https://example.com", + reasoning: true, + thinking: { mode: "effort", efforts }, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128_000, + maxTokens: 4096, + }); + } + + const MAX_LADDER = [Effort.Low, Effort.Medium, Effort.High, Effort.XHigh, Effort.Max]; + const XHIGH_LADDER = [Effort.Low, Effort.Medium, Effort.High, Effort.XHigh]; + + it("offers the max label only when opted in on a model that exposes the tier", async () => { + const optedIn = createOnlineFixture(buildLadderModel("mock-max", MAX_LADDER), "high", "max"); + await classifyDifficulty("refactor the scheduler", optedIn.deps); + const optedInRequest = optedIn.completeSimpleMock.mock.calls[0]?.[1] as { systemPrompt: string[] }; + // The label alone is inert: the criteria and the tie-break exception are + // what make the tier reachable, so all three must ship together. + expect(optedInRequest.systemPrompt[0]).toContain("`max`"); + expect(optedInRequest.systemPrompt[0]).toContain("no reproduction to work from"); + expect(optedInRequest.systemPrompt[0]).toContain("except between `xhigh` and `max`"); + + vi.restoreAllMocks(); + + const defaulted = createOnlineFixture(buildLadderModel("mock-max", MAX_LADDER), "high"); + await classifyDifficulty("refactor the scheduler", defaulted.deps); + const defaultedRequest = defaulted.completeSimpleMock.mock.calls[0]?.[1] as { systemPrompt: string[] }; + expect(defaultedRequest.systemPrompt[0]).not.toMatch(/\bmax\b/); + expect(defaultedRequest.systemPrompt[0]).toContain("`xhigh`"); + // The tie-break exception is what makes the top tier reachable, so it must + // not leak into the prompt of a user who did not opt in. + expect(defaultedRequest.systemPrompt[0]).toContain("choose the lower one."); + expect(defaultedRequest.systemPrompt[0]).not.toContain("no reproduction to work from"); + + vi.restoreAllMocks(); + + const unsupported = createOnlineFixture(buildLadderModel("mock-xhigh", XHIGH_LADDER), "high", "max"); + await classifyDifficulty("refactor the scheduler", unsupported.deps); + const unsupportedRequest = unsupported.completeSimpleMock.mock.calls[0]?.[1] as { systemPrompt: string[] }; + expect(unsupportedRequest.systemPrompt[0]).not.toMatch(/\bmax\b/); + expect(unsupportedRequest.systemPrompt[0]).toContain("choose the lower one."); + }); + + it("resolves max only when opted in, and snaps it to the ceiling otherwise", async () => { + const optedIn = createOnlineFixture(buildLadderModel("mock-max", MAX_LADDER), "max", "max"); + expect(await classifyDifficulty("untangle this cross-service race", optedIn.deps)).toBe(Effort.Max); + + vi.restoreAllMocks(); + + // Hallucinated `max` on a max-capable model must not cross the default ceiling. + const defaulted = createOnlineFixture(buildLadderModel("mock-max", MAX_LADDER), "max"); + expect(await classifyDifficulty("untangle this cross-service race", defaulted.deps)).toBe(Effort.XHigh); + }); + + it("resolves the sparse ladder's max tier when opted in", async () => { + const fixture = createOnlineFixture(buildLadderModel("mock-sparse", [Effort.High, Effort.Max]), "max", "max"); + expect(await classifyDifficulty("cut over the storage layer", fixture.deps)).toBe(Effort.Max); + }); + + it("takes the first label when the classifier echoes several", async () => { + // `earliest()` is deliberately conservative: an echoed list resolves to the + // lowest-positioned label rather than the model's final word. + const fixture = createOnlineFixture( + buildLadderModel("mock-max", MAX_LADDER), + "low, medium, high, xhigh, max", + "max", + ); + expect(await classifyDifficulty("rename a helper", fixture.deps)).toBe(Effort.Low); + }); + + it("snaps a hallucinated max back to the model's ceiling instead of failing the turn", async () => { + const fixture = createOnlineFixture(buildLadderModel("mock-xhigh", XHIGH_LADDER), "max"); + expect(await classifyDifficulty("untangle this cross-service race", fixture.deps)).toBe(Effort.XHigh); + }); + + it("resolves no level on a max-only ladder without opt-in", async () => { + // `["max"]` has nothing at or below the default ceiling, so the model clamp + // must not snap the request back up — auto yields nothing and the session + // keeps its current level. + const defaulted = createOnlineFixture(buildLadderModel("mock-max-only", [Effort.Max]), "xhigh"); + expect(await classifyDifficulty("cut over the storage layer", defaulted.deps)).toBeUndefined(); + + vi.restoreAllMocks(); + + const optedIn = createOnlineFixture(buildLadderModel("mock-max-only", [Effort.Max]), "max", "max"); + expect(await classifyDifficulty("cut over the storage layer", optedIn.deps)).toBe(Effort.Max); + }); + + it("has no provisional level on a max-only ladder", () => { + expect(resolveProvisionalAutoLevel(buildLadderModel("mock-max-only", [Effort.Max]))).toBeUndefined(); + }); + + it("stops at the highest tier under the ceiling on a sparse ladder", async () => { + const fixture = createOnlineFixture(buildLadderModel("mock-hm", [Effort.High, Effort.Max]), "max"); + expect(await classifyDifficulty("cut over the storage layer", fixture.deps)).toBe(Effort.High); + }); + + it("keeps the provisional auto level below max even when the model defaults to it", () => { + const maxDefaultModel = buildModel({ + id: "mock-max-default", + name: "mock-max-default", + api: "openai-completions", + provider: "mock", + baseUrl: "https://example.com", + reasoning: true, + thinking: { mode: "effort", efforts: MAX_LADDER, defaultLevel: Effort.Max }, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128_000, + maxTokens: 4096, + }); + + expect(resolveProvisionalAutoLevel(maxDefaultModel)).toBe(Effort.XHigh); + }); + it("clamps auto effort to model support while never resolving below low", () => { const model = getBundledModel("anthropic", "claude-sonnet-4-6"); if (!model) throw new Error("Expected bundled Claude Sonnet 4.6 model"); diff --git a/packages/coding-agent/test/autolearn-tools-gating.test.ts b/packages/coding-agent/test/autolearn-tools-gating.test.ts index af16a5fd8..9e477ec07 100644 --- a/packages/coding-agent/test/autolearn-tools-gating.test.ts +++ b/packages/coding-agent/test/autolearn-tools-gating.test.ts @@ -71,7 +71,7 @@ describe("autolearn tool gating", () => { expect(noBackend).not.toContain("learn"); }); - it("excludes the tools from a subagent even with an explicit list", async () => { + it("excludes the tools from a subagent when not in the explicit list", async () => { // taskDepth > 0: the controller never runs here, so a subagent's explicit // whitelist must not be silently widened with write-capable tools. const sub = ( @@ -90,6 +90,18 @@ describe("autolearn tool gating", () => { expect(subDiscovered).not.toContain("learn"); }); + it("allows the tools in a subagent when explicitly requested in toolNames", async () => { + // Frontmatter tools: list overrides the taskDepth gate. + const sub = ( + await createTools(makeSession({ "autolearn.enabled": true, "memory.backend": "mnemopi" }, { taskDepth: 1 }), [ + "manage_skill", + "learn", + ]) + ).map(t => t.name); + expect(sub).toContain("manage_skill"); + expect(sub).toContain("learn"); + }); + it("offers learn with the file-based local backend", async () => { const names = (await createTools(makeSession({ "autolearn.enabled": true, "memory.backend": "local" }))).map( t => t.name, diff --git a/packages/coding-agent/test/cursor-exec.test.ts b/packages/coding-agent/test/cursor-exec.test.ts index 2827cb782..799414514 100644 --- a/packages/coding-agent/test/cursor-exec.test.ts +++ b/packages/coding-agent/test/cursor-exec.test.ts @@ -5,6 +5,7 @@ import * as path from "node:path"; import { create, fromBinary } from "@bufbuild/protobuf"; import type { AgentEvent, AgentTool, AgentToolContext } from "@oh-my-pi/pi-agent-core"; import { type BlockState, handleServerMessage, type ToolCallState } from "@oh-my-pi/pi-ai/providers/cursor"; +import { buildPiLsResult, piTruncation } from "@oh-my-pi/pi-ai/providers/cursor/exec-modern"; import type { AssistantMessage } from "@oh-my-pi/pi-ai/types"; import { AssistantMessageEventStream } from "@oh-my-pi/pi-ai/utils/event-stream"; import { @@ -18,9 +19,17 @@ import { } from "@oh-my-pi/pi-catalog/discovery/cursor-gen/agent_pb"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { CursorExecHandlers } from "@oh-my-pi/pi-coding-agent/cursor"; +import { + bridgeToolMap, + createBridgeEditTool, + createBridgeGrepFactory, +} from "@oh-my-pi/pi-coding-agent/cursor-bridge-tools"; +import { EditTool } from "@oh-my-pi/pi-coding-agent/edit"; import type { ExtensionRunner } from "@oh-my-pi/pi-coding-agent/extensibility/extensions"; import { ExtensionToolWrapper } from "@oh-my-pi/pi-coding-agent/extensibility/extensions"; -import { GrepTool, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; +import { BUILTIN_TOOLS, GrepTool, ReadTool, type Tool, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; +import { BashTool } from "@oh-my-pi/pi-coding-agent/tools/bash"; +import type { TruncationMeta } from "@oh-my-pi/pi-coding-agent/tools/output-meta"; import { removeWithRetries } from "@oh-my-pi/pi-utils"; import { type } from "arktype"; import { AdviseTool } from "../src/advisor/advise-tool"; @@ -36,6 +45,25 @@ function createTestSession(cwd: string, overrides: Partial = {}): T }; } +/** + * An `ExtensionRunner` that intercepts nothing but records that it ran. + * + * The bridge's per-call tools must carry the same wrapper as registry tools; + * seeing a name arrive here proves the wrapper is present, since an unwrapped + * tool never announces. + */ +function passthroughRunner(seen: string[] = []): ExtensionRunner { + return { + hasHandlers: () => true, + consumeToolCallEmitted: () => false, + emitToolCall: async (event: { toolName: string }) => { + seen.push(event.toolName); + return undefined; + }, + emitToolResult: async () => undefined, + } as unknown as ExtensionRunner; +} + describe("CursorExecHandlers.grep bridge", () => { let cwd: string; let searchTool: GrepTool; @@ -82,6 +110,544 @@ describe("CursorExecHandlers.grep bridge", () => { } as any); expect((sensitiveResult.details as { matchCount?: number } | undefined)?.matchCount).toBe(1); }); + + it("honors pi_grep's requested match limit against real files", async () => { + // The frame's `limit` caps total surfaced matches. The model-facing schema + // has no such parameter, so without a per-call tool the cap is dropped and + // the search returns everything it found. + await Bun.write(path.join(cwd, "many.txt"), Array.from({ length: 10 }, (_, i) => `needle ${i}`).join("\n")); + const scopedHandlers = new CursorExecHandlers({ + cwd, + tools: new Map([["grep", searchTool]]), + createGrepTool: options => new GrepTool(createTestSession(cwd), options), + }); + + const capped = await scopedHandlers.piGrep({ + toolCallId: "c1", + args: { pattern: "needle", path: cwd, limit: 3 }, + } as never); + expect((capped.details as { matchCount?: number } | undefined)?.matchCount).toBe(3); + + const uncapped = await scopedHandlers.piGrep({ + toolCallId: "c2", + args: { pattern: "needle", path: cwd }, + } as never); + expect((uncapped.details as { matchCount?: number } | undefined)?.matchCount).toBe(10); + }); + + it("honors pi_grep's requested context width against real files", async () => { + // `context` has no schema parameter either: the width is read from + // settings fixed at tool construction, so the frame's value only lands + // through a per-call instance. + await Bun.write(path.join(cwd, "ctx.txt"), "before line\nneedle here\nafter line\n"); + const scopedHandlers = new CursorExecHandlers({ + cwd, + tools: new Map([["grep", searchTool]]), + createGrepTool: options => new GrepTool(createTestSession(cwd), options), + }); + + const noContext = await scopedHandlers.piGrep({ + toolCallId: "c1", + args: { pattern: "needle here", path: path.join(cwd, "ctx.txt"), context: 0 }, + } as never); + const noContextText = noContext.content.map(c => (c.type === "text" ? c.text : "")).join(""); + expect(noContextText).not.toContain("before line"); + expect(noContextText).not.toContain("after line"); + + const withContext = await scopedHandlers.piGrep({ + toolCallId: "c2", + args: { pattern: "needle here", path: path.join(cwd, "ctx.txt"), context: 1 }, + } as never); + const withContextText = withContext.content.map(c => (c.type === "text" ? c.text : "")).join(""); + expect(withContextText).toContain("before line"); + expect(withContextText).toContain("after line"); + }); + + it("satisfies a pi_grep limit that spans more files than one page", async () => { + // The local tool windows results to the first 20 files and tells the + // caller to paginate with `skip`. `PiGrepExecArgs` has no `skip` field, + // so a frame asking for 100 matches across 25 one-match files would get + // 20, no `match_limit_reached`, and advice it cannot act on — output + // silently short of what it asked for and labelled complete. + const spread = path.join(cwd, "spread"); + await fs.mkdir(spread, { recursive: true }); + await Promise.all( + Array.from({ length: 25 }, (_, i) => Bun.write(path.join(spread, `f${i}.txt`), "needle here\n")), + ); + const scopedHandlers = new CursorExecHandlers({ + cwd, + tools: new Map([["grep", searchTool]]), + createGrepTool: options => new GrepTool(createTestSession(cwd), options), + }); + + const wide = await scopedHandlers.piGrep({ + toolCallId: "c1", + args: { pattern: "needle", path: spread, limit: 100 }, + } as never); + const details = wide.details as { matchCount?: number; fileLimitReached?: number } | undefined; + expect(details?.matchCount).toBe(25); + // Nothing was clipped, so no pagination advice the frame cannot follow. + expect(details?.fileLimitReached).toBeUndefined(); + + // The cap still binds when the matches really do exceed it, and says so: + // `match_limit_reached` is the frame's only signal that output was cut, + // and one match per file makes the boundary sharp — a cap of 24 over 25 + // files is clipped, a cap of 25 is complete. Reading only `cap` files + // cannot tell those apart. + const capped = await scopedHandlers.piGrep({ + toolCallId: "c2", + args: { pattern: "needle", path: spread, limit: 24 }, + } as never); + const cappedDetails = capped.details as { matchCount?: number; perFileLimitReached?: number } | undefined; + expect(cappedDetails?.matchCount).toBe(24); + expect(cappedDetails?.perFileLimitReached).toBe(24); + + // Exactly at the cap is complete, not clipped. + const exact = await scopedHandlers.piGrep({ + toolCallId: "c3", + args: { pattern: "needle", path: spread, limit: 25 }, + } as never); + const exactDetails = exact.details as { matchCount?: number; perFileLimitReached?: number } | undefined; + expect(exactDetails?.matchCount).toBe(25); + expect(exactDetails?.perFileLimitReached).toBeUndefined(); + }); +}); + +describe("pi_bash truncation reaches the wire from a real BashTool result", () => { + let cwd: string; + + beforeEach(async () => { + cwd = await fs.mkdtemp(path.join(os.tmpdir(), "cursor-pibash-trunc-")); + }); + + afterEach(async () => { + await removeWithRetries(cwd); + }); + + it("translates the metadata BashTool actually emits, not a hand-built shape", async () => { + // Producer/consumer contract. `piTruncation` lives in `pi-ai`, which + // cannot import `BashTool`, so every test there must hand-build the + // details bag — and a bag built from the same assumption as the code + // stays green when `BashTool`'s real shape moves. This runs the actual + // tool and feeds its actual output to the actual translator. + const bash = new BashTool(createTestSession(cwd)); + const result = await bash.execute("t1", { command: "seq 1 200000" }); + + // Guard the assumption the bridge encodes: Bash files truncation under + // `details.meta.truncation`, and that record carries no `truncated` + // flag. If either moves, this fails here rather than silently sending + // clipped output to Cursor with no truncation notice. + // `TruncationMeta` is the producer's own type: if a field this bridge + // reads is renamed or dropped, this stops compiling. + const details = result.details as { truncation?: unknown; meta?: { truncation?: TruncationMeta } }; + expect(details.truncation).toBeUndefined(); + expect(details.meta?.truncation).toBeDefined(); + expect(details.meta?.truncation).not.toHaveProperty("truncated"); + + const wire = piTruncation({ + role: "toolResult", + toolCallId: "t1", + toolName: "bash", + content: result.content, + isError: false, + timestamp: Date.now(), + details: result.details, + }); + + expect(wire?.truncated).toBe(true); + expect(wire?.totalLines).toBe(details.meta?.truncation?.totalLines); + expect(wire?.outputBytes).toBe(details.meta?.truncation?.outputBytes); + expect(wire?.truncatedBy).toBe(details.meta?.truncation?.truncatedBy); + }); + + it("reports the entry cap ReadTool actually records for a large listing", async () => { + // Same producer/consumer contract for the listing cap. `glob` records it + // twice — a flat `details.resultLimitReached` and the structured meta — + // but `read`, which serves `pi_ls`, records it only through `OutputMeta`. + // Reading just the flat field dropped `entry_limit_reached` for every + // real listing, so Cursor got clipped output with no signal it was cut. + // + // The root listing is uncapped; the depth-2 tree caps each child + // directory, so the entries have to sit one level down to trip it. + const listing = path.join(cwd, "many"); + const child = path.join(listing, "child"); + await fs.mkdir(child, { recursive: true }); + await Promise.all(Array.from({ length: 40 }, (_, i) => Bun.write(path.join(child, `f${i}.txt`), "x"))); + const read = new ReadTool(createTestSession(cwd)); + const result = await read.execute("l1", { path: listing }); + + // Guard the assumption the bridge encodes: the cap lives in the nested + // meta and nowhere flat. If the producer's shape moves, this fails here + // rather than silently sending a clipped listing as if it were whole. + const details = result.details as { + resultLimitReached?: number; + meta?: { limits?: { resultLimit?: { reached: number } } }; + }; + expect(details.resultLimitReached).toBeUndefined(); + expect(details.meta?.limits?.resultLimit?.reached).toBeGreaterThan(0); + + const wire = buildPiLsResult({ + role: "toolResult", + toolCallId: "l1", + toolName: "read", + content: result.content, + isError: false, + timestamp: Date.now(), + details: result.details, + }); + if (wire.result.case !== "success") throw new Error(`expected success, got ${wire.result.case}`); + expect(wire.result.value.entryLimitReached).toBeGreaterThan(0); + }); + + it("sends no truncation summary for output that fit", async () => { + const bash = new BashTool(createTestSession(cwd)); + const result = await bash.execute("t2", { command: "echo hi" }); + const wire = piTruncation({ + role: "toolResult", + toolCallId: "t2", + toolName: "bash", + content: result.content, + isError: false, + timestamp: Date.now(), + details: result.details, + }); + expect(wire).toBeUndefined(); + }); +}); + +describe("bridge tool resolution beyond the model-facing registry", () => { + let cwd: string; + + beforeEach(async () => { + cwd = await fs.mkdtemp(path.join(os.tmpdir(), "cursor-bridge-resolve-")); + }); + + afterEach(async () => { + await removeWithRetries(cwd); + }); + + it("edits a real file from a pi_edit frame when `edit` is withheld from the model", async () => { + // For Cursor the session drops `edit` from the tool registry so the model + // is steered to full-file `write`. The native `pi_edit` frame arrives + // regardless of the advertised catalog, so the bridge must still reach a + // real edit tool through `getEditReplaceTool` — otherwise every modern + // edit answers "Tool \"edit\" not available" and the file is untouched. + // (Not the `getTool` fallback: that resolver also serves the agent loop's + // unadvertised calls, so it stays device-only.) + const target = path.join(cwd, "sample.txt"); + await Bun.write(target, "alpha\nbeta\n"); + // Build it exactly as the session does. Both bridge callsites go through + // this factory, so a regression in it — the wrong mode, a missing + // approval wrapper — fails here rather than passing against a + // hand-constructed stand-in. + const editTool = createBridgeEditTool(createTestSession(cwd), passthroughRunner()); + + const withheld = new CursorExecHandlers({ + cwd, + tools: new Map(), + getEditReplaceTool: () => editTool, + }); + const result = await withheld.piEdit({ + toolCallId: "e1", + args: { path: target, edits: [{ oldText: "beta", newText: "gamma" }] }, + } as never); + + expect(result.isError).toBeFalsy(); + expect(await Bun.file(target).text()).toBe("alpha\ngamma\n"); + }); + + it("reports the failure instead of editing when no edit tool is reachable", async () => { + const target = path.join(cwd, "sample.txt"); + await Bun.write(target, "alpha\nbeta\n"); + const unreachable = new CursorExecHandlers({ cwd, tools: new Map() }); + const result = await unreachable.piEdit({ + toolCallId: "e2", + args: { path: target, edits: [{ oldText: "beta", newText: "gamma" }] }, + } as never); + + expect(result.isError).toBe(true); + expect(await Bun.file(target).text()).toBe("alpha\nbeta\n"); + }); + + it("substitutes a replace-mode edit into a granted advisor tool map", async () => { + // The advisor roster hands the bridge the instances it built for the + // advisor's own loop — default `hashline` mode, whose schema is a single + // `input` string. A `pi_edit` frame's `old_text`/`new_text` pairs fail + // validation against it, so the file goes unmodified. This is the + // substitution the advisor path applies before constructing handlers. + const target = path.join(cwd, "sample.txt"); + await Bun.write(target, "alpha\nbeta\n"); + const session = createTestSession(cwd); + const advisorEdit = new EditTool(session); + expect(advisorEdit.mode).not.toBe("replace"); + const granted = new Map([["edit", advisorEdit]]); + + const bridged = bridgeToolMap(granted, () => createBridgeEditTool(session, passthroughRunner())); + const handlers = new CursorExecHandlers({ cwd, tools: bridged }); + const result = await handlers.piEdit({ + toolCallId: "e3", + args: { path: target, edits: [{ oldText: "beta", newText: "gamma" }] }, + } as never); + + expect(result.isError).toBeFalsy(); + expect(await Bun.file(target).text()).toBe("alpha\ngamma\n"); + // The advisor's own loop must keep the exact instance it was handed. + expect(granted.get("edit")).toBe(advisorEdit); + }); + + it("runs the replace-mode instance even when the registry still holds another mode", async () => { + // The state a session reaches by starting on a non-Cursor provider and + // switching to Cursor: `edit` was never deleted from the registry (that + // only happens for a session created on Cursor) and the roster is not + // rebuilt on switch, so the configured-mode instance is still there. + // `executeTool` prefers the map over the `getTool` fallback, so without + // an explicit replace-mode accessor every native edit after the switch + // fails validation against the wrong schema. + const target = path.join(cwd, "sample.txt"); + await Bun.write(target, "alpha\nbeta\n"); + const session = createTestSession(cwd); + const configuredEdit = new EditTool(session); + expect(configuredEdit.mode).not.toBe("replace"); + + const handlers = new CursorExecHandlers({ + cwd, + tools: new Map([["edit", configuredEdit]]), + getEditReplaceTool: () => createBridgeEditTool(session, passthroughRunner()), + }); + const result = await handlers.piEdit({ + toolCallId: "e5", + args: { path: target, edits: [{ oldText: "beta", newText: "gamma" }] }, + } as never); + + expect(result.isError).toBeFalsy(); + expect(await Bun.file(target).text()).toBe("alpha\ngamma\n"); + }); + + it("still refuses a pi_edit frame when the session granted no edit tool", async () => { + // The accessor carries the grant: a restricted roster returns undefined + // from it, and no other resolution path may substitute a mutating tool + // (issue #5680). Building the instance provider-independently must not + // weaken that. + const target = path.join(cwd, "sample.txt"); + await Bun.write(target, "alpha\nbeta\n"); + const handlers = new CursorExecHandlers({ + cwd, + tools: new Map(), + getEditReplaceTool: () => undefined, + }); + const result = await handlers.piEdit({ + toolCallId: "e6", + args: { path: target, edits: [{ oldText: "beta", newText: "gamma" }] }, + } as never); + + expect(result.isError).toBe(true); + expect(await Bun.file(target).text()).toBe("alpha\nbeta\n"); + }); + + it("leaves an ungranted tool map without an edit tool", async () => { + // The bridge tool is constructed, not looked up, so substituting for a + // roster that was never granted `edit` would hand a read-only advisor a + // mutating tool (issue #5680). The frame must fail instead. + const target = path.join(cwd, "sample.txt"); + await Bun.write(target, "alpha\nbeta\n"); + const session = createTestSession(cwd); + let built = 0; + const withheld = bridgeToolMap(new Map(), () => { + built++; + return createBridgeEditTool(session, passthroughRunner()); + }); + expect(withheld.has("edit")).toBe(false); + expect(built).toBe(0); + + const handlers = new CursorExecHandlers({ cwd, tools: withheld }); + const result = await handlers.piEdit({ + toolCallId: "e4", + args: { path: target, edits: [{ oldText: "beta", newText: "gamma" }] }, + } as never); + + expect(result.isError).toBe(true); + expect(await Bun.file(target).text()).toBe("alpha\nbeta\n"); + }); + + it("refuses a scoped pi_grep when no grep tool was granted", async () => { + // The factory builds a fresh tool and `executeTool` prefers that override + // over the registry, so a session that withheld `grep` must not install + // one — otherwise a frame carrying `context`/`limit` searches anyway. + await Bun.write(path.join(cwd, "hit.txt"), "needle\n"); + const denied = new CursorExecHandlers({ cwd, tools: new Map() }); + const result = await denied.piGrep({ + toolCallId: "g0", + args: { pattern: "needle", path: cwd, limit: 5 }, + } as never); + + expect(result.isError).toBe(true); + expect(result.content.map(c => (c.type === "text" ? c.text : "")).join("")).toContain("not available"); + }); + + it("denies a native pi_edit frame the user's policy blocks", async () => { + // The bridge's `edit` is wrapped, but `ExtensionToolWrapper` reads the + // approval mode and per-tool policies only from the execute-time + // context — with none it resolves as `yolo` with empty policies and the + // frame edits the file regardless of what the user configured. + const target = path.join(cwd, "denied.txt"); + await Bun.write(target, "alpha\nbeta\n"); + const settings = Settings.isolated({ "tools.approval": { edit: "deny" } }); + const session = createTestSession(cwd, { settings }); + const handlers = new CursorExecHandlers({ + cwd, + tools: bridgeToolMap(new Map([["edit", new EditTool(session)]]), () => + createBridgeEditTool(session, passthroughRunner()), + ), + getToolContext: () => ({ settings }) as AgentToolContext, + }); + + const result = await handlers.piEdit({ + toolCallId: "e5", + args: { path: target, edits: [{ oldText: "beta", newText: "gamma" }] }, + } as never); + + expect(result.isError).toBe(true); + expect(await Bun.file(target).text()).toBe("alpha\nbeta\n"); + }); + + it("denies a scoped pi_grep frame the user's policy blocks", async () => { + // Same gate on the other bridge-only tool: the per-call `grep` the + // factory builds for a frame carrying `context`/`limit` must answer to + // `tools.approval.grep` like every registry call. + await Bun.write(path.join(cwd, "hit.txt"), "needle\n"); + const settings = Settings.isolated({ "tools.approval": { grep: "deny" } }); + const session = createTestSession(cwd, { settings }); + const handlers = new CursorExecHandlers({ + cwd, + tools: new Map(), + createGrepTool: createBridgeGrepFactory(session, passthroughRunner()), + getToolContext: () => ({ settings }) as AgentToolContext, + }); + + const result = await handlers.piGrep({ + toolCallId: "g2", + args: { pattern: "needle", path: cwd, context: 1, limit: 5 }, + } as never); + + expect(result.isError).toBe(true); + expect(result.content.map(c => (c.type === "text" ? c.text : "")).join("")).toContain("blocked by user policy"); + }); + + it("wraps the per-call grep the real bridge factory builds", async () => { + // The reviewed bypass was in the factory the session hands the bridge, + // not in the bridge: a raw `new GrepTool(...)` there skips the approval + // gate every registry tool goes through. Exercise the shared factory + // both callsites use, so a regression in it fails here. + await Bun.write(path.join(cwd, "hit.txt"), "needle\n"); + const intercepted: string[] = []; + const factory = createBridgeGrepFactory(createTestSession(cwd), passthroughRunner(intercepted)); + const built = factory({ context: 0, totalMatchLimit: 5 }); + expect(built).toBeInstanceOf(ExtensionToolWrapper); + + const handlers = new CursorExecHandlers({ + cwd, + tools: new Map(), + createGrepTool: factory, + }); + const result = await handlers.piGrep({ + toolCallId: "g1", + args: { pattern: "needle", path: cwd, limit: 5 }, + } as never); + + // The wrapper ran (its extension hook fired) and the frame's cap still + // reached the underlying tool. + expect(intercepted).toEqual(["grep"]); + expect((result.details as { matchCount?: number } | undefined)?.matchCount).toBe(1); + }); + + it("denies a pi_write frame the user's policy blocks when the tool came from the caller's map", async () => { + // The advisor hands the bridge its own tool map. Those instances are run + // directly by `piWrite`/`piBash`, so an unwrapped one executes whatever + // the frame asks regardless of `tools.approval.` — supplying + // `getToolContext` alone does not gate anything, because the gate lives + // in `ExtensionToolWrapper`, not in the bridge. + const settings = Settings.isolated({ "tools.approval": { write: "deny" } }); + const session = createTestSession(cwd, { settings }); + const writeTool = await BUILTIN_TOOLS.write(session); + if (!writeTool) throw new Error("expected a write tool"); + const handlers = new CursorExecHandlers({ + cwd, + tools: new Map([["write", new ExtensionToolWrapper(writeTool, passthroughRunner())]]), + getToolContext: () => ({ settings }) as AgentToolContext, + }); + + const target = path.join(cwd, "denied-write.txt"); + const result = await handlers.piWrite({ + toolCallId: "w1", + args: { path: target, content: "written" }, + } as never); + + expect(result.isError).toBe(true); + expect(await Bun.file(target).exists()).toBe(false); + }); +}); + +describe("pi_bash timeout presence", () => { + let cwd: string; + let handlers: CursorExecHandlers; + + beforeEach(async () => { + cwd = await fs.mkdtemp(path.join(os.tmpdir(), "cursor-pibash-timeout-")); + const bash: Tool = new BashTool(createTestSession(cwd)); + handlers = new CursorExecHandlers({ cwd, tools: new Map([["bash", bash]]) }); + }); + + afterEach(async () => { + await removeWithRetries(cwd); + }); + + it("disables the deadline for an explicit zero instead of applying the default", async () => { + // `timeout` is `optional int32` and `bash` documents `0` as "disables + // the command deadline". Folding a supplied `0` into `undefined` applies + // the 300s default, killing the long-running command that asked not to + // be killed. + const disabled = await handlers.piBash({ + toolCallId: "b1", + args: { command: "echo hi", timeout: 0 }, + } as never); + const disabledDetails = disabled.details as { timeoutDisabled?: boolean; timeoutSeconds?: number }; + expect(disabledDetails.timeoutDisabled).toBe(true); + expect(disabledDetails.timeoutSeconds).toBeUndefined(); + + const defaulted = await handlers.piBash({ + toolCallId: "b2", + args: { command: "echo hi" }, + } as never); + const defaultedDetails = defaulted.details as { timeoutDisabled?: boolean; timeoutSeconds?: number }; + expect(defaultedDetails.timeoutDisabled).toBeUndefined(); + expect(defaultedDetails.timeoutSeconds).toBeGreaterThan(0); + }); + + it("passes a positive timeout through", async () => { + const result = await handlers.piBash({ + toolCallId: "b3", + args: { command: "echo hi", timeout: 42 }, + } as never); + expect((result.details as { timeoutSeconds?: number }).timeoutSeconds).toBe(42); + }); + + it("falls back to the default for a negative timeout", async () => { + // A negative has no local meaning. Passed through, `bash` clamps it to + // its 1s floor — a command that dies almost immediately. Dropping it to + // the default is the only sane reading, so assert the default rather + // than merely "positive", which the clamp also satisfies. + const negative = await handlers.piBash({ + toolCallId: "b4", + args: { command: "echo hi", timeout: -5 }, + } as never); + const omitted = await handlers.piBash({ + toolCallId: "b5", + args: { command: "echo hi" }, + } as never); + const negativeDetails = negative.details as { timeoutDisabled?: boolean; timeoutSeconds?: number }; + expect(negativeDetails.timeoutDisabled).toBeUndefined(); + expect(negativeDetails.timeoutSeconds).toBe((omitted.details as { timeoutSeconds?: number }).timeoutSeconds); + }); }); describe("CursorExecHandlers error results", () => { @@ -207,6 +773,384 @@ describe("CursorExecHandlers mounted tool bridge", () => { expect(executed).toBe(false); expect(result.content.find(block => block.type === "text")?.text).toContain("blocked by user policy"); }); + + it("lists resources from the session's live MCP servers", async () => { + // The provider used to answer an empty catalog unconditionally, hiding + // resources the session holds live connections to. Every entry must + // carry the server name, since that is how Cursor addresses the read. + const handlers = new CursorExecHandlers({ + cwd: ".", + tools: new Map(), + mcpResources: { + serverNames: () => ["docs", "issues"], + getServerResources: async name => + name === "docs" + ? { resources: [{ uri: "docs://readme", name: "README", mimeType: "text/markdown" }] } + : { resources: [{ uri: "issues://open" }] }, + readServerResource: async () => undefined, + }, + }); + + expect(await handlers.listMcpResources({})).toEqual([ + { uri: "docs://readme", name: "README", description: undefined, mimeType: "text/markdown", server: "docs" }, + { uri: "issues://open", name: undefined, description: undefined, mimeType: undefined, server: "issues" }, + ]); + // A server filter narrows to that server alone. + expect((await handlers.listMcpResources({ server: "issues" })).map(r => r.uri)).toEqual(["issues://open"]); + }); + + it("waits for a server's catalog instead of reporting it empty", async () => { + // A server registers its tools before its resource catalog finishes + // loading. A frame landing in that window must not read the empty cache + // and answer "this server advertises nothing" - that is a lie the model + // cannot distinguish from the truth, and it will not ask again. + // The load is gated on a promise this test resolves, so "did the listing + // wait" is answered by the gate rather than by a duration. + const discovery = Promise.withResolvers(); + let loaded = false; + const handlers = new CursorExecHandlers({ + cwd: ".", + tools: new Map(), + mcpResources: { + serverNames: () => ["slow"], + getServerResources: async () => { + await discovery.promise; + loaded = true; + return { resources: [{ uri: "slow://ready" }] }; + }, + readServerResource: async () => undefined, + }, + }); + + const listing = handlers.listMcpResources({}); + // Nothing can have been reported yet: the catalog has not arrived. + expect(loaded).toBe(false); + discovery.resolve(); + expect((await listing).map(r => r.uri)).toEqual(["slow://ready"]); + expect(loaded).toBe(true); + }); + + it("labels a mixed-content read with the mime type of the part it sends", async () => { + // An image blob followed by a text note: the wire carries one payload, + // and its mime type has to describe that payload rather than whichever + // item happened to come first, or the model is told plain text is a PNG. + const handlers = new CursorExecHandlers({ + cwd: ".", + tools: new Map(), + mcpResources: { + serverNames: () => ["files"], + getServerResources: async () => undefined, + readServerResource: async (_name, uri) => ({ + contents: [ + { uri, mimeType: "image/png", blob: Buffer.from("PNG-BYTES").toString("base64") }, + { uri, mimeType: "text/plain", text: "a note about the image" }, + ], + }), + }, + }); + + const read = await handlers.readMcpResource({ server: "files", uri: "files://mixed" }); + expect(read?.text).toBe("a note about the image"); + expect(read?.mimeType).toBe("text/plain"); + }); + + it("reads a resource and decodes a blob payload into wire bytes", async () => { + // MCP hands back a list of content items with base64 blobs; the wire + // carries one text or one byte payload. + const handlers = new CursorExecHandlers({ + cwd: ".", + tools: new Map(), + mcpResources: { + serverNames: () => ["files"], + getServerResources: async () => undefined, + readServerResource: async (name, uri) => + name === "files" && uri === "files://logo" + ? { contents: [{ uri, mimeType: "image/png", blob: Buffer.from("PNG").toString("base64") }] } + : undefined, + }, + }); + + const read = await handlers.readMcpResource({ server: "files", uri: "files://logo" }); + expect(read?.mimeType).toBe("image/png"); + expect(read?.blob && Buffer.from(read.blob).toString()).toBe("PNG"); + // An unknown uri is genuinely not found, not an error. + expect(await handlers.readMcpResource({ server: "files", uri: "files://missing" })).toBeNull(); + }); + + it("writes a download to the workspace path and returns no content", async () => { + // The frame's `download_path` means "put it on disk, don't hand me the + // bytes". Reporting success without creating the file leaves the model + // pointing at nothing. + const workspace = await fs.mkdtemp(path.join(os.tmpdir(), "cursor-mcp-download-")); + try { + const handlers = new CursorExecHandlers({ + cwd: workspace, + tools: new Map(), + mcpResources: { + serverNames: () => ["files"], + getServerResources: async () => undefined, + readServerResource: async (_name, uri) => ({ + contents: [{ uri, mimeType: "image/png", blob: Buffer.from("PNG-BYTES").toString("base64") }], + }), + }, + }); + + const read = await handlers.readMcpResource({ + server: "files", + uri: "files://logo", + downloadPath: "assets/logo.png", + }); + + expect(read?.downloadPath).toBe("assets/logo.png"); + // No content: that is the whole point of the download mode. + expect(read?.text).toBeUndefined(); + expect(read?.blob).toBeUndefined(); + // The file is really there, decoded from base64, under the workspace. + expect(await Bun.file(path.join(workspace, "assets/logo.png")).text()).toBe("PNG-BYTES"); + } finally { + await removeWithRetries(workspace); + } + }); + + it("refuses a download path that escapes the workspace", async () => { + // `download_path` is workspace-relative by contract, but it comes from + // the server and the generic resolver honors absolute paths and `..` — + // correct for a path a user typed, a write-anywhere primitive for one a + // remote peer supplied. + const workspace = await fs.mkdtemp(path.join(os.tmpdir(), "cursor-mcp-escape-")); + const outside = path.join(workspace, "outside.txt"); + try { + const inner = path.join(workspace, "ws"); + await fs.mkdir(inner); + const handlers = new CursorExecHandlers({ + cwd: inner, + tools: new Map(), + mcpResources: { + serverNames: () => ["files"], + getServerResources: async () => undefined, + readServerResource: async (_name, uri) => ({ contents: [{ uri, text: "payload" }] }), + }, + }); + + for (const escapePath of ["../outside.txt", outside, "nested/../../outside.txt"]) { + await expect( + handlers.readMcpResource({ server: "files", uri: "files://x", downloadPath: escapePath }), + ).rejects.toThrow(/outside the workspace/); + } + // Nothing was written on any of those attempts. + expect(await Bun.file(outside).exists()).toBe(false); + } finally { + await removeWithRetries(workspace); + } + }); + + it("refuses a download path that escapes through a symlink", async () => { + // A lexical check is not containment. `out/config` is relative and + // `..`-free, but with `ws/out` linked outside the workspace the write + // lands wherever the link points — as does a write to a dangling link, + // including one that only reaches outside through a second hop. + const workspace = await fs.mkdtemp(path.join(os.tmpdir(), "cursor-mcp-symlink-")); + try { + const inner = path.join(workspace, "ws"); + const outside = path.join(workspace, "outside"); + await fs.mkdir(inner); + await fs.mkdir(outside); + await fs.symlink(outside, path.join(inner, "out")); + await fs.symlink(path.join(outside, "dangling.txt"), path.join(inner, "link.txt")); + // A link whose target already exists: the write would overwrite the + // real file out there rather than create anything new. + await Bun.write(path.join(outside, "existing.txt"), "original"); + await fs.symlink(path.join(outside, "existing.txt"), path.join(inner, "existing.txt")); + // Two hops: the first link stays inside, so resolving one level reads + // as contained while the write still follows the chain out. + await fs.symlink(path.join(inner, "second.txt"), path.join(inner, "chain.txt")); + await fs.symlink(path.join(outside, "chained.txt"), path.join(inner, "second.txt")); + const handlers = new CursorExecHandlers({ + cwd: inner, + tools: new Map(), + mcpResources: { + serverNames: () => ["files"], + getServerResources: async () => undefined, + readServerResource: async (_name, uri) => ({ contents: [{ uri, text: "payload" }] }), + }, + }); + + for (const escapePath of [ + "out/config", + "out/deep/nested.txt", + "link.txt", + "existing.txt", + "chain.txt", + "second.txt", + ]) { + await expect( + handlers.readMcpResource({ server: "files", uri: "files://x", downloadPath: escapePath }), + ).rejects.toThrow(/outside the workspace/); + } + expect(await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: outside }))).toEqual(["existing.txt"]); + expect(await Bun.file(path.join(outside, "existing.txt")).text()).toBe("original"); + + // A real workspace path still downloads: the guard resolves links, it + // does not refuse every path whose parents do not exist yet. + await handlers.readMcpResource({ server: "files", uri: "files://x", downloadPath: "deep/new/file.txt" }); + expect(await Bun.file(path.join(inner, "deep/new/file.txt")).text()).toBe("payload"); + } finally { + await removeWithRetries(workspace); + } + }); + + it("overwrites an existing download target in place", async () => { + // The download open is `O_NOFOLLOW`, which also drops the plain + // `Bun.write` path — so the ordinary case has to keep working: a + // re-download truncates the previous file rather than failing or + // appending to it. + const workspace = await fs.mkdtemp(path.join(os.tmpdir(), "cursor-mcp-rewrite-")); + try { + let payload = "a much longer first payload"; + const handlers = new CursorExecHandlers({ + cwd: workspace, + tools: new Map(), + mcpResources: { + serverNames: () => ["files"], + getServerResources: async () => undefined, + readServerResource: async (_name, uri) => ({ contents: [{ uri, text: payload }] }), + }, + }); + + await handlers.readMcpResource({ server: "files", uri: "files://x", downloadPath: "out/doc.txt" }); + payload = "second"; + await handlers.readMcpResource({ server: "files", uri: "files://x", downloadPath: "out/doc.txt" }); + + expect(await Bun.file(path.join(workspace, "out/doc.txt")).text()).toBe("second"); + } finally { + await removeWithRetries(workspace); + } + }); + + it("refuses a download onto a hard link that shares its inode outside", async () => { + // A hard link is a regular file that lives inside the workspace and + // passes both containment and `O_NOFOLLOW`, yet writing through it + // overwrites every other name for the same inode — including one out of + // reach. Only the link count on the opened file reveals it. + const workspace = await fs.mkdtemp(path.join(os.tmpdir(), "cursor-mcp-hardlink-")); + try { + const inner = path.join(workspace, "ws"); + const outside = path.join(workspace, "outside"); + await fs.mkdir(inner); + await fs.mkdir(outside); + const victim = path.join(outside, "secret.txt"); + await Bun.write(victim, "SECRET"); + await fs.link(victim, path.join(inner, "innocent.txt")); + const handlers = new CursorExecHandlers({ + cwd: inner, + tools: new Map(), + mcpResources: { + serverNames: () => ["files"], + getServerResources: async () => undefined, + readServerResource: async (_name, uri) => ({ contents: [{ uri, text: "payload" }] }), + }, + }); + + await expect( + handlers.readMcpResource({ server: "files", uri: "files://x", downloadPath: "innocent.txt" }), + ).rejects.toThrow(/hard links/); + expect(await Bun.file(victim).text()).toBe("SECRET"); + } finally { + await removeWithRetries(workspace); + } + }); + + it("refuses a download onto a FIFO instead of blocking on it", async () => { + // A write-only open of a FIFO blocks until a reader attaches, and + // `download_path` comes from the server — so a named pipe planted (or + // simply present) in the workspace hung the turn forever, with the + // non-regular-file guard sitting unreachable behind the open. The refusal + // has to come from the open itself, which is what `O_NONBLOCK` buys. + const workspace = await fs.mkdtemp(path.join(os.tmpdir(), "cursor-mcp-fifo-")); + try { + const fifo = path.join(workspace, "pipe"); + const mkfifo = Bun.spawn(["mkfifo", fifo]); + if ((await mkfifo.exited) !== 0) throw new Error("mkfifo failed"); + const handlers = new CursorExecHandlers({ + cwd: workspace, + tools: new Map(), + mcpResources: { + serverNames: () => ["files"], + getServerResources: async () => undefined, + readServerResource: async (_name, uri) => ({ contents: [{ uri, text: "payload" }] }), + }, + }); + + await expect( + handlers.readMcpResource({ server: "files", uri: "files://x", downloadPath: "pipe" }), + ).rejects.toThrow(/special file|non-regular file/); + } finally { + await removeWithRetries(workspace); + } + // A regression does not fail this assertion — it never reaches it, because + // the open never returns. The timeout IS the detector, raised off the 5s + // default only so a slow runner cannot claim the same verdict. + }, 20_000); + + it("refuses a download when the session withheld file mutation or policy denies it", async () => { + // Download mode creates and overwrites workspace files without going + // through a registry tool, so nothing else enforces the session's + // mutation rules — the same hole the native `delete` frame had. A + // channel that was never granted a file-writing tool, and a `write` tier + // the user denied, must both stop it before the read, so a refused + // download does not even fetch the resource. + const workspace = await fs.mkdtemp(path.join(os.tmpdir(), "cursor-mcp-grant-")); + try { + let reads = 0; + const mcpResources = { + serverNames: () => ["files"], + getServerResources: async () => undefined, + readServerResource: async (_name: string, uri: string) => { + reads++; + return { contents: [{ uri, text: "payload" }] }; + }, + }; + + const ungranted = new CursorExecHandlers({ + cwd: workspace, + tools: new Map(), + allowDirectFileMutation: false, + mcpResources, + }); + await expect( + ungranted.readMcpResource({ server: "files", uri: "files://x", downloadPath: "out.txt" }), + ).rejects.toThrow(/not available/); + + const denied = new CursorExecHandlers({ + cwd: workspace, + tools: new Map(), + mcpResources, + getToolContext: () => + ({ settings: Settings.isolated({ "tools.approval": { write: "deny" } }) }) as AgentToolContext, + }); + await expect( + denied.readMcpResource({ server: "files", uri: "files://x", downloadPath: "out.txt" }), + ).rejects.toThrow(/blocked by user policy/); + + expect(reads).toBe(0); + expect(await Bun.file(path.join(workspace, "out.txt")).exists()).toBe(false); + + // A read without `download_path` mutates nothing, so it is unaffected. + const read = await ungranted.readMcpResource({ server: "files", uri: "files://x" }); + expect(read?.text).toBe("payload"); + expect(reads).toBe(1); + } finally { + await removeWithRetries(workspace); + } + }); + + it("answers nothing when the session has no MCP manager", async () => { + // A host without MCP must still answer truthfully rather than throwing: + // an empty catalog and `not_found` are the honest responses. + const handlers = new CursorExecHandlers({ cwd: ".", tools: new Map() }); + expect(await handlers.listMcpResources({})).toEqual([]); + expect(await handlers.readMcpResource({ server: "docs", uri: "docs://x" })).toBeNull(); + }); }); function cursorAssistantMessage(): AssistantMessage { @@ -244,6 +1188,7 @@ function newBlockState(): BlockState { return toolCall; }, firstTokenTime: undefined, + openToolCalls: new Map(), resolvedMcpToolCallIds: new Set(), setTextBlock: b => { textBlock = b; @@ -376,13 +1321,13 @@ describe("CursorExecHandlers native delete gating (issue #5680)", () => { await removeWithRetries(cwd); }); - it("rejects native delete and preserves the file when allowNativeDelete is false", async () => { + it("rejects native delete and preserves the file when allowDirectFileMutation is false", async () => { const target = path.join(cwd, "victim.txt"); await Bun.write(target, "keep me"); const handlers = new CursorExecHandlers({ cwd, tools: new Map(), - allowNativeDelete: false, + allowDirectFileMutation: false, }); const result = await handlers.delete(create(DeleteArgsSchema, { toolCallId: "call-del", path: target })); @@ -392,13 +1337,13 @@ describe("CursorExecHandlers native delete gating (issue #5680)", () => { expect(await Bun.file(target).exists()).toBe(true); }); - it("performs native delete when allowNativeDelete is true", async () => { + it("performs native delete when allowDirectFileMutation is true", async () => { const target = path.join(cwd, "victim.txt"); await Bun.write(target, "remove me"); const handlers = new CursorExecHandlers({ cwd, tools: new Map(), - allowNativeDelete: true, + allowDirectFileMutation: true, }); const result = await handlers.delete(create(DeleteArgsSchema, { toolCallId: "call-del", path: target })); @@ -419,7 +1364,7 @@ describe("CursorExecHandlers native delete gating (issue #5680)", () => { cwd, getCwd: () => currentCwd, tools: new Map(), - allowNativeDelete: true, + allowDirectFileMutation: true, }); currentCwd = movedCwd; @@ -429,4 +1374,335 @@ describe("CursorExecHandlers native delete gating (issue #5680)", () => { expect(await Bun.file(originalTarget).exists()).toBe(true); expect(await Bun.file(movedTarget).exists()).toBe(false); }); + + it("refuses a native delete the user's policy blocks", async () => { + // `allowDirectFileMutation` answers "was a mutating tool granted", not "does + // policy allow this call". The frame removes the file with `fs.rmSync` + // instead of running a registry tool, so no approval wrapper sits in + // front of it — a configured `deny` still lost the file. + const target = path.join(cwd, "protected.txt"); + await Bun.write(target, "keep me\n"); + const settings = Settings.isolated({ "tools.approval": { delete: "deny" } }); + const handlers = new CursorExecHandlers({ + cwd, + tools: new Map(), + allowDirectFileMutation: true, + getToolContext: () => ({ settings }) as AgentToolContext, + }); + + const result = await handlers.delete( + create(DeleteArgsSchema, { toolCallId: "call-deny", path: "protected.txt" }), + ); + + expect(result.isError).toBe(true); + expect(await Bun.file(target).exists()).toBe(true); + }); + + it("refuses a native delete in always-ask mode, which has no prompt channel", async () => { + // The exec channel cannot raise an interactive approval, so a mode that + // demands one must fail closed rather than silently auto-approving. + const target = path.join(cwd, "asked.txt"); + await Bun.write(target, "keep me\n"); + const settings = Settings.isolated({ "tools.approvalMode": "always-ask" }); + const handlers = new CursorExecHandlers({ + cwd, + tools: new Map(), + allowDirectFileMutation: true, + getToolContext: () => ({ settings }) as AgentToolContext, + }); + + const result = await handlers.delete(create(DeleteArgsSchema, { toolCallId: "call-ask", path: "asked.txt" })); + + expect(result.isError).toBe(true); + expect(await Bun.file(target).exists()).toBe(true); + }); +}); + +// A `smart_mode_approval_only` frame asks whether an MCP call would be allowed, +// ahead of the call itself. The verdict has to come from the same policy that +// gates execution — without running the tool to find out. +describe("CursorExecHandlers MCP approval preflight", () => { + let cwd: string; + + beforeEach(async () => { + cwd = await fs.mkdtemp(path.join(os.tmpdir(), "cursor-preflight-test-")); + }); + + afterEach(async () => { + await removeWithRetries(cwd); + }); + + function mcpHandlers(settings: Settings): { handlers: CursorExecHandlers; executed: () => number } { + let executed = 0; + const tool: AgentTool = { + name: "mcp__ops__deploy", + label: "deploy", + description: "", + parameters: type({}), + execute: async () => { + executed += 1; + return { content: [{ type: "text", text: "ran" }] }; + }, + } as unknown as AgentTool; + const handlers = new CursorExecHandlers({ + cwd, + tools: new Map([[tool.name, tool]]), + getToolContext: () => ({ settings }) as AgentToolContext, + }); + return { handlers, executed: () => executed }; + } + + const call = { + name: "mcp__ops__deploy", + toolName: "mcp__ops__deploy", + toolCallId: "c1", + providerIdentifier: "ops", + args: {}, + rawArgs: {}, + }; + + it("approves a call the policy allows, without running it", async () => { + const { handlers, executed } = mcpHandlers(Settings.isolated({ "tools.approvalMode": "yolo" })); + + expect(await handlers.mcpApprovalPreflight(call)).toBe(true); + // Approval is the answer; the invocation is a separate frame. + expect(executed()).toBe(0); + }); + + it("refuses a call the user's policy denies", async () => { + const { handlers, executed } = mcpHandlers( + Settings.isolated({ "tools.approvalMode": "yolo", "tools.approval": { mcp__ops__deploy: "deny" } }), + ); + + // Approving here would launder the deny into a server-side blessing. + expect(await handlers.mcpApprovalPreflight(call)).toBe(false); + expect(executed()).toBe(0); + }); + + it("refuses when the policy demands a prompt this frame cannot raise", async () => { + const { handlers } = mcpHandlers(Settings.isolated({ "tools.approvalMode": "always-ask" })); + + // The user is asked for real when the call arrives; answering yes on + // their behalf pre-authorizes something they never saw. + expect(await handlers.mcpApprovalPreflight(call)).toBe(false); + }); + + it("refuses a tool the session does not have", async () => { + const { handlers } = mcpHandlers(Settings.isolated({ "tools.approvalMode": "yolo" })); + + expect( + await handlers.mcpApprovalPreflight({ ...call, name: "mcp__ops__absent", toolName: "mcp__ops__absent" }), + ).toBe(false); + }); +}); + +// The Pi frames (`ExecServerMessage` 45-51) are a separate wire family from the +// legacy `read`/`shell`/`grep` args, with different field names and different +// semantics. Each bridge handler therefore performs a real translation, and a +// wrong one silently searches the wrong thing instead of failing. +describe("CursorExecHandlers Pi frame translation", () => { + let cwd: string; + + beforeEach(async () => { + cwd = await fs.mkdtemp(path.join(os.tmpdir(), "cursor-pi-test-")); + }); + + afterEach(async () => { + await removeWithRetries(cwd); + }); + + /** Captures the args one local tool was invoked with. */ + function recordingHandlers(toolName: string): { handlers: CursorExecHandlers; calls: unknown[] } { + const calls: unknown[] = []; + const tool: AgentTool = { + name: toolName, + label: toolName, + description: "records its args", + parameters: type({}), + execute: async (_toolCallId: string, params: unknown) => { + calls.push(params); + return { content: [{ type: "text" as const, text: "ok" }] }; + }, + }; + const handlers = new CursorExecHandlers({ cwd, tools: new Map([[toolName, tool]]) }); + return { handlers, calls }; + } + + it("inverts pi_grep's ignore_case into the local tool's case-sensitivity flag", async () => { + // `ignore_case` and `case` are opposites. Passing the frame's value + // straight through would flip every search's matching. + const { handlers, calls } = recordingHandlers("grep"); + + await handlers.piGrep({ toolCallId: "c1", args: { pattern: "x", ignoreCase: true } } as never); + await handlers.piGrep({ toolCallId: "c2", args: { pattern: "x", ignoreCase: false } } as never); + + expect(calls).toEqual([ + { pattern: "x", path: ".", case: false }, + // Case-sensitive is the local default, so `false` maps to "unset", + // not to `case: true`. + { pattern: "x", path: ".", case: undefined }, + ]); + }); + + it("folds pi_grep's separate glob onto the local tool's single path spec", async () => { + const { handlers, calls } = recordingHandlers("grep"); + + await handlers.piGrep({ toolCallId: "c1", args: { pattern: "x", path: "src", glob: "**/*.ts" } } as never); + await handlers.piGrep({ toolCallId: "c2", args: { pattern: "x", glob: "**/*.ts" } } as never); + await handlers.piGrep({ toolCallId: "c3", args: { pattern: "x", path: ".", glob: "**/*.ts" } } as never); + await handlers.piGrep({ toolCallId: "c4", args: { pattern: "x", path: "src", glob: "/abs/**/*.ts" } } as never); + + expect((calls[0] as { path: string }).path).toBe("src/**/*.ts"); + // An absent or "." path leaves the glob standing alone: a "./"-prefixed + // spec is a needlessly different path expression for the same scope. + expect((calls[1] as { path: string }).path).toBe("**/*.ts"); + expect((calls[2] as { path: string }).path).toBe("**/*.ts"); + // An absolute glob ignores the frame's path entirely. + expect((calls[3] as { path: string }).path).toBe("/abs/**/*.ts"); + }); + + it("composes pi_read's offset/limit onto the path as the read tool's range selector", async () => { + // `read` takes no range kwargs, so a dropped offset/limit silently returns + // the whole file. `offset` is a 1-indexed start and `limit` a line count, + // which is exactly the `:N+K` selector — `raw`, since a plain range pads + // with context lines the frame never asked for. + const { handlers, calls } = recordingHandlers("read"); + + await handlers.piRead({ toolCallId: "c1", args: { path: "a.ts", offset: 5, limit: 20 } } as never); + await handlers.piRead({ toolCallId: "c2", args: { path: "a.ts", offset: 5 } } as never); + await handlers.piRead({ toolCallId: "c3", args: { path: "a.ts", limit: 20 } } as never); + await handlers.piRead({ toolCallId: "c4", args: { path: "a.ts" } } as never); + // `optional int32`: a present 0 offset is not "unset". The reference + // clamps it to the first line rather than falling back to no range. + await handlers.piRead({ toolCallId: "c5", args: { path: "a.ts", offset: 0, limit: 20 } } as never); + + expect(calls).toEqual([ + { path: "a.ts:raw:5+20" }, + { path: "a.ts:raw:5-" }, + { path: "a.ts:raw:1+20" }, + { path: "a.ts" }, + { path: "a.ts:raw:1+20" }, + ]); + }); + + it("answers a present pi_read limit of zero with empty output instead of the whole file", async () => { + // `limit: 0` is present, not unset: the reference slices zero lines. No + // `read` selector expresses an empty range, so treating it as unset would + // return the entire file — the opposite of what was asked. + const { handlers, calls } = recordingHandlers("read"); + + const result = await handlers.piRead({ toolCallId: "c1", args: { path: "a.ts", limit: 0 } } as never); + + expect(calls).toEqual([]); + expect(result.isError).toBe(false); + expect(result.content).toEqual([{ type: "text", text: "" }]); + }); + + it("composes the legacy read frame's offset/limit the same way pi_read does", async () => { + // Modern Cursor builds paginate the legacy `read` frame too. Dropping the + // range returns the whole file for every page, so a model walking a large + // file never advances past the first window. + const { handlers, calls } = recordingHandlers("read"); + + await handlers.read({ toolCallId: "c1", path: "a.ts", offset: 5, limit: 20 } as never); + await handlers.read({ toolCallId: "c2", path: "a.ts" } as never); + + expect(calls).toEqual([{ path: "a.ts:raw:5+20" }, { path: "a.ts" }]); + }); + + it("forwards the legacy grep frame's offset as the local tool's file skip", async () => { + // `grep` paginates by file and reports "use skip=N for the next page" in + // that same unit. An unforwarded offset re-runs the identical search, so + // every page after the first repeats page one. + const { handlers, calls } = recordingHandlers("grep"); + + await handlers.grep({ toolCallId: "c1", pattern: "x", path: "src", offset: 20 } as never); + await handlers.grep({ toolCallId: "c2", pattern: "x", path: "src" } as never); + // A present 0 is "start at the beginning", which is the unset search. + await handlers.grep({ toolCallId: "c3", pattern: "x", path: "src", offset: 0 } as never); + + expect(calls.map(call => (call as { skip?: number }).skip)).toEqual([20, undefined, undefined]); + }); + + it("returns exactly the lines a pi_read range asked for", async () => { + // Producer/consumer contract against the real `ReadTool`: a plain `:N+K` + // selector deliberately pads with one leading and three trailing context + // lines, so offset 5/limit 20 would hand Cursor lines 4-27 for a request + // that named 5-24. The frame has no way to tell the padding apart from + // content it asked for. + const cwd = await fs.mkdtemp(path.join(os.tmpdir(), "cursor-piread-range-")); + try { + await Bun.write( + path.join(cwd, "n.txt"), + `${Array.from({ length: 40 }, (_, i) => `line${i + 1}`).join("\n")}\n`, + ); + const handlers = new CursorExecHandlers({ + cwd, + tools: new Map([["read", new ReadTool(createTestSession(cwd))]]), + }); + + const result = await handlers.piRead({ + toolCallId: "c1", + args: { path: "n.txt", offset: 5, limit: 20 }, + } as never); + const text = result.content + .filter(part => part.type === "text") + .map(part => (part as { text: string }).text) + .join(""); + + expect(text.trimEnd().split("\n")).toEqual(Array.from({ length: 20 }, (_, i) => `line${i + 5}`)); + } finally { + await removeWithRetries(cwd); + } + }); + + it("escapes pi_grep's pattern when the frame asks for a literal search", async () => { + // The local tool is regex-only, so an unescaped literal turns regex + // metacharacters into operators and matches the wrong lines. + const { handlers, calls } = recordingHandlers("grep"); + + await handlers.piGrep({ toolCallId: "c1", args: { pattern: "a.b(c)", literal: true } } as never); + await handlers.piGrep({ toolCallId: "c2", args: { pattern: "a.b(c)" } } as never); + + expect((calls[0] as { pattern: string }).pattern).toBe("a\\.b\\(c\\)"); + expect((calls[1] as { pattern: string }).pattern).toBe("a.b(c)"); + }); + + it("routes pi_find to glob, not grep, joining its pattern onto the path", async () => { + // `pi_find` searches filenames. Routing it to `grep` would search file + // contents for the glob text and return nothing. + const { handlers, calls } = recordingHandlers("glob"); + + await handlers.piFind({ toolCallId: "c1", args: { pattern: "*.ts", path: "src", limit: 10 } } as never); + await handlers.piFind({ toolCallId: "c2", args: { pattern: "*.ts", limit: 0 } } as never); + await handlers.piFind({ toolCallId: "c3", args: { pattern: "*.ts" } } as never); + + expect(calls).toEqual([ + { path: "src/*.ts", limit: 10 }, + // `optional int32`: a present 0 is clamped to 1 (as the reference + // does), not silently widened to the tool's default. + { path: "*.ts", limit: 1 }, + // Genuinely unset leaves the local tool's own default in place. + { path: "*.ts", limit: undefined }, + ]); + }); + + it("renames pi_edit's camelCase replacements to the local tool's snake_case pairs", async () => { + const { handlers, calls } = recordingHandlers("edit"); + + await handlers.piEdit({ + toolCallId: "c1", + args: { path: "a.ts", edits: [{ oldText: "before", newText: "after" }] }, + } as never); + + expect(calls[0]).toEqual({ path: "a.ts", edits: [{ old_text: "before", new_text: "after" }] }); + }); + + it("lists directories for pi_ls through read, defaulting an empty path to cwd", async () => { + const { handlers, calls } = recordingHandlers("read"); + + await handlers.piLs({ toolCallId: "c1", args: { path: "" } } as never); + + expect(calls[0]).toEqual({ path: "." }); + }); }); diff --git a/packages/coding-agent/test/extensibility/legacy-pi-bundled-subpath-overrides.test.ts b/packages/coding-agent/test/extensibility/legacy-pi-bundled-subpath-overrides.test.ts index 6d2919361..bd74df6e3 100644 --- a/packages/coding-agent/test/extensibility/legacy-pi-bundled-subpath-overrides.test.ts +++ b/packages/coding-agent/test/extensibility/legacy-pi-bundled-subpath-overrides.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from "bun:test"; +import * as fs from "node:fs/promises"; import * as path from "node:path"; import * as url from "node:url"; import { __buildLegacyPiPackageRootOverrides } from "@oh-my-pi/pi-coding-agent/extensibility/plugins/legacy-pi-compat"; @@ -83,6 +84,60 @@ process.stdout.write(JSON.stringify([ expect(bundledModuleKeys.has("@oh-my-pi/pi-ai/oauth/openai-codex")).toBe(true); }); + it("actually loads the shim's shared Pi translation through the bundled registry", async () => { + // The legacy shim performs the same Pi arg translation as the modern + // bridge and imports the shared helpers rather than copying them. Those + // live in a single-segment `providers/` module on purpose: `./providers/*` + // cannot match a nested `providers//` specifier, which would + // fall through to `Bun.resolveSync` and fail under bunfs (issue #3442). + // + // Executing the generated registry is the contract — a key present in the + // override map still proves nothing if the module cannot be imported. + const key = "@oh-my-pi/pi-ai/providers/cursor-pi-args"; + const entry = (await collectBundledPiEntries()).find(candidate => candidate.key === key); + expect(entry).toBeDefined(); + + // The rendered registry imports by bare specifier, exactly as the real + // bundle does, so it must run somewhere those specifiers resolve — the + // package itself. A temp dir has no workspace links and would fail for + // a reason unrelated to the export map. + const packageRoot = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "..", ".."); + const registryPath = path.join(packageRoot, `.probe-legacy-pi-args-${Bun.randomUUIDv7()}.ts`); + await Bun.write( + registryPath, + `${__renderLegacyPiVirtualModule([entry!])} +const mod = await BUNDLED_PI_MODULE_LOADERS[${JSON.stringify(key)}](); +process.stdout.write(JSON.stringify([ + mod.piEscapeRegexLiteral("a.b*c"), + mod.piJoinPath("src", "*.ts"), +])); +`, + ); + let exitCode: number; + let stdout: string; + let stderr: string; + try { + const proc = Bun.spawn([process.execPath, registryPath], { + cwd: packageRoot, + stdout: "pipe", + stderr: "pipe", + }); + [exitCode, stdout, stderr] = await Promise.all([ + proc.exited, + new Response(proc.stdout).text(), + new Response(proc.stderr).text(), + ]); + } finally { + await fs.rm(registryPath, { force: true }); + } + expect(stderr).toBe(""); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual(["a\\.b\\*c", path.join("src", "*.ts")]); + + const overrides = __buildLegacyPiPackageRootOverrides(true, bundledModuleKeys); + expect(overrides[key]).toBe(`omp-legacy-pi-bundled:${key}`); + }); + it("expands web search provider wildcard exports for compiled plugin imports", () => { const overrides = __buildLegacyPiPackageRootOverrides(true, bundledModuleKeys); const providerKeys = [ diff --git a/packages/coding-agent/test/extension-context-async-jobs.test.ts b/packages/coding-agent/test/extension-context-async-jobs.test.ts new file mode 100644 index 000000000..611385b93 --- /dev/null +++ b/packages/coding-agent/test/extension-context-async-jobs.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from "bun:test"; +import { ExtensionRunner } from "@oh-my-pi/pi-coding-agent/extensibility/extensions/runner"; +import type { ExtensionRuntime } from "@oh-my-pi/pi-coding-agent/extensibility/extensions/types"; +import type { AsyncJobSnapshot } from "@oh-my-pi/pi-coding-agent/session/agent-session"; + +function createRunner(getAsyncJobSnapshot?: () => AsyncJobSnapshot | null): ExtensionRunner { + const runtime = { + flagValues: new Map(), + pendingProviderRegistrations: [], + } as unknown as ExtensionRuntime; + return new ExtensionRunner( + [], + runtime, + "/tmp", + {} as never, + {} as never, + undefined, + undefined, + undefined, + getAsyncJobSnapshot, + ); +} + +describe("ExtensionRunner async job context", () => { + it("defaults to null outside a session", () => { + expect(createRunner().createContext().getAsyncJobSnapshot()).toBeNull(); + }); + + it("exposes the owning session snapshot", () => { + const snapshot: AsyncJobSnapshot = { + running: [{ id: "bg-1", type: "bash", status: "running", label: "sleep 30", startTime: 1 }], + recent: [], + delivery: { queued: 0, delivering: false, pendingJobIds: [] }, + }; + expect( + createRunner(() => snapshot) + .createContext() + .getAsyncJobSnapshot(), + ).toBe(snapshot); + }); +}); diff --git a/packages/coding-agent/test/extensions-runner.test.ts b/packages/coding-agent/test/extensions-runner.test.ts index 277a63f06..b25dd020f 100644 --- a/packages/coding-agent/test/extensions-runner.test.ts +++ b/packages/coding-agent/test/extensions-runner.test.ts @@ -2829,6 +2829,169 @@ describe("ExtensionRunner", () => { }); }); + describe("mcp_notification", () => { + it("delivers mcp_notification events to subscribed extensions with the typed payload", async () => { + const eventsPath = path.join(tempDir.path(), "mcp-notification-events.jsonl"); + const extCode = ` + import * as fs from "node:fs"; + + export default function(pi) { + pi.on("mcp_notification", async (event) => { + fs.appendFileSync( + ${JSON.stringify(eventsPath)}, + JSON.stringify({ + type: event.type, + server: event.server, + method: event.method, + params: event.params, + }) + "\\n", + ); + }); + } + `; + fs.writeFileSync(path.join(extensionsDir, "mcp-notification.ts"), extCode); + + const result = await loadTestExtensions(); + const runner = new ExtensionRunner( + result.extensions, + result.runtime, + tempDir.path(), + sessionManager, + modelRegistry, + ); + runner.initialize( + { + sendMessage: () => {}, + sendUserMessage: () => {}, + appendEntry: () => {}, + setLabel: () => {}, + getActiveTools: () => [], + getAllTools: () => [], + setActiveTools: async () => {}, + getCommands: () => [], + setModel: async () => false, + getThinkingLevel: () => undefined, + setThinkingLevel: () => {}, + getSessionName: () => sessionManager.getSessionName(), + setSessionName: async () => {}, + }, + { + getModel: () => undefined, + isIdle: () => true, + abort: () => {}, + hasPendingMessages: () => false, + shutdown: () => {}, + getContextUsage: () => undefined, + compact: async () => {}, + getSystemPrompt: () => [], + }, + ); + + await runner.emitMcpNotification({ + server: "peers", + method: "notifications/peer_message", + params: { from: "alice", text: "hi" }, + }); + + const events = fs + .readFileSync(eventsPath, "utf8") + .trim() + .split("\n") + .map(line => JSON.parse(line)); + expect(events).toEqual([ + { + type: "mcp_notification", + server: "peers", + method: "notifications/peer_message", + params: { from: "alice", text: "hi" }, + }, + ]); + }); + + it("buffers pre-initialize events and drains them on initialize (caps at 100, drops oldest)", async () => { + // Guard against regression of the two-layer startup race that Codex flagged + // on PR #6535 (commit ffa058aa8): the sdk.ts bridge wires + // mcpManager.addNotificationListener inside createAgentSession BEFORE the + // mode controller calls ExtensionRunner.initialize(). Frames the manager + // drains from its own buffer arrive here pre-init. Prior behavior silently + // dropped them; the fix buffers and drains on initialize (same shape as + // emitCredentialDisabled). + const eventsPath = path.join(tempDir.path(), "mcp-notification-cap.jsonl"); + const extCode = ` + import * as fs from "node:fs"; + + export default function(pi) { + pi.on("mcp_notification", async (event) => { + fs.appendFileSync( + ${JSON.stringify(eventsPath)}, + JSON.stringify({ server: event.server, method: event.method }) + "\\n", + ); + }); + } + `; + fs.writeFileSync(path.join(extensionsDir, "mcp-notification-cap.ts"), extCode); + + const result = await loadTestExtensions(); + const runner = new ExtensionRunner( + result.extensions, + result.runtime, + tempDir.path(), + sessionManager, + modelRegistry, + ); + + // Push 101 events while uninitialized — the 1st should be dropped, next 100 buffered. + for (let i = 0; i < 101; i++) { + await runner.emitMcpNotification({ + server: "peers", + method: `notifications/test/${i}`, + params: null, + }); + } + + runner.initialize( + { + sendMessage: () => {}, + sendUserMessage: () => {}, + appendEntry: () => {}, + setLabel: () => {}, + getActiveTools: () => [], + getAllTools: () => [], + setActiveTools: async () => {}, + getCommands: () => [], + setModel: async () => false, + getThinkingLevel: () => undefined, + setThinkingLevel: () => {}, + getSessionName: () => sessionManager.getSessionName(), + setSessionName: async () => {}, + }, + { + getModel: () => undefined, + isIdle: () => true, + abort: () => {}, + hasPendingMessages: () => false, + shutdown: () => {}, + getContextUsage: () => undefined, + compact: async () => {}, + getSystemPrompt: () => [], + }, + ); + + // Drain microtasks so the fire-and-forget emit() calls inside initialize() complete. + for (let i = 0; i < 5; i++) await Promise.resolve(); + + const events = fs + .readFileSync(eventsPath, "utf8") + .trim() + .split("\n") + .map(line => JSON.parse(line)); + expect(events).toHaveLength(100); + // Drop-oldest policy: test/0 was evicted, test/1 survives as the head. + expect(events[0]?.method).toBe("notifications/test/1"); + expect(events[99]?.method).toBe("notifications/test/100"); + }); + }); + describe("managed timers (ctx.setInterval / ctx.setTimeout)", () => { it("contains a throwing interval callback instead of letting it escape as uncaughtException", () => { vi.useFakeTimers(); diff --git a/packages/coding-agent/test/fast-mode-scope.test.ts b/packages/coding-agent/test/fast-mode-scope.test.ts index 77cabc969..dcd78cc47 100644 --- a/packages/coding-agent/test/fast-mode-scope.test.ts +++ b/packages/coding-agent/test/fast-mode-scope.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import * as path from "node:path"; import { Agent } from "@oh-my-pi/pi-agent-core"; -import type { Api, Model } from "@oh-my-pi/pi-ai"; +import type { Api, Model, ProviderSessionState } from "@oh-my-pi/pi-ai"; import { buildModel } from "@oh-my-pi/pi-catalog/build"; import { getBundledModel } from "@oh-my-pi/pi-catalog/models"; import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry"; @@ -61,6 +61,28 @@ describe("/fast targets the current model's service-tier family", () => { expect(session.isFastModeEnabled()).toBe(true); }); + it("keeps Anthropic priority enabled while an exact-model provider fallback makes it inactive", async () => { + const model = getBundledModel("anthropic", "claude-sonnet-4-5"); + if (!model) throw new Error("Expected bundled test model anthropic/claude-sonnet-4-5 to exist"); + const session = await createSessionForModel(model); + session.setFastMode(true); + const state = { + strictToolsDisabled: false, + fastModeDisabled: true, + replayUnsignedThinkingDisabled: false, + close: () => {}, + } as ProviderSessionState & { fastModeDisabled: boolean }; + session.providerSessionState.set(`anthropic-messages:${model.baseUrl}\u0000${model.id}`, state); + + expect(session.isFastModeEnabled()).toBe(true); + expect(session.isFastModeActive()).toBe(false); + + session.setFastMode(true); + expect(session.isFastModeEnabled()).toBe(true); + expect(session.isFastModeActive()).toBe(true); + expect(state.fastModeDisabled).toBe(false); + }); + it("enables priority on the OpenAI family for an OpenAI model", async () => { const session = await createSession("openai", "gpt-5.2"); session.setFastMode(true); diff --git a/packages/coding-agent/test/fixtures/mock-rpc-agent.ts b/packages/coding-agent/test/fixtures/mock-rpc-agent.ts index f7a7ccc33..ef6dc791b 100755 --- a/packages/coding-agent/test/fixtures/mock-rpc-agent.ts +++ b/packages/coding-agent/test/fixtures/mock-rpc-agent.ts @@ -15,6 +15,20 @@ if (Bun.env.MOCK_RPC_IGNORE_SIGTERM === "1") { } const supportsProtocolV2 = Bun.env.MOCK_RPC_V2 === "1"; +const legacyState = { + thinkingLevel: "off", + isStreaming: false, + isCompacting: false, + steeringMode: "all", + followUpMode: "all", + interruptMode: "immediate", + sessionId: "mock-session", + autoCompactionEnabled: false, + messageCount: 0, + queuedMessageCount: 0, + todoPhases: [], +}; + let protocolV2Enabled = false; process.stdout.write( `${JSON.stringify( @@ -138,6 +152,26 @@ for await (const raw of console) { }); continue; } + if ( + frame.type === "get_state" && + (Bun.env.MOCK_RPC_LEGACY_STATE === "1" || Bun.env.MOCK_RPC_INVALID_TPS === "1") + ) { + const data = { + ...legacyState, + ...(Bun.env.MOCK_RPC_INVALID_TPS === "1" + ? { fastModeEnabled: false, fastModeActive: false, tokensPerSecond: "invalid" } + : {}), + }; + writeFrame({ + id, + type: "response", + command: frame.type, + success: true, + data, + }); + continue; + } + writeFrame({ id, type: "response", diff --git a/packages/coding-agent/test/fixtures/notifications-mcp.ts b/packages/coding-agent/test/fixtures/notifications-mcp.ts new file mode 100755 index 000000000..c8aa1c571 --- /dev/null +++ b/packages/coding-agent/test/fixtures/notifications-mcp.ts @@ -0,0 +1,86 @@ +#!/usr/bin/env bun +/** + * Test fixture: minimal stdio MCP server used by + * `mcp-manager-notification-listeners.test.ts` to exercise the notification + * listener API. On receiving the client's `notifications/initialized`, emits + * TWO server-to-client notification frames: + * + * 1. `notifications/tools/list_changed` — a known method that MCPManager + * handles internally (triggers a `tools/list` refresh). Delivered to + * listeners AFTER the internal handling; verifies fanout is not gated + * on the method being unknown. + * 2. A server-custom method (`notifications/custom/test-event`) with a + * distinctive payload — verifies that arbitrary methods are delivered + * to listeners verbatim, which is the extension use case (bridging a + * peer-messaging MCP's custom push into a session steer). + * + * The custom method + payload constants are exported so the test can assert + * on frame equality without duplicating literals. + */ +import * as readline from "node:readline"; + +export const CUSTOM_NOTIFICATION_METHOD = "notifications/custom/test-event"; +export const CUSTOM_NOTIFICATION_PAYLOAD: Readonly> = Object.freeze({ + hello: "world", + n: 42, +}); + +type JsonRpcMessage = { + jsonrpc: "2.0"; + id?: string | number; + method?: string; + params?: Record; +}; + +function buildResult(method: string): Record { + switch (method) { + case "initialize": + return { + protocolVersion: "2025-03-26", + serverInfo: { name: "notifications-fixture", version: "1.0.0" }, + capabilities: { tools: { listChanged: true } }, + }; + case "tools/list": + return { tools: [] }; + default: + return {}; + } +} + +function writeNotification(method: string, params?: Record): void { + const frame: JsonRpcMessage = { jsonrpc: "2.0", method }; + if (params) frame.params = params; + process.stdout.write(`${JSON.stringify(frame)}\n`); +} + +function startServer(): void { + const rl = readline.createInterface({ input: process.stdin }); + rl.on("line", line => { + void (async () => { + const trimmed = line.trim(); + if (trimmed.length === 0) return; + let msg: JsonRpcMessage; + try { + msg = JSON.parse(trimmed) as JsonRpcMessage; + } catch { + return; + } + const isNotification = msg.id === undefined || msg.id === null; + if (isNotification) { + if (msg.method === "notifications/initialized") { + writeNotification("notifications/tools/list_changed"); + writeNotification(CUSTOM_NOTIFICATION_METHOD, { ...CUSTOM_NOTIFICATION_PAYLOAD }); + } + return; + } + if (!msg.method) return; + const response = { jsonrpc: "2.0" as const, id: msg.id, result: buildResult(msg.method) }; + process.stdout.write(`${JSON.stringify(response)}\n`); + })(); + }); + rl.on("close", () => process.exit(0)); +} + +if (import.meta.main) { + startServer(); +} diff --git a/packages/coding-agent/test/foreign-session-stores.test.ts b/packages/coding-agent/test/foreign-session-stores.test.ts index 52eb0c97c..324898be4 100644 --- a/packages/coding-agent/test/foreign-session-stores.test.ts +++ b/packages/coding-agent/test/foreign-session-stores.test.ts @@ -7,6 +7,7 @@ import { ClaudeSessionStore } from "../src/session/claude-session-store"; import { CodexSessionStore } from "../src/session/codex-session-store"; import { persistForeignSession } from "../src/session/foreign-session-import"; import type { ForeignSessionInfo } from "../src/session/foreign-session-store"; +import { buildSessionContext } from "../src/session/session-context"; import { SessionManager } from "../src/session/session-manager"; import { FileSessionStorage } from "../src/session/session-storage"; @@ -170,8 +171,12 @@ describe("CodexSessionStore", () => { const id = "44444444-4444-4444-8444-444444444444"; const sessionPath = path.join(root, ".sessions", "2025", "01", "01", `rollout-${id}.jsonl`); await writeJsonl(path.join(root, "session_index.jsonl"), [ - { id, thread_name: "Legacy Codex", updated_at: "2025-01-01T00:00:09.000Z" }, + { id, thread_name: "Legacy Codex", updated_at: "2025-01-01T00:00:12.000Z" }, ]); + const replacementHistory = [ + { type: "message", role: "user", content: [{ type: "input_text", text: "Retained context" }] }, + { type: "compaction", encrypted_content: "encrypted-compaction" }, + ]; await writeJsonl(sessionPath, [ { type: "session_meta", timestamp: "2025-01-01T00:00:00.000Z", payload: { id, cwd } }, { type: "turn_context", timestamp: "2025-01-01T00:00:01.000Z", payload: { model: "gpt-5.3-codex" } }, @@ -196,18 +201,29 @@ describe("CodexSessionStore", () => { payload: { type: "function_call_output", call_id: "call-codex", output: "file contents" }, }, { - type: "event_msg", + type: "compacted", timestamp: "2025-01-01T00:00:06.000Z", + payload: { message: "", replacement_history: replacementHistory }, + }, + { type: "turn_context", timestamp: "2025-01-01T00:00:07.000Z", payload: { model: "gpt-5.3-codex" } }, + { + type: "response_item", + timestamp: "2025-01-01T00:00:08.000Z", + payload: { type: "message", role: "user", content: [{ type: "input_text", text: "Continue" }] }, + }, + { + type: "event_msg", + timestamp: "2025-01-01T00:00:09.000Z", payload: { type: "web_search_end", call_id: "call-web", query: "docs", action: { query: "docs" } }, }, { type: "response_item", - timestamp: "2025-01-01T00:00:07.000Z", + timestamp: "2025-01-01T00:00:10.000Z", payload: { type: "message", role: "assistant", content: [{ type: "output_text", text: "Done" }] }, }, { type: "event_msg", - timestamp: "2025-01-01T00:00:08.000Z", + timestamp: "2025-01-01T00:00:11.000Z", payload: { type: "thread_name_updated", thread_name: "Imported Codex" }, }, ]); @@ -224,6 +240,51 @@ describe("CodexSessionStore", () => { expect(entries.some(entry => entry.type === "model_change" && entry.model === "openai-codex/gpt-5.3-codex")).toBe( true, ); + const compaction = entries.find(entry => entry.type === "compaction"); + if (compaction?.type !== "compaction") throw new Error("Missing imported Codex compaction"); + expect(compaction.firstKeptEntryId).toBe(compaction.id); + expect(compaction.preserveData).toEqual({ + openaiRemoteCompaction: { + provider: "openai-codex", + replacementHistory, + compactionItem: replacementHistory[1], + }, + }); + const activeContext = buildSessionContext(entries); + expect(activeContext.messages.map(message => message.role)).toEqual([ + "compactionSummary", + "user", + "assistant", + "toolResult", + "assistant", + ]); + expect(activeContext.messages[1]).toMatchObject({ + role: "user", + content: [{ type: "text", text: "Continue" }], + }); + const activeSummary = activeContext.messages[0]; + if (activeSummary?.role !== "compactionSummary") throw new Error("Missing active Codex compaction context"); + expect(activeSummary.providerPayload).toEqual({ + type: "openaiResponsesHistory", + provider: "openai-codex", + items: replacementHistory, + }); + const transcript = buildSessionContext(entries, undefined, undefined, { transcript: true }); + expect(transcript.messages.map(message => message.role)).toEqual([ + "user", + "assistant", + "assistant", + "toolResult", + "compactionSummary", + "user", + "assistant", + "toolResult", + "assistant", + ]); + expect(transcript.messages[0]).toMatchObject({ + role: "user", + content: [{ type: "text", text: "Inspect" }], + }); const calls = new Set(); for (const entry of entries) { if (entry.type !== "message" || entry.message.role !== "assistant") continue; diff --git a/packages/coding-agent/test/issue-986-compaction-auth-fallback.test.ts b/packages/coding-agent/test/issue-986-compaction-auth-fallback.test.ts index e49551a73..27c5fe1b4 100644 --- a/packages/coding-agent/test/issue-986-compaction-auth-fallback.test.ts +++ b/packages/coding-agent/test/issue-986-compaction-auth-fallback.test.ts @@ -1,7 +1,9 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test"; import * as path from "node:path"; +import { scheduler } from "node:timers/promises"; import { Agent } from "@oh-my-pi/pi-agent-core"; import * as compactionModule from "@oh-my-pi/pi-agent-core/compaction"; +import * as AIError from "@oh-my-pi/pi-ai/error"; import { getBundledModel } from "@oh-my-pi/pi-catalog/models"; import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; @@ -81,25 +83,361 @@ describe("issue #986 compaction auth fallback", () => { return { currentModel, fallbackModel }; } - it("falls back to an authenticated role model when the current provider returns auth_unavailable", async () => { - const { currentModel, fallbackModel } = await createSession({ fallbackModelRole: "smol" }); - const compactSpy = vi.spyOn(compactionModule, "compact").mockImplementation(async (preparation, model) => { - if (model.provider === currentModel.provider && model.id === currentModel.id) { - throw new Error( - "Turn prefix summarization failed: 503 auth_unavailable: no auth available (providers=codex, model=gpt-5.4-mini)", - ); + async function createAutoNativeFallbackSession(options?: { sameProviderNativeEnabled?: boolean }) { + const currentModel = getBundledModel("openai", "gpt-5"); + const sameProviderBase = getBundledModel("openai", "gpt-5-mini"); + const sameProviderModel = + sameProviderBase && options?.sameProviderNativeEnabled === false + ? { ...sameProviderBase, remoteCompaction: { ...sameProviderBase.remoteCompaction, enabled: false } } + : sameProviderBase; + const crossProviderModel = getBundledModel("anthropic", "claude-sonnet-4-5"); + if (!currentModel || !sameProviderModel || !crossProviderModel) { + throw new Error("Expected bundled native fallback test models"); + } + + const settings = Settings.isolated({ + "compaction.autoContinue": false, + "compaction.keepRecentTokens": 1, + "compaction.strategy": "context-full", + "contextPromotion.enabled": false, + }); + settings.setModelRole("smol", `${sameProviderModel.provider}/${sameProviderModel.id}`); + settings.setModelRole("slow", `${crossProviderModel.provider}/${crossProviderModel.id}`); + const agent = new Agent({ + initialState: { model: currentModel, systemPrompt: ["Test"], tools: [], messages: [] }, + }); + + authStorage = await AuthStorage.create(path.join(tempDir.path(), "testauth.db")); + authStorage.setRuntimeApiKey(currentModel.provider, "openai-token"); + authStorage.setRuntimeApiKey(crossProviderModel.provider, "anthropic-token"); + modelRegistry = new ModelRegistry(authStorage, path.join(tempDir.path(), "models.yml")); + session = new AgentSession({ + agent, + sessionManager: SessionManager.inMemory(), + settings, + modelRegistry, + }); + session.subscribe(() => {}); + for (const [userText, assistantText] of [ + ["first question", "first answer"], + ["second question", "second answer"], + ] as const) { + const user = userMsg(userText); + const assistant = assistantMsg(assistantText); + session.agent.appendMessage(user); + session.sessionManager.appendMessage(user); + session.agent.appendMessage(assistant); + session.sessionManager.appendMessage(assistant); + } + vi.spyOn(modelRegistry, "getAvailable").mockReturnValue([currentModel, sameProviderModel, crossProviderModel]); + const apiKeySpy = vi.spyOn(modelRegistry, "getApiKey").mockResolvedValue("test-key"); + + const triggerAutoCompaction = async (): Promise => { + const { promise, resolve } = Promise.withResolvers(); + session.subscribe(event => { + if (event.type === "auto_compaction_end") resolve(); + }); + const contextWindow = currentModel.contextWindow; + if (!contextWindow) throw new Error("Expected current model context window"); + const assistant = { + ...assistantMsg("threshold reached"), + api: currentModel.api, + provider: currentModel.provider, + model: currentModel.id, + usage: { + input: contextWindow, + output: 1, + cacheRead: 0, + cacheWrite: 0, + totalTokens: contextWindow + 1, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, + }, + }; + session.agent.emitExternalEvent({ type: "message_end", message: assistant }); + session.agent.emitExternalEvent({ type: "agent_end", messages: [assistant] }); + await promise; + await session.waitForIdle(); + }; + + return { apiKeySpy, crossProviderModel, currentModel, sameProviderModel, triggerAutoCompaction }; + } + + it("continues same-provider native candidates but stops before crossing providers on non-auth failure", async () => { + const { crossProviderModel, currentModel, sameProviderModel, triggerAutoCompaction } = + await createAutoNativeFallbackSession(); + const attemptedModels: string[] = []; + vi.spyOn(compactionModule, "compact").mockImplementation(async (preparation, model) => { + attemptedModels.push(`${model.provider}/${model.id}`); + if (model.provider === currentModel.provider || model.provider === sameProviderModel.provider) { + throw new compactionModule.NativeCompactionError(new Error("native compaction transport failed")); } - if (model.provider !== fallbackModel.provider || model.id !== fallbackModel.id) { + if (model.provider !== crossProviderModel.provider || model.id !== crossProviderModel.id) { throw new Error(`Unexpected compaction model ${model.provider}/${model.id}`); } return { - summary: "fallback summary", - shortSummary: "fallback short summary", + summary: "cross-provider summary", + shortSummary: "cross-provider", firstKeptEntryId: preparation.firstKeptEntryId, tokensBefore: 42, - details: { provider: model.provider }, }; }); + + await triggerAutoCompaction(); + + expect(attemptedModels).toEqual([ + `${currentModel.provider}/${currentModel.id}`, + `${sameProviderModel.provider}/${sameProviderModel.id}`, + ]); + }); + + it("preserves a native transport failure when a later same-provider candidate fails authentication", async () => { + const { apiKeySpy, crossProviderModel, currentModel, sameProviderModel, triggerAutoCompaction } = + await createAutoNativeFallbackSession(); + apiKeySpy.mockImplementation(async model => + model.provider === crossProviderModel.provider ? undefined : "test-key", + ); + const attemptedModels: string[] = []; + let errorMessage: string | undefined; + session.subscribe(event => { + if (event.type === "auto_compaction_end") errorMessage = event.errorMessage; + }); + vi.spyOn(compactionModule, "compact").mockImplementation(async (_preparation, model) => { + attemptedModels.push(`${model.provider}/${model.id}`); + if (model.provider === currentModel.provider && model.id === currentModel.id) { + throw new compactionModule.NativeCompactionError(new Error("native compaction transport failed")); + } + if (model.provider === sameProviderModel.provider && model.id === sameProviderModel.id) { + throw new compactionModule.NativeCompactionError( + Object.assign(new Error("native compaction authentication failed"), { status: 401 }), + ); + } + throw new Error(`Unexpected compaction model ${model.provider}/${model.id}`); + }); + + await triggerAutoCompaction(); + + expect(attemptedModels).toEqual([ + `${currentModel.provider}/${currentModel.id}`, + `${sameProviderModel.provider}/${sameProviderModel.id}`, + ]); + expect(errorMessage).toContain("native compaction transport failed"); + }); + + it("skips unauthenticated cross-provider candidates before enforcing the native boundary", async () => { + const { apiKeySpy, crossProviderModel, currentModel, sameProviderModel, triggerAutoCompaction } = + await createAutoNativeFallbackSession(); + session.settings.setModelRole("smol", `${crossProviderModel.provider}/${crossProviderModel.id}`); + session.settings.setModelRole("slow", `${sameProviderModel.provider}/${sameProviderModel.id}`); + apiKeySpy.mockImplementation(async model => + model.provider === crossProviderModel.provider ? undefined : "test-key", + ); + const attemptedModels: string[] = []; + vi.spyOn(compactionModule, "compact").mockImplementation(async (preparation, model) => { + attemptedModels.push(`${model.provider}/${model.id}`); + if (model.provider === currentModel.provider && model.id === currentModel.id) { + throw new compactionModule.NativeCompactionError(new Error("native compaction transport failed")); + } + if (model.provider === sameProviderModel.provider && model.id === sameProviderModel.id) { + return { + summary: "authenticated same-provider summary", + shortSummary: "authenticated same-provider", + firstKeptEntryId: preparation.firstKeptEntryId, + tokensBefore: 42, + }; + } + throw new Error(`Unexpected compaction model ${model.provider}/${model.id}`); + }); + + await triggerAutoCompaction(); + + expect(attemptedModels).toEqual([ + `${currentModel.provider}/${currentModel.id}`, + `${sameProviderModel.provider}/${sameProviderModel.id}`, + ]); + }); + + it("retries a transient native compaction failure on the same candidate", async () => { + const { currentModel, triggerAutoCompaction } = await createAutoNativeFallbackSession(); + session.settings.set("retry.enabled", true); + session.settings.set("retry.baseDelayMs", 1); + session.settings.set("retry.maxRetries", 1); + const waitSpy = vi.spyOn(scheduler, "wait").mockResolvedValue(undefined); + const attemptedModels: string[] = []; + vi.spyOn(compactionModule, "compact").mockImplementation(async (preparation, model) => { + attemptedModels.push(`${model.provider}/${model.id}`); + if (model.provider !== currentModel.provider || model.id !== currentModel.id) { + throw new Error(`Unexpected compaction model ${model.provider}/${model.id}`); + } + if (attemptedModels.length === 1) { + throw new compactionModule.NativeCompactionError( + new AIError.ProviderHttpError("native compaction temporarily unavailable", 503), + ); + } + return { + summary: "native retry summary", + shortSummary: "native retry", + firstKeptEntryId: preparation.firstKeptEntryId, + tokensBefore: 42, + }; + }); + + await triggerAutoCompaction(); + + expect(attemptedModels).toEqual([ + `${currentModel.provider}/${currentModel.id}`, + `${currentModel.provider}/${currentModel.id}`, + ]); + expect(waitSpy).toHaveBeenCalledTimes(1); + }); + + it("preserves native timeout failures before crossing providers", async () => { + const { crossProviderModel, currentModel, sameProviderModel, triggerAutoCompaction } = + await createAutoNativeFallbackSession(); + const attemptedModels: string[] = []; + vi.spyOn(compactionModule, "compact").mockImplementation(async (preparation, model) => { + attemptedModels.push(`${model.provider}/${model.id}`); + if (model.provider === currentModel.provider || model.provider === sameProviderModel.provider) { + throw new compactionModule.NativeCompactionError(new Error("provider stream stall timeout")); + } + if (model.provider !== crossProviderModel.provider || model.id !== crossProviderModel.id) { + throw new Error(`Unexpected compaction model ${model.provider}/${model.id}`); + } + return { + summary: "cross-provider summary", + shortSummary: "cross-provider", + firstKeptEntryId: preparation.firstKeptEntryId, + tokensBefore: 42, + }; + }); + + await triggerAutoCompaction(); + + expect(attemptedModels).toEqual([ + `${currentModel.provider}/${currentModel.id}`, + `${sameProviderModel.provider}/${sameProviderModel.id}`, + ]); + }); + + it("stops auto-compaction before a same-provider candidate with native compaction disabled", async () => { + const { currentModel, sameProviderModel, triggerAutoCompaction } = await createAutoNativeFallbackSession({ + sameProviderNativeEnabled: false, + }); + const attemptedModels: string[] = []; + vi.spyOn(compactionModule, "compact").mockImplementation(async (preparation, model) => { + attemptedModels.push(`${model.provider}/${model.id}`); + if (model.provider === currentModel.provider && model.id === currentModel.id) { + throw new compactionModule.NativeCompactionError(new Error("native compaction transport failed")); + } + return { + summary: "generic same-provider summary", + shortSummary: "generic same-provider", + firstKeptEntryId: preparation.firstKeptEntryId, + tokensBefore: 42, + }; + }); + + await triggerAutoCompaction(); + + expect(attemptedModels).toEqual([`${currentModel.provider}/${currentModel.id}`]); + expect(sameProviderModel.remoteCompaction?.enabled).toBe(false); + }); + + it("preserves cross-provider auto-compaction fallback for auth-classified native failures", async () => { + const { crossProviderModel, currentModel, sameProviderModel, triggerAutoCompaction } = + await createAutoNativeFallbackSession(); + const attemptedModels: string[] = []; + vi.spyOn(compactionModule, "compact").mockImplementation(async (preparation, model) => { + attemptedModels.push(`${model.provider}/${model.id}`); + if (model.provider === currentModel.provider || model.provider === sameProviderModel.provider) { + throw new compactionModule.NativeCompactionError( + Object.assign(new Error("native compaction authentication failed"), { status: 401 }), + ); + } + if (model.provider !== crossProviderModel.provider || model.id !== crossProviderModel.id) { + throw new Error(`Unexpected compaction model ${model.provider}/${model.id}`); + } + return { + summary: "authenticated fallback summary", + shortSummary: "authenticated fallback", + firstKeptEntryId: preparation.firstKeptEntryId, + tokensBefore: 42, + }; + }); + + await triggerAutoCompaction(); + + expect(attemptedModels).toEqual([ + `${currentModel.provider}/${currentModel.id}`, + `${sameProviderModel.provider}/${sameProviderModel.id}`, + `${crossProviderModel.provider}/${crossProviderModel.id}`, + ]); + }); + + it("tries same-provider native candidates during manual compaction before crossing providers", async () => { + const { crossProviderModel, currentModel, sameProviderModel } = await createAutoNativeFallbackSession(); + const attemptedModels: string[] = []; + vi.spyOn(compactionModule, "compact").mockImplementation(async (preparation, model) => { + attemptedModels.push(`${model.provider}/${model.id}`); + if (model.provider === currentModel.provider && model.id === currentModel.id) { + throw new compactionModule.NativeCompactionError(new Error("native manual compaction failed")); + } + if (model.provider === sameProviderModel.provider && model.id === sameProviderModel.id) { + return { + summary: "same-provider manual summary", + shortSummary: "same-provider manual", + firstKeptEntryId: preparation.firstKeptEntryId, + tokensBefore: 42, + }; + } + throw new Error(`Unexpected compaction model ${model.provider}/${model.id}`); + }); + + const result = await session.compact(); + + expect(result.summary).toBe("same-provider manual summary"); + expect(attemptedModels).toEqual([ + `${currentModel.provider}/${currentModel.id}`, + `${sameProviderModel.provider}/${sameProviderModel.id}`, + ]); + expect(attemptedModels).not.toContain(`${crossProviderModel.provider}/${crossProviderModel.id}`); + }); + + it("falls back across providers when native compaction receives auth_unavailable", async () => { + const { currentModel, fallbackModel } = await createSession({ fallbackModelRole: "smol" }); + const originalCompact = compactionModule.compact; + const fetchMock = vi.fn(async () => + Response.json( + { error: { type: "auth_unavailable", message: "no auth available for codex" } }, + { status: 503, statusText: "Service Unavailable" }, + ), + ); + const compactSpy = vi + .spyOn(compactionModule, "compact") + .mockImplementation(async (preparation, model, apiKey, customInstructions, signal, options) => { + if (model.provider === currentModel.provider && model.id === currentModel.id) { + return originalCompact( + { + ...preparation, + settings: { ...preparation.settings, remoteStreamingV2Enabled: false }, + }, + model, + apiKey, + customInstructions, + signal, + { ...options, fetch: fetchMock }, + ); + } + if (model.provider !== fallbackModel.provider || model.id !== fallbackModel.id) { + throw new Error(`Unexpected compaction model ${model.provider}/${model.id}`); + } + return { + summary: "fallback summary", + shortSummary: "fallback short summary", + firstKeptEntryId: preparation.firstKeptEntryId, + tokensBefore: 42, + details: { provider: model.provider }, + }; + }); vi.spyOn(modelRegistry, "getApiKey").mockImplementation(async model => { if (model.provider === currentModel.provider && model.id === currentModel.id) return "codex-token"; if (model.provider === fallbackModel.provider && model.id === fallbackModel.id) return "anthropic-token"; @@ -109,6 +447,7 @@ describe("issue #986 compaction auth fallback", () => { const result = await session.compact(); expect(result.summary).toBe("fallback summary"); + expect(fetchMock).toHaveBeenCalled(); expect(compactSpy).toHaveBeenCalledTimes(2); expect(compactSpy.mock.calls.map(([, model]) => `${model.provider}/${model.id}`)).toEqual([ `${currentModel.provider}/${currentModel.id}`, diff --git a/packages/coding-agent/test/mcp-manager-notification-listeners.test.ts b/packages/coding-agent/test/mcp-manager-notification-listeners.test.ts new file mode 100644 index 000000000..8f2678c14 --- /dev/null +++ b/packages/coding-agent/test/mcp-manager-notification-listeners.test.ts @@ -0,0 +1,336 @@ +import { afterEach, beforeEach, describe, expect, it } from "bun:test"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { MCPManager } from "@oh-my-pi/pi-coding-agent/mcp/manager"; +import type { MCPServerConfig } from "@oh-my-pi/pi-coding-agent/mcp/types"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; +import { CUSTOM_NOTIFICATION_METHOD, CUSTOM_NOTIFICATION_PAYLOAD } from "./fixtures/notifications-mcp"; + +const FIXTURE_PATH = path.join(import.meta.dir, "fixtures", "notifications-mcp.ts"); +const BUN_EXEC = process.execPath; + +type CapturedFrame = { server: string; method: string; params: unknown }; + +function serverConfig(): MCPServerConfig { + return { type: "stdio", command: BUN_EXEC, args: [FIXTURE_PATH] }; +} + +/** + * Bind a capturing listener that ALSO exposes a promise-based await for a + * specific `(server, method)` pair. Lets each test wait for the exact frame it + * cares about without polling — the frame arriving is the real signal, not a + * timer. bun:test's per-test timeout backstops a genuine hang. + */ +function makeFrameCollector(): { + listener: (server: string, method: string, params: unknown) => void; + frames: CapturedFrame[]; + awaitFrame: (server: string, method: string) => Promise; +} { + const frames: CapturedFrame[] = []; + const waiters = new Map void>>(); + const key = (server: string, method: string) => `${server}\x00${method}`; + return { + frames, + listener: (server, method, params) => { + const frame: CapturedFrame = { server, method, params }; + frames.push(frame); + const bucket = waiters.get(key(server, method)); + if (bucket && bucket.length > 0) { + waiters.delete(key(server, method)); + for (const resolve of bucket) resolve(frame); + } + }, + awaitFrame: (server, method) => { + const existing = frames.find(f => f.server === server && f.method === method); + if (existing) return Promise.resolve(existing); + const { promise, resolve } = Promise.withResolvers(); + const bucket = waiters.get(key(server, method)) ?? []; + bucket.push(resolve); + waiters.set(key(server, method), bucket); + return promise; + }, + }; +} + +describe("MCPManager notification listeners", () => { + let workDir: string; + + beforeEach(() => { + workDir = fs.mkdtempSync(path.join(os.tmpdir(), "omp-mcp-notif-")); + }); + + afterEach(() => { + removeSyncWithRetries(workDir); + }); + + it("delivers known and server-custom notifications to a registered listener", async () => { + const manager = new MCPManager(workDir); + const collector = makeFrameCollector(); + const unsubscribe = manager.addNotificationListener(collector.listener); + expect(typeof unsubscribe).toBe("function"); + + try { + const result = await manager.connectServers({ alpha: serverConfig() }, {}); + expect(result.connectedServers).toContain("alpha"); + + // Await both the known list_changed and the server-custom frame + // independently. Arrival order across the two isn't guaranteed + // because list_changed triggers an internal `tools/list` refresh + // that MCPManager awaits before fanout (so listeners see fresh + // `getTools()`), which lets a non-refresh frame like the custom + // one overtake in the same batch. Both MUST still be delivered; + // that's the actual contract this test exercises. + const [listChanged, custom] = await Promise.all([ + collector.awaitFrame("alpha", "notifications/tools/list_changed"), + collector.awaitFrame("alpha", CUSTOM_NOTIFICATION_METHOD), + ]); + expect(custom.params).toEqual(CUSTOM_NOTIFICATION_PAYLOAD); + expect(listChanged.method).toBe("notifications/tools/list_changed"); + } finally { + await manager.disconnectAll(); + } + }); + + it("dispatches to every listener and isolates handler errors", async () => { + const manager = new MCPManager(workDir); + const collectorA = makeFrameCollector(); + const collectorC = makeFrameCollector(); + + manager.addNotificationListener(collectorA.listener); + manager.addNotificationListener(() => { + // Middle listener throws synchronously. Must NOT prevent the third + // listener from being invoked for the same frame. + throw new Error("listener-B intentionally throws"); + }); + manager.addNotificationListener(collectorC.listener); + + try { + await manager.connectServers({ alpha: serverConfig() }, {}); + const [frameA, frameC] = await Promise.all([ + collectorA.awaitFrame("alpha", CUSTOM_NOTIFICATION_METHOD), + collectorC.awaitFrame("alpha", CUSTOM_NOTIFICATION_METHOD), + ]); + expect(frameA.params).toEqual(CUSTOM_NOTIFICATION_PAYLOAD); + expect(frameC.params).toEqual(CUSTOM_NOTIFICATION_PAYLOAD); + } finally { + await manager.disconnectAll(); + } + }); + + it("unsubscribe stops delivery to that listener without affecting others", async () => { + const manager = new MCPManager(workDir); + const early = makeFrameCollector(); + const late = makeFrameCollector(); + + const unsubscribeEarly = manager.addNotificationListener(early.listener); + + try { + await manager.connectServers({ alpha: serverConfig() }, {}); + await early.awaitFrame("alpha", CUSTOM_NOTIFICATION_METHOD); + const earlyCountBeforeUnsub = early.frames.length; + + unsubscribeEarly(); + + // Add a fresh listener AFTER unsub, then connect a second server so + // the fixture emits a new batch. The unsubscribed listener must not + // grow; the fresh listener must observe the fresh batch. Dispatch + // per frame is synchronous over the listener set, so once `late` + // resolves for a `beta` frame, any concurrent delivery to `early` + // would have already happened had the unsubscribe not landed. + manager.addNotificationListener(late.listener); + await manager.connectServers({ beta: serverConfig() }, {}); + await late.awaitFrame("beta", CUSTOM_NOTIFICATION_METHOD); + + expect(early.frames.length).toBe(earlyCountBeforeUnsub); + } finally { + await manager.disconnectAll(); + } + }); + + it("buffers notifications received before any listener attaches, drains into first subscriber", async () => { + const manager = new MCPManager(workDir); + + try { + // Connect FIRST, before any listener is registered. The fixture + // emits `notifications/tools/list_changed` plus a server-custom + // notification during its `notifications/initialized` handshake, + // so by the time connect resolves at least one notification frame + // has already been received by the manager with no consumer. + await manager.connectServers({ alpha: serverConfig() }, {}); + + // Attach the FIRST listener AFTER frames were already dispatched. + // Drain-on-first-subscribe must deliver the buffered frames now. + const collector = makeFrameCollector(); + manager.addNotificationListener(collector.listener); + // Both the known list_changed and the server-custom frame must + // have been buffered and drained on attach. Await each + // independently — arrival order between refresh-triggering and + // non-refresh frames is not guaranteed (see the delivery test + // for rationale), but both are always delivered. + const [listChanged, custom] = await Promise.all([ + collector.awaitFrame("alpha", "notifications/tools/list_changed"), + collector.awaitFrame("alpha", CUSTOM_NOTIFICATION_METHOD), + ]); + expect(custom.params).toEqual(CUSTOM_NOTIFICATION_PAYLOAD); + expect(listChanged.method).toBe("notifications/tools/list_changed"); + } finally { + await manager.disconnectAll(); + } + }); + + it("refreshServerTools awaits an async setOnToolsChanged handler before resolving", async () => { + // Verifies the second-layer guarantee (issue raised in review): + // #onToolsChanged in the sdk.ts wiring calls session.refreshMCPTools() + // asynchronously. If refreshServerTools does not await the callback, + // a mcp_notification listener acting on tools/list_changed can call + // getAllTools() on the session before its registry has been rebound. + // This test drives the same pattern by registering a slow async + // setOnToolsChanged handler and confirming refreshServerTools blocks + // on it before resolving — proving the fanout in + // #handleServerNotification (which awaits refreshServerTools) sees + // the callback complete. + const manager = new MCPManager(workDir); + const events: string[] = []; + const { promise: hold, resolve: release } = Promise.withResolvers(); + + manager.setOnToolsChanged(async () => { + events.push("cb:start"); + await hold; + events.push("cb:end"); + }); + + try { + await manager.connectServers({ alpha: serverConfig() }, {}); + // connectServers may kick the callback for the initial tool load via + // its background continuation; drain that first so we can assert + // specifically on the refresh path. + await Bun.sleep(30); + release(); + await Bun.sleep(30); + const priorLen = events.length; + + // Trigger a fresh refresh and hold the callback again — this is the + // path #handleServerNotification takes for tools/list_changed. + const { promise: hold2, resolve: release2 } = Promise.withResolvers(); + let cbState: "started" | "ended" | undefined; + manager.setOnToolsChanged(async () => { + cbState = "started"; + await hold2; + cbState = "ended"; + }); + const refreshDone = manager.refreshServerTools("alpha"); + // Give the callback time to enter the await. + await Bun.sleep(20); + expect(cbState).toBe("started"); + // refreshServerTools has NOT resolved yet — proves it's awaiting. + let refreshResolved = false; + void refreshDone.then(() => { + refreshResolved = true; + }); + await Bun.sleep(10); + expect(refreshResolved).toBe(false); + // Release the callback; refresh should now resolve promptly. + release2(); + await refreshDone; + expect(cbState).toBe("ended"); + // Drain events reference — just to silence unused var if refactored. + expect(events.length).toBeGreaterThanOrEqual(priorLen); + } finally { + await manager.disconnectAll(); + } + }); + + it("mcp_notification listener for tools/list_changed fires only AFTER setOnToolsChanged callback resolves", async () => { + // Option B integration test: exercises the full manager-side chain + // #handleServerNotification → refreshServerTools → #onToolsChanged + // → fanout to notification listeners. Uses a slow async + // setOnToolsChanged callback that gates on a controllable promise + // (matches the mcp-dispose-disconnect-bounded.test.ts gate pattern) + // to prove that if the SDK's setOnToolsChanged handler is async and + // its work isn't complete, the mcp_notification listener does NOT + // fire prematurely. Regression guard: catches any future refactor + // that reintroduces fire-and-forget in the callback chain (e.g. the + // original sdk.ts:3411 `void (async () => ...)()` wrapper — which + // was the specific race raised in review of #6535). + const manager = new MCPManager(workDir); + const events: string[] = []; + let cbCall = 0; + const { promise: gate, resolve: releaseGate } = Promise.withResolvers(); + + manager.setOnToolsChanged(async () => { + cbCall++; + // Initial connect fires the callback via a background continuation + // (manager.ts line ~555, fire-and-forget). Return immediately so + // its floating promise resolves cleanly; only gate on the second + // call, which is the one our simulated post-connect notification + // triggers. + if (cbCall === 1) { + events.push("initial-cb:done"); + return; + } + events.push("cb:start"); + await gate; + events.push("cb:end"); + }); + + manager.addNotificationListener((_server, method) => { + if (method === "notifications/tools/list_changed") { + events.push("listener:fire"); + } + }); + + try { + await manager.connectServers({ alpha: serverConfig() }, {}); + // Wait for the initial-connect background continuation to fire the + // callback (call #1, ungated). Once it's recorded, we know the + // callback is idle and the next invocation will be call #2. + for (let i = 0; i < 20 && !events.includes("initial-cb:done"); i++) { + await Bun.sleep(10); + } + expect(events).toContain("initial-cb:done"); + + // Simulate a post-connect `notifications/tools/list_changed` frame + // by driving the transport's onNotification hook directly — that's + // exactly what the transport does when a server pushes a + // notification after startup. Bypasses the fixture's initial + // handshake burst so we exercise the connectionKnown=true path + // (where refresh actually runs and the callback is awaited). + const conn = manager.getConnection("alpha"); + if (!conn?.transport.onNotification) throw new Error("expected transport onNotification to be wired"); + void conn.transport.onNotification("notifications/tools/list_changed", {}); + + // Give the async chain time to enter the callback's await. + for (let i = 0; i < 20 && !events.includes("cb:start"); i++) { + await Bun.sleep(10); + } + expect(events).toContain("cb:start"); + + // CRITICAL: at this point the callback is holding, and the + // listener MUST NOT have fired. If it has, the manager fanned + // out before awaiting the callback — the exact race this test + // guards against. + expect(events).not.toContain("cb:end"); + expect(events).not.toContain("listener:fire"); + + // Release the gate. Now the callback resolves, refresh completes, + // and the listener fires — in that order. + releaseGate(); + + // Wait for the listener to fire (or bail on timeout). + for (let i = 0; i < 50 && !events.includes("listener:fire"); i++) { + await Bun.sleep(10); + } + + const idxCbEnd = events.indexOf("cb:end"); + const idxFire = events.indexOf("listener:fire"); + expect(idxCbEnd).toBeGreaterThanOrEqual(0); + expect(idxFire).toBeGreaterThanOrEqual(0); + expect(idxFire).toBeGreaterThan(idxCbEnd); + } finally { + // Ensure gate is released even on early failure so no promise leaks. + releaseGate(); + await manager.disconnectAll(); + } + }, 15_000); +}); diff --git a/packages/coding-agent/test/mcp-server-tool-ownership.test.ts b/packages/coding-agent/test/mcp-server-tool-ownership.test.ts index fdd7973c5..bc63ddfdd 100644 --- a/packages/coding-agent/test/mcp-server-tool-ownership.test.ts +++ b/packages/coding-agent/test/mcp-server-tool-ownership.test.ts @@ -61,7 +61,9 @@ describe("MCP tool ownership with prefix-colliding server names", () => { expect(names()).toHaveLength(MANY_TOOL_COUNT * 2); const payloads: string[][] = []; - manager.setOnToolsChanged(tools => payloads.push(tools.map(t => t.name))); + manager.setOnToolsChanged(tools => { + payloads.push(tools.map(t => t.name)); + }); // Same code path a reconnect takes: replace the named server's tools. await manager.refreshServerTools(SHORT_SERVER); @@ -80,7 +82,9 @@ describe("MCP tool ownership with prefix-colliding server names", () => { it("disconnecting a server with sanitized name characters removes exactly its tools", async () => { await manager.connectServers({ [SHORT_SERVER]: fixtureConfig(), [COLON_SERVER]: fixtureConfig() }, {}); const payloads: string[][] = []; - manager.setOnToolsChanged(tools => payloads.push(tools.map(t => t.name))); + manager.setOnToolsChanged(tools => { + payloads.push(tools.map(t => t.name)); + }); await manager.disconnectServer(COLON_SERVER); diff --git a/packages/coding-agent/test/modes/components/codex-reset-fireworks.test.ts b/packages/coding-agent/test/modes/components/codex-reset-fireworks.test.ts new file mode 100644 index 000000000..70c95717c --- /dev/null +++ b/packages/coding-agent/test/modes/components/codex-reset-fireworks.test.ts @@ -0,0 +1,214 @@ +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "bun:test"; +import { stripVTControlCharacters } from "node:util"; +import type { Component, OverlayHandle, OverlayOptions } from "@oh-my-pi/pi-tui"; +import { Settings } from "../../../src/config/settings"; +import { + CodexResetFireworksController, + detectCodexResetFireworks, +} from "../../../src/modes/components/codex-reset-fireworks"; +import { getThemeByName, setThemeInstance, type Theme, theme } from "../../../src/modes/theme/theme"; + +type CodexResetFireworksHost = ConstructorParameters[0]; + +interface FakeHost { + host: CodexResetFireworksHost; + shown: Component[]; + options: OverlayOptions[]; + focused: Component[]; + requestRenderCount(): number; + hiddenCount(): number; +} + +function makeHost(rows = 24): FakeHost { + const shown: Component[] = []; + const options: OverlayOptions[] = []; + const focused: Component[] = []; + let renders = 0; + let hidden = 0; + const host: CodexResetFireworksHost = { + ui: { + showOverlay(component, nextOptions) { + shown.push(component); + options.push(nextOptions ?? {}); + return { + hide() { + hidden++; + }, + setHidden() {}, + isHidden: () => false, + } satisfies OverlayHandle; + }, + setFocus(component) { + focused.push(component); + }, + requestRender() { + renders++; + }, + terminal: { rows }, + }, + }; + return { + host, + shown, + options, + focused, + requestRenderCount: () => renders, + hiddenCount: () => hidden, + }; +} + +async function flushMicrotasks(): Promise { + await Promise.resolve(); + await Promise.resolve(); +} + +describe("Codex reset fireworks", () => { + let priorTheme: Theme | undefined; + beforeAll(async () => { + priorTheme = theme; + await Settings.init({ inMemory: true }); + const loaded = await getThemeByName("dark"); + if (!loaded) throw new Error("theme unavailable"); + setThemeInstance(loaded); + }); + + afterAll(() => { + if (priorTheme) setThemeInstance(priorTheme); + }); + + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it("detects an unscheduled weekly reset and prioritizes newly banked resets", () => { + const previous = { + observedAt: 1_000, + sevenDay: { percent: 42, resetsAt: 10_000 }, + savedResets: 0, + }; + expect( + detectCodexResetFireworks(previous, { + observedAt: 2_000, + sevenDay: { percent: 2, resetsAt: 10_000 }, + savedResets: 0, + }), + ).toEqual({ kind: "unscheduled-weekly-reset" }); + expect( + detectCodexResetFireworks(previous, { + observedAt: 2_000, + sevenDay: { percent: 0, resetsAt: 10_000 }, + savedResets: 2, + }), + ).toEqual({ kind: "saved-reset-banked", added: 2, available: 2 }); + }); + it("suppresses an early weekly drop when a saved reset was redeemed", () => { + expect( + detectCodexResetFireworks( + { + observedAt: 1_000, + sevenDay: { percent: 42, resetsAt: 10_000 }, + savedResets: 1, + }, + { + observedAt: 2_000, + sevenDay: { percent: 0, resetsAt: 10_000 }, + savedResets: 0, + }, + ), + ).toBeUndefined(); + }); + + it("suppresses a weekly transition observed at its scheduled reset deadline", () => { + expect( + detectCodexResetFireworks( + { + observedAt: 1_000, + sevenDay: { percent: 42, resetsAt: 2_000 }, + }, + { + observedAt: 2_000, + sevenDay: { percent: 0, resetsAt: 10_000 }, + }, + ), + ).toBeUndefined(); + }); + + it("requires the prior weekly reset deadline to establish that a reset was unscheduled", () => { + expect( + detectCodexResetFireworks( + { + observedAt: 1_000, + sevenDay: { percent: 42 }, + }, + { + observedAt: 2_000, + sevenDay: { percent: 0 }, + }, + ), + ).toBeUndefined(); + }); + + it("requires the provider fetch timestamp to establish that a reset was early", () => { + expect( + detectCodexResetFireworks( + { + observedAt: 1_000, + sevenDay: { percent: 42, resetsAt: 10_000 }, + }, + { + sevenDay: { percent: 0, resetsAt: 10_000 }, + }, + ), + ).toBeUndefined(); + }); + + it("renders distinct copy for usage-window and saved-reset celebrations", () => { + const usage = makeHost(); + const usageController = new CodexResetFireworksController(usage.host); + usageController.show({ kind: "unscheduled-weekly-reset" }); + const usageText = usage.shown[0]?.render(80).map(stripVTControlCharacters).join("\n") ?? ""; + + const saved = makeHost(); + const savedController = new CodexResetFireworksController(saved.host); + savedController.show({ kind: "saved-reset-banked", added: 1, available: 3 }); + const savedText = saved.shown[0]?.render(80).map(stripVTControlCharacters).join("\n") ?? ""; + + expect(usageText).toContain("O P E N A I R E S E T"); + expect(usageText).toContain("Weekly usage cleared early"); + expect(savedText).toContain("S A V E D R E S E T"); + expect(savedText).toContain("New reset banked · 3 available"); + expect(savedText).not.toContain("Weekly usage cleared early"); + + usageController.dispose(); + savedController.dispose(); + }); + + it("holds a top-third modal until Escape and ignores overlapping celebrations", async () => { + const fake = makeHost(24); + const controller = new CodexResetFireworksController(fake.host); + expect(controller.show({ kind: "unscheduled-weekly-reset" })).toBe(true); + expect(controller.show({ kind: "saved-reset-banked", added: 1, available: 1 })).toBe(false); + expect(fake.shown).toHaveLength(1); + expect(fake.focused).toEqual(fake.shown); + expect(fake.options[0]).toMatchObject({ anchor: "top-center", width: "100%", maxHeight: "33%", margin: 0 }); + expect(fake.shown[0]?.render(80)).toHaveLength(7); + + vi.advanceTimersByTime(5_000); + expect(fake.requestRenderCount()).toBeGreaterThan(FRAME_COUNT_FOR_THREE_SECONDS); + expect(fake.hiddenCount()).toBe(0); + + const component = fake.shown[0]; + component?.handleInput?.("x"); + expect(fake.hiddenCount()).toBe(0); + component?.handleInput?.("\x1b"); + await flushMicrotasks(); + expect(fake.hiddenCount()).toBe(1); + controller.dispose(); + }); +}); + +const FRAME_COUNT_FOR_THREE_SECONDS = Math.floor(3_000 / 85); diff --git a/packages/coding-agent/test/rpc-client.restart.test.ts b/packages/coding-agent/test/rpc-client.restart.test.ts index 540a6998d..195c54389 100644 --- a/packages/coding-agent/test/rpc-client.restart.test.ts +++ b/packages/coding-agent/test/rpc-client.restart.test.ts @@ -30,6 +30,30 @@ describe("RpcClient lifecycle (issue #4079 B)", () => { ]); }, 20_000); + test("normalizes state fields omitted by a legacy RPC server", async () => { + using client = new RpcClient({ + cliPath: MOCK_AGENT, + env: { MOCK_RPC_LEGACY_STATE: "1" }, + }); + + await client.start(); + const state = await client.getState(); + expect(state.fastModeEnabled).toBe(false); + expect(state.fastModeActive).toBe(false); + expect(state.tokensPerSecond).toBeNull(); + }, 20_000); + + test("normalizes a runtime-invalid tokensPerSecond from the RPC server", async () => { + using client = new RpcClient({ + cliPath: MOCK_AGENT, + env: { MOCK_RPC_INVALID_TPS: "1" }, + }); + + await client.start(); + const state = await client.getState(); + expect(state.tokensPerSecond).toBeNull(); + }, 20_000); + test("preserves getMessages snapshot behavior while a v2 page walk is unavailable", async () => { using client = new RpcClient({ cliPath: MOCK_AGENT, diff --git a/packages/coding-agent/test/rpc-input-frame.test.ts b/packages/coding-agent/test/rpc-input-frame.test.ts index 0464c32af..f7876f906 100644 --- a/packages/coding-agent/test/rpc-input-frame.test.ts +++ b/packages/coding-agent/test/rpc-input-frame.test.ts @@ -324,6 +324,9 @@ describe("RpcInputDispatcher", () => { interruptMode: "immediate", sessionId: "session-1", autoCompactionEnabled: false, + fastModeEnabled: false, + fastModeActive: false, + tokensPerSecond: null, messageCount: 0, queuedMessageCount: 0, todoPhases: [], diff --git a/packages/coding-agent/test/rpc.test.ts b/packages/coding-agent/test/rpc.test.ts index d20da322a..41e764d05 100644 --- a/packages/coding-agent/test/rpc.test.ts +++ b/packages/coding-agent/test/rpc.test.ts @@ -59,6 +59,25 @@ describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("RPC mode", () => { expect(state.model?.id).toBe("claude-sonnet-4-5"); expect(state.isStreaming).toBe(false); expect(state.messageCount).toBe(0); + expect(state.tokensPerSecond).toBeNull(); + }, 30000); + + test("should set fast mode and expose enabled/active state", async () => { + await client.start(); + + const enabled = await client.setFastMode(true); + expect(enabled).toEqual({ enabled: true, active: true }); + + const stateWhileEnabled = await client.getState(); + expect(stateWhileEnabled.fastModeEnabled).toBe(true); + expect(stateWhileEnabled.fastModeActive).toBe(true); + + const disabled = await client.setFastMode(false); + expect(disabled).toEqual({ enabled: false, active: false }); + + const stateWhileDisabled = await client.getState(); + expect(stateWhileDisabled.fastModeEnabled).toBe(false); + expect(stateWhileDisabled.fastModeActive).toBe(false); }, 30000); test("should save messages to session file", async () => { @@ -296,3 +315,45 @@ describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("RPC mode", () => { expect(text).toContain("test123"); }, 90000); }); + +describe("RPC fast mode with unsupported Fireworks model and priority tier", () => { + let client: RpcClient; + let sessionDir: string; + + beforeEach(async () => { + sessionDir = path.join(os.tmpdir(), `omp-rpc-fast-mode-test-${Snowflake.next()}`); + await Bun.write(path.join(sessionDir, "config.yml"), ["providers:", " fireworksTier: priority", ""].join("\n")); + client = new RpcClient({ + cliPath: path.join(import.meta.dir, "..", "src", "cli.ts"), + cwd: path.join(import.meta.dir, ".."), + env: { + PI_CODING_AGENT_DIR: sessionDir, + FIREWORKS_API_KEY: "test-fireworks-key", + }, + provider: "fireworks", + model: "deepseek-v4-flash", + }); + }); + + afterEach(async () => { + await client.stop(); + if (sessionDir && fs.existsSync(sessionDir)) { + removeSyncWithRetries(sessionDir); + } + }); + + test("rejects enable but disable preserves Fireworks priority activity", async () => { + await client.start(); + + await expect(client.setFastMode(true)).rejects.toMatchObject({ + message: "Fast mode is unavailable for the current model.", + }); + + const disabled = await client.setFastMode(false); + expect(disabled).toEqual({ enabled: false, active: true }); + + const state = await client.getState(); + expect(state.fastModeEnabled).toBe(false); + expect(state.fastModeActive).toBe(true); + }, 30000); +}); diff --git a/packages/coding-agent/test/sdk-async-job-manager-singleton.test.ts b/packages/coding-agent/test/sdk-async-job-manager-singleton.test.ts index ca66ba796..9bc0c2517 100644 --- a/packages/coding-agent/test/sdk-async-job-manager-singleton.test.ts +++ b/packages/coding-agent/test/sdk-async-job-manager-singleton.test.ts @@ -5,9 +5,11 @@ import * as path from "node:path"; import { AsyncJobManager } from "@oh-my-pi/pi-coding-agent/async/job-manager"; import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; -import { createAgentSession } from "@oh-my-pi/pi-coding-agent/sdk"; +import { createAgentSession, type ExtensionFactory } from "@oh-my-pi/pi-coding-agent/sdk"; +import type { AsyncJobSnapshot } from "@oh-my-pi/pi-coding-agent/session/agent-session"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; +import { type } from "arktype"; describe("AsyncJobManager singleton across concurrent top-level sessions", () => { const tempDirs: string[] = []; @@ -38,7 +40,7 @@ describe("AsyncJobManager singleton across concurrent top-level sessions", () => AsyncJobManager.resetForTests(); }); - async function spawnTopLevelSession(extraSettings?: Record) { + async function spawnTopLevelSession(extraSettings?: Record, extensions: ExtensionFactory[] = []) { const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), `pi-sdk-async-singleton-${Snowflake.next()}-`)); tempDirs.push(tempDir); const cwd = path.join(tempDir, `project-${Snowflake.next()}`); @@ -49,6 +51,7 @@ describe("AsyncJobManager singleton across concurrent top-level sessions", () => agentDir, settings: Settings.isolated({ "bash.autoBackground.enabled": true, ...(extraSettings ?? {}) }), disableExtensionDiscovery: true, + extensions, skills: [], contextFiles: [], promptTemplates: [], @@ -130,6 +133,42 @@ describe("AsyncJobManager singleton across concurrent top-level sessions", () => } }, 60000); + it("exposes the owning session's jobs through a production extension context", async () => { + let observedSnapshot: AsyncJobSnapshot | null | undefined; + const snapshotExtension: ExtensionFactory = pi => { + pi.registerTool({ + name: "capture_async_job_snapshot", + label: "Capture async job snapshot", + description: "Capture the session-owned async job snapshot for this test.", + parameters: type({}), + approval: "read", + async execute(_toolCallId, _params, _signal, _onUpdate, ctx) { + observedSnapshot = ctx.getAsyncJobSnapshot(); + return { content: [{ type: "text", text: "captured" }] }; + }, + }); + }; + const session = await spawnTopLevelSession(undefined, [snapshotExtension]); + const manager = AsyncJobManager.instance(); + expect(manager).toBeDefined(); + const release = Promise.withResolvers(); + const jobId = manager!.register("bash", "extension snapshot test", async () => release.promise, { + ownerId: "Main", + }); + + try { + const snapshotTool = session.getToolByName("capture_async_job_snapshot"); + expect(snapshotTool).toBeDefined(); + await snapshotTool!.execute("call-snapshot", {}); + + expect(observedSnapshot?.running.some(job => job.id === jobId)).toBe(true); + } finally { + release.resolve("done"); + await manager!.waitForAll(); + await session.dispose(); + } + }, 60000); + it("refuses async bash from a secondary session instead of routing it to the primary's manager", async () => { const primary = await spawnTopLevelSession({ "async.enabled": true }); try { diff --git a/packages/coding-agent/test/sdk-autolearn-active-tools.test.ts b/packages/coding-agent/test/sdk-autolearn-active-tools.test.ts index d77bd9ff2..6dd520107 100644 --- a/packages/coding-agent/test/sdk-autolearn-active-tools.test.ts +++ b/packages/coding-agent/test/sdk-autolearn-active-tools.test.ts @@ -86,4 +86,59 @@ describe("createAgentSession auto-learn tool activation", () => { expect(names).toContain("read"); expect(names).not.toContain("manage_skill"); }); + + it("activates checkpoint and rewind when only checkpoint is in an explicit toolNames list", async () => { + const { session } = await createAgentSession({ + cwd: registryDir, + agentDir: registryDir, + modelRegistry, + sessionManager: SessionManager.inMemory(), + settings: Settings.isolated({ "checkpoint.enabled": true }), + model: getBundledModel("openai", "gpt-4o-mini"), + disableExtensionDiscovery: true, + toolNames: ["checkpoint"], + requireYieldTool: true, + }); + sessions.push(session); + const names = session.getActiveToolNames(); + expect(names).toContain("checkpoint"); + expect(names).toContain("rewind"); + }); + + it("activates checkpoint and rewind when only rewind is in an explicit toolNames list", async () => { + const { session } = await createAgentSession({ + cwd: registryDir, + agentDir: registryDir, + modelRegistry, + sessionManager: SessionManager.inMemory(), + settings: Settings.isolated({ "checkpoint.enabled": true }), + model: getBundledModel("openai", "gpt-4o-mini"), + disableExtensionDiscovery: true, + toolNames: ["rewind"], + requireYieldTool: true, + }); + sessions.push(session); + const names = session.getActiveToolNames(); + expect(names).toContain("checkpoint"); + expect(names).toContain("rewind"); + }); + + it("activates checkpoint and rewind in a restricted session with one-sided toolNames", async () => { + const { session } = await createAgentSession({ + cwd: registryDir, + agentDir: registryDir, + modelRegistry, + sessionManager: SessionManager.inMemory(), + settings: Settings.isolated({ "checkpoint.enabled": true }), + model: getBundledModel("openai", "gpt-4o-mini"), + disableExtensionDiscovery: true, + toolNames: ["checkpoint"], + requireYieldTool: true, + restrictToolNames: true, + }); + sessions.push(session); + const names = session.getActiveToolNames(); + expect(names).toContain("checkpoint"); + expect(names).toContain("rewind"); + }); }); diff --git a/packages/coding-agent/test/sdk-tool-activation.test.ts b/packages/coding-agent/test/sdk-tool-activation.test.ts index 8b40eadf7..bc3917846 100644 --- a/packages/coding-agent/test/sdk-tool-activation.test.ts +++ b/packages/coding-agent/test/sdk-tool-activation.test.ts @@ -2,9 +2,13 @@ import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "bun:te import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; +import type { StreamFn } from "@oh-my-pi/pi-agent-core"; +import type { Model, ToolResultMessage } from "@oh-my-pi/pi-ai"; +import { createMockModel } from "@oh-my-pi/pi-ai/providers/mock"; import { getBundledModel } from "@oh-my-pi/pi-catalog/models"; import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; +import type { CursorExecHandlers } from "@oh-my-pi/pi-coding-agent/cursor"; import type { MCPManager } from "@oh-my-pi/pi-coding-agent/mcp/manager"; import { type CreateAgentSessionOptions, @@ -13,6 +17,7 @@ import { discoverAuthStorage, type ExtensionFactory, } from "@oh-my-pi/pi-coding-agent/sdk"; +import type { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; import { VIBE_TOOL_NAMES } from "@oh-my-pi/pi-coding-agent/tools/vibe"; import { logger, removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; @@ -617,4 +622,239 @@ describe("createAgentSession defaultInactive tool activation", () => { await session.dispose(); } }); + + // A session created on another provider keeps its configured-mode `edit` in + // the registry (only a Cursor-created session moves it out) and the tool + // roster is built once, at creation — switching to Cursor later does not + // rebuild it. These two cover both directions of that wiring: the granted + // session must still reach a replace-mode instance for `pi_edit` (whose + // `old_text`/`new_text` args do not validate against the default `hashline` + // schema), and the restricted one must still be refused. + // + // The handlers are internal to the session; `streamFn` is where they are + // handed to the provider, which is the externally observable seam. + const captureCursorExecHandlers = async (session: AgentSession, cursorModel: Model): Promise => { + let handlers: CursorExecHandlers | undefined; + const streamFn: StreamFn = (_model, _context, options) => { + // The session installs the concrete class; the provider option is + // typed as the wire-level interface, whose `piEdit` answers a proto + // result rather than the tool result the class returns. + handlers = options?.cursorExecHandlers as CursorExecHandlers | undefined; + throw new Error("captured"); + }; + vi.spyOn(session.agent, "streamFn").mockImplementation(streamFn); + + await session.setModel(cursorModel); + // Not wrapped in a catch: `prompt` resolves even when the turn fails (the + // loop records the stream error), so a rejection here is a genuine setup + // failure and must surface rather than be mistaken for the capture. + await session.prompt("hi"); + if (!handlers) throw new Error("no exec handlers reached the provider"); + return handlers; + }; + + // `setModel` and `prompt` both refuse a provider with no configured auth. + // Granted on the suite's isolated storage rather than through the provider's + // env var — an env mutation would outlive this file — and removed after, + // since the storage is shared by every test here. + const withProviderAuth = async (providers: string[], run: () => Promise): Promise => { + for (const provider of providers) modelRegistry.authStorage.setRuntimeApiKey(provider, "test-key"); + try { + await run(); + } finally { + for (const provider of providers) modelRegistry.authStorage.removeRuntimeApiKey(provider); + } + }; + + it("answers a native pi_edit after a session switches onto Cursor", async () => { + const tempDir = makeTempDir(); + const cursorModel = getBundledModel("cursor", "composer-1.5"); + if (!cursorModel) throw new Error("expected bundled Cursor model"); + const target = path.join(tempDir, "sample.txt"); + fs.writeFileSync(target, "alpha\nbeta\n"); + + await withProviderAuth(["cursor"], async () => { + const { session } = await createAgentSession(baseOptions(tempDir)); + try { + const handlers = await captureCursorExecHandlers(session, cursorModel); + const result = await handlers.piEdit({ + toolCallId: "sdk-switch-1", + args: { path: target, edits: [{ oldText: "beta", newText: "gamma" }] }, + } as never); + + expect(result.isError).toBeFalsy(); + expect(fs.readFileSync(target, "utf8")).toBe("alpha\ngamma\n"); + } finally { + await session.dispose(); + } + }); + }); + + it("refuses a native pi_edit after a read-only session switches onto Cursor", async () => { + // The bridge instance is constructed, not looked up, so building it for + // a roster that was never granted `edit` would hand a read-only session + // a mutating tool the native frames reach regardless of the advertised + // catalog (issue #5680). Making the construction provider-independent + // must not widen it. + const tempDir = makeTempDir(); + const cursorModel = getBundledModel("cursor", "composer-1.5"); + if (!cursorModel) throw new Error("expected bundled Cursor model"); + const target = path.join(tempDir, "sample.txt"); + fs.writeFileSync(target, "alpha\nbeta\n"); + + await withProviderAuth(["cursor"], async () => { + const { session } = await createAgentSession({ ...baseOptions(tempDir), toolNames: ["read"] }); + try { + const handlers = await captureCursorExecHandlers(session, cursorModel); + const result = await handlers.piEdit({ + toolCallId: "sdk-switch-2", + args: { path: target, edits: [{ oldText: "beta", newText: "gamma" }] }, + } as never); + + expect(result.isError).toBe(true); + expect(fs.readFileSync(target, "utf8")).toBe("alpha\nbeta\n"); + } finally { + await session.dispose(); + } + }); + }); + + it("resolves bridge frame paths through the session's live cwd", async () => { + // The bridge is built once, at session creation, while the session's cwd + // moves under it (`/cd`, resume, branch restore). The path-confining + // frames — the native `delete`, and a `download_path` resource read — + // resolve a relative path against whichever cwd the bridge was handed, so + // a startup snapshot means acting on the workspace the session has left + // while reporting success for the path the server named. + const tempDir = makeTempDir(); + const movedDir = makeTempDir(); + const cursorModel = getBundledModel("cursor", "composer-1.5"); + if (!cursorModel) throw new Error("expected bundled Cursor model"); + const staleTarget = path.join(tempDir, "obsolete.txt"); + const liveTarget = path.join(movedDir, "obsolete.txt"); + fs.writeFileSync(staleTarget, "preserve me"); + fs.writeFileSync(liveTarget, "remove me"); + + await withProviderAuth(["cursor"], async () => { + const sessionManager = SessionManager.inMemory(); + const { session } = await createAgentSession({ ...baseOptions(tempDir), sessionManager }); + try { + const handlers = await captureCursorExecHandlers(session, cursorModel); + await sessionManager.moveTo(movedDir); + + const result = await handlers.delete({ toolCallId: "sdk-cwd-1", path: "obsolete.txt" } as never); + + expect(result.isError).toBe(false); + expect(fs.existsSync(liveTarget)).toBe(false); + expect(fs.existsSync(staleTarget)).toBe(true); + } finally { + await session.dispose(); + } + }); + }); + + it("does not execute an unadvertised edit call through the fallback resolver", async () => { + // One resolver serves two roles: the session's device resolver is passed + // to the bridge as `getTool` AND installed as the agent loop's + // `resolveFallbackTool`, which runs for ANY call the advertised set does + // not contain. It must stay device-only: routing `edit` through it would + // execute a replace-mode edit for a call the model was never offered — + // a hallucinated one, or a tool the session deselected after startup. + // `pi_edit` gets its instance from `getEditReplaceTool` instead. + const tempDir = makeTempDir(); + const target = path.join(tempDir, "sample.txt"); + fs.writeFileSync(target, "alpha\nbeta\n"); + + await withProviderAuth(["openai"], async () => { + // Granted at startup, so an `edit` instance exists to leak, then + // deselected — the exact state that makes the fallback dangerous. + const { session } = await createAgentSession(baseOptions(tempDir)); + try { + await session.setActiveToolsByName(session.getActiveToolNames().filter(name => name !== "edit")); + expect(session.getActiveToolNames()).not.toContain("edit"); + + // A real mock provider, not a hand-rolled stream: the loop builds + // the assistant message from the full event sequence, and an + // incomplete one is dropped before tool dispatch ever runs. + const toolCallId = "unadvertised-edit-1"; + const mock = createMockModel({ + responses: [ + { + content: [ + { + type: "toolCall", + id: toolCallId, + name: "edit", + arguments: { path: target, edits: [{ old_text: "beta", new_text: "gamma" }] }, + }, + ], + }, + { content: [{ type: "text", text: "done" }] }, + ], + }); + vi.spyOn(session.agent, "streamFn").mockImplementation(mock.stream); + + await session.prompt("hi"); + + // The surfaced result, not just the file: an unchanged file alone + // would also pass if the fallback HAD resolved the tool and the + // edit then failed validation or approval. Only "not found" + // proves the resolver refused to hand one over. + const result = session.messages.find( + (message): message is ToolResultMessage => + message.role === "toolResult" && message.toolCallId === toolCallId, + ); + expect(result?.isError).toBe(true); + expect(JSON.stringify(result?.content)).toContain("Tool edit not found"); + expect(fs.readFileSync(target, "utf8")).toBe("alpha\nbeta\n"); + } finally { + await session.dispose(); + } + }); + }); + + it("runs advisor tools through the approval gate", async () => { + // The advisor's tools are built straight from `BUILTIN_TOOLS`, outside + // the registry loop that wraps everything else. Its own loop and its + // Cursor exec bridge (`piWrite`/`piBash`) run those instances directly, + // so an unwrapped one executes whatever it is handed regardless of the + // user's `tools.approval.` policy — the gate lives in + // `ExtensionToolWrapper`, not in either caller. + const tempDir = makeTempDir(); + const target = path.join(tempDir, "advisor-write.txt"); + + // An advisor only builds once a model resolves for it, and both the + // explicit override and the `advisor` role chain resolve against + // `modelRegistry.getAvailable()` — the models this machine holds auth + // for. Grant the suite's isolated storage a key and name the model + // outright, or the roster silently resolves to `no_model` wherever no + // provider is configured (CI) while passing on a developer box whose + // environment happens to carry provider keys. + await withProviderAuth(["openai"], async () => { + const { session } = await createAgentSession({ + ...baseOptions(tempDir), + settings: Settings.isolated({ "advisor.enabled": true, "tools.approval": { write: "deny" } }), + }); + try { + // The default advisor roster is read-only (read/grep/glob); the + // reviewed hole needs one actually granted a mutating tool. + session.applyAdvisorConfigs([{ name: "writer", tools: ["write"], model: "gpt-4o-mini" }], undefined); + const advisor = session.getAdvisorAgent(); + if (!advisor) throw new Error("expected an advisor agent"); + const writeTool = advisor.state.tools?.find(tool => tool.name === "write"); + if (!writeTool) throw new Error("expected the advisor to hold a write tool"); + + // The gate rejects rather than returning an error result — that throw + // IS the refusal, and it only happens when the instance is wrapped. + await expect( + writeTool.execute("advisor-w1", { path: target, content: "written" }, undefined, undefined, { + settings: session.settings, + } as never), + ).rejects.toThrow(/blocked by user policy/); + expect(fs.existsSync(target)).toBe(false); + } finally { + await session.dispose(); + } + }); + }); }); diff --git a/packages/coding-agent/test/settings-stream-fn.test.ts b/packages/coding-agent/test/settings-stream-fn.test.ts index 4abdc0d6d..424889447 100644 --- a/packages/coding-agent/test/settings-stream-fn.test.ts +++ b/packages/coding-agent/test/settings-stream-fn.test.ts @@ -115,6 +115,18 @@ describe("createSettingsAwareStreamFn", () => { expect(calls[1]?.options?.streamIdleTimeoutMs).toBe(10_000); }); + it("forwards retry.maxDelayMs while preserving caller overrides", () => { + const settings = Settings.isolated({ "retry.maxDelayMs": 300_000 }); + const { fn: base, calls } = captureBase(); + const wrapped = createSettingsAwareStreamFn(settings, base); + + wrapped(stubModel, stubContext, undefined); + wrapped(stubModel, stubContext, { maxRetryDelayMs: 5_000 }); + + expect(calls[0]?.options?.maxRetryDelayMs).toBe(300_000); + expect(calls[1]?.options?.maxRetryDelayMs).toBe(5_000); + }); + it("treats the default openrouterVariant as absent so the base call carries no variant", () => { const settings = Settings.isolated({ "providers.openrouterVariant": "default" }); const { fn: base, calls } = captureBase(); diff --git a/packages/coding-agent/test/status-line-usage-refresh.test.ts b/packages/coding-agent/test/status-line-usage-refresh.test.ts index 8c76fd30e..499ca96d4 100644 --- a/packages/coding-agent/test/status-line-usage-refresh.test.ts +++ b/packages/coding-agent/test/status-line-usage-refresh.test.ts @@ -1,6 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test"; import { stripVTControlCharacters } from "node:util"; import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; +import type { CodexResetFireworksEvent } from "@oh-my-pi/pi-coding-agent/modes/components/codex-reset-fireworks"; import { StatusLineComponent } from "@oh-my-pi/pi-coding-agent/modes/components/status-line"; import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; import type { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session"; @@ -57,6 +58,83 @@ function usageReport(percent: number): unknown[] { ]; } +interface CodexUsageState { + sevenDayPercent: number; + sevenDayResetAt: number; + savedResets?: number; + omitFetchedAt?: boolean; + tier?: string; + plan?: string; +} + +function codexUsageReport( + state: CodexUsageState, + accountId = "account-1", + email = "codex@example.com", + orgId?: string, +): unknown[] { + return [ + { + provider: "openai-codex", + ...(state.omitFetchedAt ? {} : { fetchedAt: Date.now() }), + metadata: { + accountId, + email, + ...(orgId ? { orgId } : {}), + ...(state.plan ? { planType: state.plan } : {}), + }, + ...(state.savedResets === undefined ? {} : { resetCredits: { availableCount: state.savedResets } }), + limits: [ + { + id: "openai-codex:secondary", + label: "Codex 7 Day", + scope: { + provider: "openai-codex", + accountId, + windowId: "7d", + ...(state.tier ? { tier: state.tier } : {}), + }, + window: { + id: "7d", + label: "7d", + resetsAt: state.sevenDayResetAt, + }, + amount: { unit: "percent", usedFraction: state.sevenDayPercent / 100 }, + }, + ], + }, + ]; +} + +function makeCodexSession( + fetchUsageReports: (signal?: AbortSignal) => Promise, + resolveActiveIdentity: () => { accountId: string; email?: string; orgId?: string } = () => ({ + accountId: "account-1", + email: "codex@example.com", + }), +): AgentSession { + const session = makeSession(fetchUsageReports) as unknown as Record; + session.sessionId = "session-1"; + session.state = { + messages: [], + model: { contextWindow: 200_000, provider: "openai-codex" }, + }; + session.model = { contextWindow: 200_000, provider: "openai-codex" }; + session.modelRegistry = { + authStorage: { + getOAuthAccountIdentity: resolveActiveIdentity, + }, + }; + return session as unknown as AgentSession; +} + +async function refreshUsage(component: StatusLineComponent, advanceMs = 0): Promise { + if (advanceMs > 0) vi.advanceTimersByTime(advanceMs); + component.refreshUsageInBackground(); + vi.advanceTimersByTime(0); + await flushMicrotasks(); +} + function plain(text: string): string { return stripVTControlCharacters(text); } @@ -200,4 +278,289 @@ describe("StatusLineComponent usage refresh", () => { await flushMicrotasks(); expect(calls).toBe(2); }); + + it("keeps reset fireworks opt-in while advancing the disabled baseline", async () => { + const sevenDayResetAt = Date.now() + 80 * 3_600_000; + let state: CodexUsageState = { + sevenDayPercent: 42, + sevenDayResetAt, + savedResets: 0, + }; + const component = new StatusLineComponent(makeCodexSession(async () => codexUsageReport(state))); + const events: CodexResetFireworksEvent[] = []; + component.setCodexResetFireworksHandler(event => events.push(event)); + + expect(Settings.instance.get("tui.codexResetFireworks")).toBe(false); + await refreshUsage(component); + state = { + sevenDayPercent: 0, + sevenDayResetAt, + savedResets: 0, + }; + await refreshUsage(component, 5 * 60_000); + expect(events).toEqual([]); + component.dispose(); + }); + + it("emits distinct enabled events for an unscheduled weekly reset and a newly banked reset", async () => { + Settings.instance.set("tui.codexResetFireworks", true); + const sevenDayResetAt = Date.now() + 80 * 3_600_000; + let state: CodexUsageState = { + sevenDayPercent: 42, + sevenDayResetAt, + savedResets: 0, + }; + const component = new StatusLineComponent(makeCodexSession(async () => codexUsageReport(state))); + const events: CodexResetFireworksEvent[] = []; + component.setCodexResetFireworksHandler(event => events.push(event)); + + await refreshUsage(component); + expect(events).toEqual([]); + state = { + sevenDayPercent: 2, + sevenDayResetAt, + savedResets: 0, + }; + await refreshUsage(component, 5 * 60_000); + state = { + sevenDayPercent: 25, + sevenDayResetAt, + savedResets: 0, + }; + await refreshUsage(component, 5 * 60_000); + state = { + sevenDayPercent: 25.2, + sevenDayResetAt, + savedResets: 1, + }; + await refreshUsage(component, 5 * 60_000); + + expect(events).toEqual([ + { kind: "unscheduled-weekly-reset" }, + { kind: "saved-reset-banked", added: 1, available: 1 }, + ]); + component.dispose(); + }); + + it("compares weekly reset drops only within the same Codex quota tier", async () => { + Settings.instance.set("tui.codexResetFireworks", true); + const sevenDayResetAt = Date.now() + 80 * 3_600_000; + let state: CodexUsageState = { + sevenDayPercent: 42, + sevenDayResetAt, + savedResets: 0, + tier: "spark", + plan: "pro", + }; + const component = new StatusLineComponent(makeCodexSession(async () => codexUsageReport(state))); + const events: CodexResetFireworksEvent[] = []; + component.setCodexResetFireworksHandler(event => events.push(event)); + + await refreshUsage(component); + state = { ...state, sevenDayPercent: 2, tier: undefined }; + await refreshUsage(component, 5 * 60_000); + expect(events).toEqual([]); + + state = { ...state, sevenDayPercent: 42, tier: "spark" }; + await refreshUsage(component, 5 * 60_000); + state = { ...state, sevenDayPercent: 2 }; + await refreshUsage(component, 5 * 60_000); + expect(events).toEqual([{ kind: "unscheduled-weekly-reset" }]); + component.dispose(); + }); + + it("binds each reset snapshot to the account identity used to normalize it", async () => { + Settings.instance.set("tui.codexResetFireworks", true); + const sevenDayResetAt = Date.now() + 80 * 3_600_000; + const reports = [ + ...codexUsageReport( + { + sevenDayPercent: 18, + sevenDayResetAt, + savedResets: 0, + }, + "account-a", + ), + ...codexUsageReport( + { + sevenDayPercent: 22, + sevenDayResetAt, + savedResets: 1, + }, + "account-b", + ), + ]; + const identityLookups: string[] = []; + const component = new StatusLineComponent( + makeCodexSession( + async () => reports, + () => ({ accountId: identityLookups.shift() ?? "account-a" }), + ), + ); + const events: CodexResetFireworksEvent[] = []; + component.setCodexResetFireworksHandler(event => events.push(event)); + + await refreshUsage(component); + // The refresh starts under A, but B is active when its report is normalized. + // A later identity lookup must not attribute B's saved reset to A. + identityLookups.push("account-a", "account-b", "account-a"); + await refreshUsage(component, 5 * 60_000); + + expect(events).toEqual([]); + component.dispose(); + }); + + it("does not attribute a workspace sibling's saved resets to the active credential", async () => { + Settings.instance.set("tui.codexResetFireworks", true); + const workspaceId = "workspace-1"; + const sevenDayResetAt = Date.now() + 80 * 3_600_000; + let bobSavedResets = 0; + const component = new StatusLineComponent( + makeCodexSession( + async () => [ + ...codexUsageReport( + { sevenDayPercent: 18, sevenDayResetAt, savedResets: 0 }, + workspaceId, + "alice@example.com", + workspaceId, + ), + ...codexUsageReport( + { sevenDayPercent: 22, sevenDayResetAt, savedResets: bobSavedResets }, + workspaceId, + "bob@example.com", + workspaceId, + ), + ], + () => ({ + accountId: workspaceId, + email: "alice@example.com", + orgId: workspaceId, + }), + ), + ); + const events: CodexResetFireworksEvent[] = []; + component.setCodexResetFireworksHandler(event => events.push(event)); + + await refreshUsage(component); + bobSavedResets = 1; + await refreshUsage(component, 5 * 60_000); + + expect(events).toEqual([]); + component.dispose(); + }); + + it("keeps an unavailable saved-reset count unknown across refreshes", async () => { + Settings.instance.set("tui.codexResetFireworks", true); + const sevenDayResetAt = Date.now() + 80 * 3_600_000; + let state: CodexUsageState = { + sevenDayPercent: 18, + sevenDayResetAt, + savedResets: 1, + }; + const component = new StatusLineComponent(makeCodexSession(async () => codexUsageReport(state))); + const events: CodexResetFireworksEvent[] = []; + component.setCodexResetFireworksHandler(event => events.push(event)); + + await refreshUsage(component); + state = { + sevenDayPercent: 18.1, + sevenDayResetAt, + }; + await refreshUsage(component, 5 * 60_000); + state = { ...state, savedResets: 1 }; + await refreshUsage(component, 5 * 60_000); + + expect(events).toEqual([]); + component.dispose(); + }); + + it("suppresses an early weekly drop when a prior saved-reset balance becomes unavailable", async () => { + Settings.instance.set("tui.codexResetFireworks", true); + const sevenDayResetAt = Date.now() + 80 * 3_600_000; + let state: CodexUsageState = { + sevenDayPercent: 42, + sevenDayResetAt, + savedResets: 1, + }; + const component = new StatusLineComponent(makeCodexSession(async () => codexUsageReport(state))); + const events: CodexResetFireworksEvent[] = []; + component.setCodexResetFireworksHandler(event => events.push(event)); + + await refreshUsage(component); + state = { + sevenDayPercent: 0, + sevenDayResetAt, + }; + await refreshUsage(component, 5 * 60_000); + + expect(events).toEqual([]); + component.dispose(); + }); + + it("does not infer an observation time when the provider omits fetchedAt", async () => { + Settings.instance.set("tui.codexResetFireworks", true); + const sevenDayResetAt = Date.now() + 80 * 3_600_000; + let state: CodexUsageState = { + sevenDayPercent: 42, + sevenDayResetAt, + savedResets: 0, + }; + const component = new StatusLineComponent(makeCodexSession(async () => codexUsageReport(state))); + const events: CodexResetFireworksEvent[] = []; + component.setCodexResetFireworksHandler(event => events.push(event)); + + await refreshUsage(component); + state = { + sevenDayPercent: 0, + sevenDayResetAt, + savedResets: 0, + omitFetchedAt: true, + }; + await refreshUsage(component, 5 * 60_000); + + expect(events).toEqual([]); + component.dispose(); + }); + + it("discards a timed-out report after a newer refresh applies", async () => { + Settings.instance.set("tui.codexResetFireworks", true); + const stale = Promise.withResolvers(); + const sevenDayResetAt = Date.now() + 80 * 3_600_000; + const current: CodexUsageState = { + sevenDayPercent: 0, + sevenDayResetAt, + savedResets: 0, + }; + let calls = 0; + const component = new StatusLineComponent( + makeCodexSession(async () => { + calls++; + return calls === 1 ? stale.promise : codexUsageReport(current); + }), + ); + const events: CodexResetFireworksEvent[] = []; + component.setCodexResetFireworksHandler(event => events.push(event)); + + component.refreshUsageInBackground(); + vi.advanceTimersByTime(0); + await flushMicrotasks(); + vi.advanceTimersByTime(2_000); + await flushMicrotasks(); + await refreshUsage(component, 5 * 60_000); + expect(calls).toBe(2); + + stale.resolve( + codexUsageReport({ + sevenDayPercent: 42, + sevenDayResetAt, + savedResets: 1, + }), + ); + await flushMicrotasks(); + await refreshUsage(component, 5 * 60_000); + + expect(calls).toBe(3); + expect(events).toEqual([]); + component.dispose(); + }); }); diff --git a/packages/coding-agent/test/streaming-output-scrollback.test.ts b/packages/coding-agent/test/streaming-output-scrollback.test.ts index 8d60dd3a2..94e580f5d 100644 --- a/packages/coding-agent/test/streaming-output-scrollback.test.ts +++ b/packages/coding-agent/test/streaming-output-scrollback.test.ts @@ -1,9 +1,11 @@ -import { afterEach, beforeAll, describe, expect, test, vi } from "bun:test"; +import { afterAll, afterEach, beforeAll, describe, expect, test, vi } from "bun:test"; import type { AssistantMessage } from "@oh-my-pi/pi-ai"; +import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { AssistantMessageComponent } from "@oh-my-pi/pi-coding-agent/modes/components/assistant-message"; import { ToolExecutionComponent } from "@oh-my-pi/pi-coding-agent/modes/components/tool-execution"; import { TranscriptContainer } from "@oh-my-pi/pi-coding-agent/modes/components/transcript-container"; import { theme as activeTheme, initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; +import type { AgentProgress, TaskToolDetails } from "@oh-my-pi/pi-coding-agent/task/types"; import { evalToolRenderer } from "@oh-my-pi/pi-coding-agent/tools/eval-render"; import { previewWindowRows } from "@oh-my-pi/pi-coding-agent/tools/render-utils"; import { type Component, TUI } from "@oh-my-pi/pi-tui"; @@ -159,10 +161,62 @@ function makeEvalProbeResult(output: string, status: "running" | "complete") { isError: false, }; } + +/** + * Six running designer agents at `tick`, shaped as a detached (async-running) + * task snapshot. Rows carry the time-derived fields the real executor sets — + * a current-tool start (elapsed suffix) and one retry countdown — so the + * regression also covers clock-derived byte drift. + */ +function detachedTaskDetails(tick: number): TaskToolDetails { + const now = Date.now(); + const progress: AgentProgress[] = Array.from({ length: 6 }, (_, index) => ({ + index, + id: `Task${index + 1}`, + agent: "designer", + agentSource: "bundled", + status: "running", + task: `Design concept ${index + 1}`, + assignment: `Design concept ${index + 1}`, + description: `Concept ${index + 1}, update ${tick}`, + currentTool: tick % 2 === 0 ? "read" : "hub", + currentToolStartMs: now - 20_000, + lastIntent: `progress ${tick}`, + recentTools: [], + recentOutput: [], + toolCount: tick, + requests: tick, + tokens: tick * 100, + cost: tick / 100, + durationMs: tick * 100, + retryState: + index === 5 + ? { + attempt: 1, + maxAttempts: 5, + delayMs: 120_000, + errorMessage: "429 rate limited", + startedAtMs: now, + } + : undefined, + })); + return { + projectAgentsDir: null, + results: [], + totalDurationMs: tick * 100, + progress, + async: { state: "running", jobId: "Task1", type: "task" }, + }; +} describe("streaming tool output never sprays duplicate scrollback banners", () => { beforeAll(async () => { + resetSettingsForTest(); + await Settings.init({ inMemory: true, cwd: process.cwd() }); await initTheme(); }); + afterAll(() => { + resetSettingsForTest(); + }); afterEach(() => { if (ORIGINAL_ROWS) Object.defineProperty(process.stdout, "rows", ORIGINAL_ROWS); else Reflect.deleteProperty(process.stdout, "rows"); @@ -206,6 +260,224 @@ describe("streaming tool output never sprays duplicate scrollback banners", () = } }, 30_000); + // The detached-task smear (user report): a background (`async.state === + // "running"`) task block reports finalized so later turns can commit, but + // as the transcript tail its head rows enter native scrollback the moment + // the frame outgrows the viewport. Progress snapshots kept mutating those + // committed rows; with scrollback rebuild off (default) every audit repair + // appended a fresh copy of the agent one-liners below the stale one. + test("task: detached progress never duplicates agent rows in scrollback", async () => { + const rows = 24; + stubStdoutRows(rows); + const term = new VirtualTerminal(100, rows); + const scheduler = makeDrainableScheduler(); + const tui = new TUI(term, undefined, { renderScheduler: scheduler }); + // Default engine mode: divergence repairs append below the stale copy, + // which is exactly the duplication this test guards against. + tui.setScrollbackRebuild(false); + const transcript = new TranscriptContainer(); + const taskArgs = { + context: Array.from({ length: 40 }, (_, index) => `context-line-${index}`).join("\n"), + tasks: Array.from({ length: 6 }, (_, index) => ({ + name: `Task${index + 1}`, + agent: "designer", + task: `Design concept ${index + 1}`, + })), + }; + const task = new ToolExecutionComponent( + "task", + taskArgs, + { liveRegion: transcript }, + undefined, + tui, + process.cwd(), + ); + transcript.addChild(task); + tui.addChild(transcript); + tui.addChild(new Footer(6)); + + try { + tui.start(); + scheduler.flush(); + await term.flush(); + for (let tick = 1; tick <= 12; tick++) { + task.updateResult( + { content: [{ type: "text", text: "running" }], details: detachedTaskDetails(tick) }, + true, + ); + term.scrollLines(1000); + tui.requestRender(); + scheduler.flush(); + await term.flush(); + } + + // The turn's reply streams in below the card (the screenshot shape): + // the whole card — progress rows included — is pushed above the + // window top and committed to the tape as frozen history. + transcript.addChild(new StaticBlock(Array.from({ length: 24 }, (_, i) => `reply-line-${i}`))); + term.scrollLines(1000); + tui.requestRender(); + scheduler.flush(); + await term.flush(); + + // A rebuild without a new snapshot (settings toggle, theme epoch) + // re-derives the frame; time-derived rows (current-tool elapsed, + // retry countdown) must not drift under committed history, so the + // render clock freezes with the latch: the rebuild output must be + // byte-identical even with the wall clock 90s ahead. + const beforeRebuild = task.render(100).join("\n"); + vi.spyOn(Date, "now").mockReturnValue(Date.now() + 90_000); + task.setShowImages(false); + expect(task.render(100).join("\n")).toBe(beforeRebuild); + term.scrollLines(1000); + tui.requestRender(); + scheduler.flush(); + await term.flush(); + + // The terminal snapshot settles an on-screen card in place, but a + // card with committed rows is immutable history: replacing it would + // re-commit the whole slab below the stale copy, so it is dropped + // (the job result surfaces through its own delivery message). + const settledDetails = detachedTaskDetails(13); + settledDetails.async = { state: "completed", jobId: "Task1", type: "task" }; + settledDetails.progress = []; + settledDetails.results = Array.from({ length: 6 }, (_, index) => ({ + index, + id: `Task${index + 1}`, + agent: "designer", + agentSource: "bundled" as const, + task: `Design concept ${index + 1}`, + assignment: `Design concept ${index + 1}`, + description: `Concept ${index + 1}`, + exitCode: 0, + output: `concept ${index + 1} shipped`, + stderr: "", + truncated: false, + durationMs: 1000 + index, + tokens: 1200, + requests: 12, + })); + task.updateResult({ content: [{ type: "text", text: "done" }], details: settledDetails }, false); + // A committed card is immutable: the dropped settlement leaves its + // render byte-identical (the frozen progress rows, not result rows). + expect(task.render(100).join("\n")).toBe(beforeRebuild); + for (let i = 0; i < 2; i++) { + term.scrollLines(1000); + tui.requestRender(); + scheduler.flush(); + await term.flush(); + } + + const buffer = plainScrollBuffer(term); + // The launch brief is append-only content: it belongs in history, + // exactly once. + expect(buffer.filter(line => line.includes("context-line-0"))).toHaveLength(1); + // Collapsed view shows the last 4 agents; each one-liner must appear + // exactly once across history + grid — no stale generations stacked. + for (let index = 3; index <= 6; index++) { + expect(buffer.filter(line => line.includes(`Task${index}`)).length).toBeLessThanOrEqual(1); + } + expect(buffer.filter(line => line.includes("more agents"))).toHaveLength(1); + expect(buffer.filter(line => line.includes("retrying 1/5")).length).toBeLessThanOrEqual(1); + // The dropped settlement never re-rendered the card as results. + expect(buffer.some(line => line.includes("shipped"))).toBe(false); + } finally { + task.stopAnimation(); + tui.stop(); + await term.flush(); + } + }, 30_000); + + // A blocking (foreground) task call is a self-replacing dashboard: its + // progress rows rewrite in place, so none of them may be recorded as + // frozen scrollback snapshots mid-run. Only the finalized result commits. + test("task: blocking progress taller than the viewport records only the final frame", async () => { + const rows = 24; + stubStdoutRows(rows); + const term = new VirtualTerminal(100, rows); + const scheduler = makeDrainableScheduler(); + const tui = new TUI(term, undefined, { renderScheduler: scheduler }); + tui.setScrollbackRebuild(false); + const transcript = new TranscriptContainer(); + const taskArgs = { + context: Array.from({ length: 40 }, (_, index) => `context-line-${index}`).join("\n"), + tasks: Array.from({ length: 6 }, (_, index) => ({ + name: `Task${index + 1}`, + agent: "designer", + task: `Design concept ${index + 1}`, + })), + }; + const task = new ToolExecutionComponent( + "task", + taskArgs, + { liveRegion: transcript }, + undefined, + tui, + process.cwd(), + ); + transcript.addChild(task); + tui.addChild(transcript); + tui.addChild(new Footer(6)); + + try { + tui.start(); + scheduler.flush(); + await term.flush(); + for (let tick = 1; tick <= 12; tick++) { + const details = detachedTaskDetails(tick); + delete details.async; // blocking call: no async job attached + task.updateResult({ content: [{ type: "text", text: "running" }], details }, true); + term.scrollLines(1000); + tui.requestRender(); + scheduler.flush(); + await term.flush(); + } + + const midRun = plainScrollBuffer(term); + for (let index = 1; index <= 6; index++) { + expect(midRun.filter(line => line.includes(`Task${index}`)).length).toBeLessThanOrEqual(1); + } + + // Finalize: results replace the progress rows and commit exactly once. + const finalDetails = detachedTaskDetails(12); + delete finalDetails.async; + finalDetails.progress = []; + finalDetails.results = Array.from({ length: 6 }, (_, index) => ({ + index, + id: `Task${index + 1}`, + agent: "designer", + agentSource: "bundled" as const, + task: `Design concept ${index + 1}`, + assignment: `Design concept ${index + 1}`, + description: `Concept ${index + 1}`, + exitCode: 0, + output: `concept ${index + 1} shipped`, + stderr: "", + truncated: false, + durationMs: 1000 + index, + tokens: 1200, + requests: 12, + })); + task.updateResult({ content: [{ type: "text", text: "done" }], details: finalDetails }, false); + for (let i = 0; i < 2; i++) { + term.scrollLines(1000); + tui.requestRender(); + scheduler.flush(); + await term.flush(); + } + + const settled = plainScrollBuffer(term); + expect(settled.filter(line => line.includes("context-line-0"))).toHaveLength(1); + for (let index = 1; index <= 6; index++) { + expect(settled.filter(line => line.includes(`Task${index}`)).length).toBeLessThanOrEqual(1); + } + } finally { + task.stopAnimation(); + tui.stop(); + await term.flush(); + } + }, 30_000); + test("eval: collapsed cell output stays within the viewport budget", () => { const rows = 18; stubStdoutRows(rows); diff --git a/packages/coding-agent/test/tools/index.test.ts b/packages/coding-agent/test/tools/index.test.ts index 981e68bc9..e7700146f 100644 --- a/packages/coding-agent/test/tools/index.test.ts +++ b/packages/coding-agent/test/tools/index.test.ts @@ -301,6 +301,117 @@ describe("createTools", () => { expect(session.isToolActive?.("read")).toBe(false); }); + it("allows checkpoint/rewind in subagent when explicitly requested and enabled", async () => { + const names = ( + await createTools( + createTestSession({ + taskDepth: 1, + settings: createSettingsWithOverrides({ "checkpoint.enabled": true }), + }), + ["checkpoint", "rewind"], + ) + ).map(t => t.name); + expect(names).toContain("checkpoint"); + expect(names).toContain("rewind"); + }); + + it("excludes checkpoint/rewind from subagent when not explicitly requested", async () => { + const names = ( + await createTools( + createTestSession({ + taskDepth: 1, + settings: createSettingsWithOverrides({ "checkpoint.enabled": true }), + }), + ) + ).map(t => t.name); + expect(names).not.toContain("checkpoint"); + expect(names).not.toContain("rewind"); + }); + + it("excludes checkpoint/rewind from subagent when disabled even if explicitly requested", async () => { + const names = ( + await createTools( + createTestSession({ + taskDepth: 1, + settings: createSettingsWithOverrides({ "checkpoint.enabled": false }), + }), + ["checkpoint", "rewind"], + ) + ).map(t => t.name); + expect(names).not.toContain("checkpoint"); + expect(names).not.toContain("rewind"); + }); + + it("allows checkpoint/rewind at top level when enabled and explicitly requested", async () => { + const names = ( + await createTools( + createTestSession({ + settings: createSettingsWithOverrides({ "checkpoint.enabled": true }), + }), + ["checkpoint", "rewind"], + ) + ).map(t => t.name); + expect(names).toContain("checkpoint"); + expect(names).toContain("rewind"); + }); + + it("auto-includes rewind when only checkpoint is in the explicit list", async () => { + const names = ( + await createTools( + createTestSession({ + taskDepth: 1, + settings: createSettingsWithOverrides({ "checkpoint.enabled": true }), + }), + ["checkpoint"], + ) + ).map(t => t.name); + expect(names).toContain("checkpoint"); + expect(names).toContain("rewind"); + }); + + it("auto-includes checkpoint when only rewind is in the explicit list", async () => { + const names = ( + await createTools( + createTestSession({ + taskDepth: 1, + settings: createSettingsWithOverrides({ "checkpoint.enabled": true }), + }), + ["rewind"], + ) + ).map(t => t.name); + expect(names).toContain("checkpoint"); + expect(names).toContain("rewind"); + }); + + it("does not auto-include checkpoint/rewind when neither is requested", async () => { + const names = ( + await createTools( + createTestSession({ + taskDepth: 1, + settings: createSettingsWithOverrides({ "checkpoint.enabled": true }), + }), + ["read"], + ) + ).map(t => t.name); + expect(names).not.toContain("checkpoint"); + expect(names).not.toContain("rewind"); + }); + + it("auto-pairs checkpoint/rewind in a restricted subagent with one-sided list", async () => { + const names = ( + await createTools( + createTestSession({ + taskDepth: 1, + restrictToolNames: true, + settings: createSettingsWithOverrides({ "checkpoint.enabled": true }), + }), + ["checkpoint"], + ) + ).map(t => t.name); + expect(names).toContain("checkpoint"); + expect(names).toContain("rewind"); + }); + it("HIDDEN_TOOLS contains yield and goal", () => { expect(Object.keys(HIDDEN_TOOLS).sort()).toEqual(["goal", "yield"]); }); diff --git a/packages/coding-agent/test/turn-recovery-replay-unsafe.test.ts b/packages/coding-agent/test/turn-recovery-replay-unsafe.test.ts index 212a90aeb..b9494bab4 100644 --- a/packages/coding-agent/test/turn-recovery-replay-unsafe.test.ts +++ b/packages/coding-agent/test/turn-recovery-replay-unsafe.test.ts @@ -46,6 +46,7 @@ function createHost( return { agent: undefined as never, sessionManager: undefined as never, + persistedAssistantEntryId: () => undefined, settings, modelRegistry, configWarnings: [], diff --git a/packages/coding-agent/test/utils/changelog.test.ts b/packages/coding-agent/test/utils/changelog.test.ts index 3e45195e8..a7d64d5f1 100644 --- a/packages/coding-agent/test/utils/changelog.test.ts +++ b/packages/coding-agent/test/utils/changelog.test.ts @@ -15,13 +15,16 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import { removeWithRetries, VERSION } from "@oh-my-pi/pi-utils"; +import { SETTINGS_SCHEMA, Settings } from "../../src/config/settings"; import { type ChangelogEntry, + formatStartupChangelogSummary, getNewEntries, parseChangelog, RECENT_CHANGELOG_ENTRY_LIMIT, readLastChangelogVersion, renderChangelogEntries, + resolveStartupChangelogForDisplay, STARTUP_CHANGELOG_FULL_HINT, STARTUP_CHANGELOG_MAX_BYTES, selectStartupChangelog, @@ -44,6 +47,39 @@ function release(major: number, minor: number, patch: number, body: string): Cha return { major, minor, patch, content }; } +describe("startup changelog mode settings", () => { + test("defaults to a summary", () => { + expect(Settings.isolated().get("startup.changelogMode")).toBe("summary"); + }); + + test("keeps the legacy key out of the public schema while migrating raw config", async () => { + expect(Object.hasOwn(SETTINGS_SCHEMA, "collapseChangelog")).toBe(false); + + await withTempAgentDir(async agentDir => { + const configPath = path.join(agentDir, "config.yml"); + for (const [legacyValue, expectedMode] of [ + [true, "summary"], + [false, "expanded"], + ] as const) { + await Bun.write(configPath, `collapseChangelog: ${legacyValue}\n`); + const settings = await Settings.loadReadOnly({ cwd: agentDir, agentDir }); + expect(settings.get("startup.changelogMode")).toBe(expectedMode); + } + }); + }); + + test("lets an explicit new mode win over the legacy raw config key", async () => { + await withTempAgentDir(async agentDir => { + await Bun.write( + path.join(agentDir, "config.yml"), + "collapseChangelog: false\nstartup:\n changelogMode: hidden\n", + ); + const settings = await Settings.loadReadOnly({ cwd: agentDir, agentDir }); + expect(settings.get("startup.changelogMode")).toBe("hidden"); + }); + }); +}); + async function withTempAgentDir(callback: (agentDir: string) => Promise): Promise { const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "omp-changelog-marker-")); try { @@ -106,6 +142,10 @@ describe("selectStartupChangelog", () => { expect(selection.persistCurrentVersion).toBe(true); expect(selection.truncated).toBe(false); expect(selection.selectedEntries).toBe(RECENT_CHANGELOG_ENTRY_LIMIT); + expect(selection.totalUnseenEntries).toBe(5); + expect(selection.latestVersion).toBe("1.0.5"); + expect(selection.changeCount).toBe(3); + expect(selection.categoryCounts).toEqual({ Added: 3 }); expect(selection.markdown?.match(/## \[(\d+\.\d+\.\d+)\]/)?.[1]).toBe("1.0.5"); expect(selection.markdown).toContain("## [1.0.5]"); expect(selection.markdown).toContain("## [1.0.4]"); @@ -153,6 +193,41 @@ describe("selectStartupChangelog", () => { }); }); +describe("formatStartupChangelogSummary", () => { + test("summarizes selected releases and points to omitted history", () => { + const selection = selectStartupChangelog( + [ + release(2, 0, 0, "### Added\n\n- First addition.\n- Second addition.\n\n### Fixed\n\n- A fix."), + release(1, 9, 0, "### Changed\n\n- A behavior change."), + release(1, 8, 0, "### Security\n\n- A security improvement."), + release(1, 7, 0, "### Fixed\n\n- An earlier fix."), + release(1, 6, 0, "### Added\n\n- Already seen."), + ], + "1.6.0", + "2.0.0", + ); + + expect(formatStartupChangelogSummary(selection)).toBe( + [ + "Updated to v2.0.0 · 5 changes across 3 releases", + "2 added · 1 changed · 1 fixed · 1 security · +1 earlier release · Use /changelog full for history.", + ].join("\n"), + ); + }); + + test("uses the recent-details hint when every unseen release is represented", () => { + const selection = selectStartupChangelog( + [release(2, 0, 0, "### Breaking Changes\n\n- Removed the old wire format.")], + "1.0.0", + "2.0.0", + ); + + expect(formatStartupChangelogSummary(selection)).toBe( + ["Updated to v2.0.0 · 1 change in 1 release", "1 breaking change · Use /changelog for details."].join("\n"), + ); + }); +}); + describe("parseChangelog", () => { test("reads current source release data and filters versions newer than the previous release", async () => { const entries = await parseChangelog(undefined); @@ -198,6 +273,31 @@ describe("last changelog marker", () => { expect(await Bun.file(path.join(agentDir, "last-changelog-version")).text()).toBe(CURRENT_VERSION); }); }); + + test("hidden mode suppresses display and advances the marker only for upgrades", async () => { + await withTempAgentDir(async agentDir => { + await writeLastChangelogVersion("1.0.0", agentDir); + + const upgradeDisplay = await resolveStartupChangelogForDisplay({ + mode: "hidden", + currentVersion: CURRENT_VERSION, + agentDir, + }); + + expect(upgradeDisplay).toBeUndefined(); + expect(await readLastChangelogVersion(agentDir)).toBe(CURRENT_VERSION); + + await writeLastChangelogVersion("3.0.0", agentDir); + const downgradeDisplay = await resolveStartupChangelogForDisplay({ + mode: "hidden", + currentVersion: CURRENT_VERSION, + agentDir, + }); + + expect(downgradeDisplay).toBeUndefined(); + expect(await readLastChangelogVersion(agentDir)).toBe("3.0.0"); + }); + }); }); describe.skipIf(!hasPtyHarness)("interactive startup changelog PTY smoke", () => { diff --git a/python/omp-rpc/src/omp_rpc/__init__.py b/python/omp-rpc/src/omp_rpc/__init__.py index d1fe16ec3..fd7ef107c 100644 --- a/python/omp-rpc/src/omp_rpc/__init__.py +++ b/python/omp-rpc/src/omp_rpc/__init__.py @@ -43,6 +43,7 @@ from .protocol import ( AutoRetryEndEvent, AutoRetryStartEvent, BashResult, + FastModeResult, BranchMessage, BranchResult, CancellationResult, @@ -109,6 +110,7 @@ __all__ = [ "AutoRetryEndEvent", "AutoRetryStartEvent", "BashResult", + "FastModeResult", "BranchMessage", "BranchResult", "CancellationResult", diff --git a/python/omp-rpc/src/omp_rpc/client.py b/python/omp-rpc/src/omp_rpc/client.py index 538e6c4ff..0f3b8983d 100644 --- a/python/omp-rpc/src/omp_rpc/client.py +++ b/python/omp-rpc/src/omp_rpc/client.py @@ -25,6 +25,7 @@ from .protocol import ( AutoRetryEndEvent, AutoRetryStartEvent, BashResult, + FastModeResult, BranchMessage, BranchResult, CancellationResult, @@ -67,6 +68,7 @@ from .protocol import ( assistant_text, parse_agent_messages, parse_bash_result, + parse_fast_mode_result, parse_branch_messages, parse_branch_result, parse_cancellation_result, @@ -916,6 +918,9 @@ class RpcClient: payload = self._request("get_state") return parse_session_state(payload) + def set_fast_mode(self, enabled: bool) -> FastModeResult: + return parse_fast_mode_result(self._request("set_fast_mode", enabled=enabled)) + def set_model(self, provider: str, model_id: str) -> ModelInfo: payload = self._request("set_model", provider=provider, modelId=model_id) model = parse_model_info(payload) diff --git a/python/omp-rpc/src/omp_rpc/protocol.py b/python/omp-rpc/src/omp_rpc/protocol.py index f903964a7..248ca89b7 100644 --- a/python/omp-rpc/src/omp_rpc/protocol.py +++ b/python/omp-rpc/src/omp_rpc/protocol.py @@ -183,6 +183,13 @@ def _require_str(payload: JsonObject, field: str) -> str: return value +def _require_bool(payload: JsonObject, field: str) -> bool: + value = payload.get(field) + if not isinstance(value, bool): + raise ValueError(f"{field} must be a boolean") + return value + + def _optional_str(payload: JsonObject, field: str) -> str | None: value = payload.get(field) if value is None: @@ -233,6 +240,15 @@ def _optional_int(payload: JsonObject, field: str) -> int | None: return value +def _optional_float(payload: JsonObject, field: str) -> float | None: + value = payload.get(field) + if value is None: + return None + if isinstance(value, bool) or not isinstance(value, (int, float)): + raise ValueError(f"{field} must be a number") + return float(value) + + def _tuple_of_strings(values: object, *, field: str) -> tuple[str, ...] | None: if values is None: return None @@ -776,6 +792,9 @@ class SessionState: todo_phases: tuple[TodoPhase, ...] = () system_prompt: tuple[str, ...] = () dump_tools: tuple[ToolDescriptor, ...] = () + fast_mode_enabled: bool = False + fast_mode_active: bool = False + tokens_per_second: float | None = None @dataclass(slots=True, frozen=True) @@ -791,6 +810,12 @@ class BashResult: artifact_id: str | None = None +@dataclass(slots=True, frozen=True) +class FastModeResult: + enabled: bool + active: bool + + @dataclass(slots=True, frozen=True) class CompactionResult: summary: str @@ -1344,6 +1369,9 @@ def parse_session_state(payload: JsonObject) -> SessionState: ), system_prompt=_optional_str_list(payload, "systemPrompt"), dump_tools=dump_tools, + fast_mode_enabled=bool(payload.get("fastModeEnabled", False)), + fast_mode_active=bool(payload.get("fastModeActive", False)), + tokens_per_second=_optional_float(payload, "tokensPerSecond"), ) @@ -1361,6 +1389,13 @@ def parse_bash_result(payload: JsonObject) -> BashResult: ) +def parse_fast_mode_result(payload: JsonObject) -> FastModeResult: + return FastModeResult( + enabled=_require_bool(payload, "enabled"), + active=_require_bool(payload, "active"), + ) + + def parse_compaction_result(payload: JsonObject) -> CompactionResult: return CompactionResult( summary=str(payload.get("summary", "")), diff --git a/python/omp-rpc/tests/test_client.py b/python/omp-rpc/tests/test_client.py index c9129ac8f..4355ee4b8 100644 --- a/python/omp-rpc/tests/test_client.py +++ b/python/omp-rpc/tests/test_client.py @@ -84,6 +84,9 @@ FAKE_SERVER = textwrap.dedent( "interruptMode": interrupt_mode, "sessionId": "fake-session", "sessionName": session_name, + "fastModeEnabled": False, + "fastModeActive": True, + "tokensPerSecond": 7.25, "autoCompactionEnabled": auto_compaction_enabled, "messageCount": len(messages), "queuedMessageCount": 0, @@ -319,6 +322,21 @@ FAKE_SERVER = textwrap.dedent( "compact", {"summary": "trimmed", "shortSummary": "trimmed", "firstKeptEntryId": "entry-1", "tokensBefore": 123}, ) + elif command_type == "set_fast_mode": + enabled = command.get("enabled") + if not isinstance(enabled, bool): + respond( + request_id, + "set_fast_mode", + success=False, + error="set_fast_mode requires boolean enabled", + ) + else: + respond( + request_id, + "set_fast_mode", + {"enabled": False, "active": True}, + ) elif command_type == "set_auto_compaction": auto_compaction_enabled = command["enabled"] respond(request_id, "set_auto_compaction", {}) @@ -877,11 +895,21 @@ class RpcClientTests(unittest.TestCase): self.assertEqual( state.model.id if state.model else None, "claude-sonnet-4-5" ) + self.assertFalse(state.fast_mode_enabled) + self.assertTrue(state.fast_mode_active) + self.assertEqual(state.tokens_per_second, 7.25) result = client.bash("echo hello") self.assertEqual(result.output, "hello\n") self.assertEqual(result.exit_code, 0) + def test_set_fast_mode_preserves_provider_tier_state(self) -> None: + with self.make_client() as client: + result = client.set_fast_mode(False) + + self.assertFalse(result.enabled) + self.assertTrue(result.active) + def test_prompt_and_wait_returns_assistant_text(self) -> None: with self.make_client() as client: turn = client.prompt_and_wait("say hello", timeout=2.0) diff --git a/python/omp-rpc/tests/test_protocol.py b/python/omp-rpc/tests/test_protocol.py index c9f7b2697..b19fa967a 100644 --- a/python/omp-rpc/tests/test_protocol.py +++ b/python/omp-rpc/tests/test_protocol.py @@ -51,6 +51,9 @@ class ProtocolParsingTests(unittest.TestCase): "sessionFile": "/tmp/test.jsonl", "sessionId": "session-123", "sessionName": "Scratchpad", + "fastModeEnabled": False, + "fastModeActive": True, + "tokensPerSecond": 12.5, "autoCompactionEnabled": True, "messageCount": 4, "queuedMessageCount": 1, @@ -95,6 +98,38 @@ class ProtocolParsingTests(unittest.TestCase): self.assertEqual(state.model.thinking.default_level, "medium") self.assertEqual(state.model.thinking.effort_map, {"high": "xhigh"}) self.assertTrue(state.model.thinking.supports_display) + self.assertFalse(state.fast_mode_enabled) + self.assertTrue(state.fast_mode_active) + self.assertEqual(state.tokens_per_second, 12.5) + + def test_parse_session_state_defaults_missing_fast_mode_and_throughput( + self, + ) -> None: + missing = object() + for tokens_per_second, expected in ( + (None, None), + (missing, None), + ): + with self.subTest(tokens_per_second=tokens_per_second): + payload = { + "sessionId": "session-123", + "steeringMode": "one-at-a-time", + "followUpMode": "all", + "interruptMode": "immediate", + } + if tokens_per_second is not missing: + payload["tokensPerSecond"] = tokens_per_second + + state = parse_session_state(payload) + + self.assertEqual( + ( + state.fast_mode_enabled, + state.fast_mode_active, + state.tokens_per_second, + ), + (False, False, expected), + ) def test_parse_agent_end_notification(self) -> None: notification = parse_notification(