@otto-code/client 0.7.6 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { normalizeAgentModelDefinition } from "@otto-code/protocol/agent-types";
2
- // COMPAT(model-normalize): daemon normalizes at source (provider-registry) shim covers older daemons; drop when floor >= v0.1.104
2
+ // COMPAT(model-normalize): daemon normalizes at source (provider-registry) - shim covers older daemons; drop when floor >= v0.1.104
3
3
  function normalizeAgentModels(models) {
4
4
  if (!models) {
5
5
  return models;
@@ -6,7 +6,7 @@ import { AgentRefreshedStatusPayloadSchema, CheckoutRenameBranchResponseSchema,
6
6
  import type { AgentStreamEventPayload, AgentSnapshotPayload, ProjectPlacementPayload, AgentPermissionResolvedMessage, CreateAgentRequestMessage, CreateOttoWorktreeRequest, CodeListFilesResponse, CodeSymbolLocation, CodeDefinitionLocation, CodeDefinitionStatus, CodeRenameApplyStatus, CodeRenameFileOutcome, CodeRenameUndoFile, CodeRenameUndoStatus, CodeHoverRange, CodeRenameEdit, CodeRenameFilePlan, LspLanguageState, LspRunningServer, CodeSolutionGetTreeResponse, CodeSolutionLoadProjectResponse, SolutionFormat, SolutionRef, SolutionTreeFolder, SolutionTreeProject, SolutionProjectNode, SolutionProjectStatus, SolutionPackageReference, FileCreateResult, FileDeleteResult, FileDownloadTokenResponse, FileEntryKind, FileEol, FileRenameResult, FileReplaceFileResult, FileReplaceRequest, FileReplaceResponse, FileSearchResultPayload, FileSearchSummary, FileUploadResponse, FileExplorerResponse, FileWatchEventPayload, FileWriteResult, FileRefineResult, FileRefineDocument, FileRefineReference, FetchAgentTimelineResponseMessage, AgentForkContextResponseMessage, GitSetupOptions, CheckoutStatusResponse, CheckoutCommitResponse, CheckoutGitCommitResponse, CheckoutGitCommitAgentResponse, CheckoutGitRollbackResponse, CheckoutGitFileHistoryResponse, CheckoutGitFileCommitDiffResponse, CheckoutGitFileBlameResponse, CheckoutGitFileOriginResponse, CheckoutGitGetOperationLogResponse, CheckoutMergeResponse, CheckoutMergeFromBaseResponse, CheckoutPullResponse, CheckoutPushResponse, CheckoutRefreshResponse, CheckoutPrCreateResponse, CheckoutPrMergeResponse, CheckoutPrMergeMethod, CheckoutGithubSetAutoMergeResponse, CheckoutGithubGetCheckDetailsResponse, PreviewListConfigResponse, PreviewStartResponse, PreviewBindTabResponse, PreviewStopResponse, CheckoutPrStatusResponse, PullRequestTimelineResponse, CheckoutSwitchBranchResponse, StashSaveResponse, StashPopResponse, StashListResponse, ValidateBranchResponse, BranchSuggestionsResponse, FileVersion, CheckoutCommit, ParsedDiffFile, WorkspaceRecoveryState, CheckoutForgeGetCheckDetailsResponse, CheckoutForgeSetAutoMergeResponse, ProjectCreateDirectoryResponse, FsFileWriteResult, FsFileWriteBinaryResult, GitHubSearchResponse, GitHubSearchRequest, ForgeSearchResponse, ForgeSearchRequest, GitHostingProviderId, HostingSearchRequest, HostingSearchResponse, HostingAuthStatusResponse, DirectorySuggestionsResponse, OttoWorktreeListResponse, OttoWorktreeArchiveResponse, ProjectIconResponse, ContextCategory, ContextPromptPreviewGetResponseMessage, ContextReportGetResponseMessage, ContextEdgeConvertResponseMessage, ContextFindingsFixResponseMessage, PersonalityMemoryListResponseMessage, PersonalityMemoryUpdateResponseMessage, PersonalityMemoryTransferResponseMessage, PersonalityMemoryStatsResponseMessage, ProjectAddResponse, ProjectScaffoldGit, ProjectScaffoldProgress, ProjectScaffoldResponse, HostingListRepositoriesResponse, HostingListOwnersResponse, OpenProjectResponseMessage, ArchiveWorkspaceResponseMessage, WorkspaceArchivePreflightResponse, WorktreeBaseRefSetResponse, WorktreeReattachListResponse, WorktreeReattachResponse, WorktreeReattachTarget, WorkspaceSetupStatusResponseMessage, ListCommandsResponse, ListProviderFeaturesResponseMessage, ListProviderModelsResponseMessage, ListProviderModesResponseMessage, ListAvailableProvidersResponse, GetProvidersSnapshotResponseMessage, RefreshProvidersSnapshotResponseMessage, ProviderDiagnosticResponseMessage, ProviderUsageListResponseMessage, StatsActivityGetResponseMessage, StatsActivityResetResponseMessage, UsageLogGetResponseMessage, AgentContextGetUsageResponseMessage, DaemonGetStatusResponse, DaemonGetPairingOfferResponse, DiagnosticsResponse, AgentRewindResponseMessage, ListTerminalsResponse, CreateTerminalResponse, SubscribeTerminalResponse, SubscribeTerminalRequest, CloseItemsResponse, AttachmentsImagesClearResponse, AttachmentsImagesStatsResponse, HistoryAgentsClearArchivedResponse, KillTerminalResponse, CaptureTerminalResponse, TerminalInput, SessionInboundMessage, SessionOutboundMessage, SendAgentMessageRequest, AgentPromptDelivery, TasksSuggestedStartMode, OttoConfigRaw, OttoConfigRevision, WorkspaceCreateRequest } from "@otto-code/protocol/messages";
7
7
  import type { AgentPermissionRequest, AgentPermissionResponse, AgentPersistenceHandle, AgentProviderNotice, AgentProvider, AgentSessionConfig } from "@otto-code/protocol/agent-types";
8
8
  import type { OrchestrationGraph, PromptTemplate, Run } from "@otto-code/protocol/orchestration";
9
- import type { BrainCatalogModel, BrainEvals, BrainHfSearchResult, BrainHostStatus, BrainInstalledModel, BrainJob, BrainNetworkInfo, BrainRemoteConfig, BrainRepoQuant, BrainRuntime, ConnectorsListToolsResponse, CueMoment, MutableDaemonConfig, MutableDaemonConfigPatch, ProjectLink, SpeechSettingsOptions, SpeechTtsPreviewResult, SpeechTtsSpeakResult, SpeechTtsSpeakCancelResult, VisualizerVoiceCuesResult, AgentPersonalitiesGenerateProfileResult } from "@otto-code/protocol/messages";
9
+ import type { BrainCatalogModel, BrainDiskUsage, BrainEvals, BrainHfSearchResult, BrainHostStatus, BrainInstalledModel, BrainInventoryModel, BrainJob, BrainLogsTailResponse, BrainModelBudgetGetResponse, BrainModelDeleteResponse, BrainModelLoadResponse, BrainModelProfileGetResponse, BrainModelProfileSetResponse, BrainNetworkInfo, BrainRemoteConfig, BrainRepoQuant, BrainRuntime, ConnectorsListToolsResponse, ConnectorsOauthAuthorizeResponse, ConnectorsOauthDisconnectResponse, CueMoment, MutableDaemonConfig, MutableDaemonConfigPatch, ProjectLink, SpeechSettingsOptions, SpeechTtsPreviewResult, SpeechTtsSpeakResult, SpeechTtsSpeakCancelResult, VisualizerVoiceCuesResult, AgentPersonalitiesGenerateProfileResult } from "@otto-code/protocol/messages";
10
10
  import { type DaemonTransportFactory, type WebSocketFactory } from "./daemon-client-transport.js";
11
11
  import { type DaemonClientTrafficHotspot, type DaemonClientTrafficTotals } from "./daemon-client-runtime-metrics.js";
12
12
  import { type TerminalStreamEvent } from "./terminal-stream-router.js";
@@ -255,7 +255,7 @@ export interface FileReadResult {
255
255
  modifiedAt: string;
256
256
  /**
257
257
  * Detected line endings. Optional because only the inline JSON read path
258
- * reports them the chunked binary transfer never does, and neither do
258
+ * reports them - the chunked binary transfer never does, and neither do
259
259
  * daemons older than v0.4.4. Absent means "not reported", not "LF".
260
260
  */
261
261
  eol?: FileEol;
@@ -284,7 +284,7 @@ export interface FileWriteOptions {
284
284
  /**
285
285
  * Bytes to a workspace path. Gated on `features.binaryFileWrite`. Unlike
286
286
  * {@link FileWriteOptions} this carries no precondition: `overwrite` is the
287
- * whole policy see `FsFileWriteBinaryRequestSchema` for why a generated
287
+ * whole policy - see `FsFileWriteBinaryRequestSchema` for why a generated
288
288
  * artifact has nothing to reconcile against.
289
289
  */
290
290
  export interface FileWriteBinaryOptions {
@@ -299,7 +299,7 @@ export interface FileWriteBinaryOptions {
299
299
  chunkSize?: number;
300
300
  }
301
301
  /**
302
- * The general file-mutation surface what exists in a directory, rather than
302
+ * The general file-mutation surface - what exists in a directory, rather than
303
303
  * what is inside a file. Gated on `features.fileMutations`; there is no
304
304
  * client-side substitute, so callers check the flag before offering the action.
305
305
  */
@@ -325,14 +325,14 @@ export interface FileRenameOptions {
325
325
  requestId?: string;
326
326
  }
327
327
  /**
328
- * Refine ask the daemon for proposed rewrites of a pinned set of documents.
328
+ * Refine - ask the daemon for proposed rewrites of a pinned set of documents.
329
329
  * This call never writes: accepted proposals go back through {@link
330
330
  * DaemonClient.writeFile}, one per file, like any other save.
331
331
  */
332
332
  export interface FileRefineOptions {
333
333
  /** Provider resolution only; the documents themselves travel inline. */
334
334
  cwd: string;
335
- /** What the model may rewrite this list is the request's blast radius. */
335
+ /** What the model may rewrite - this list is the request's blast radius. */
336
336
  documents: FileRefineDocument[];
337
337
  /** What it may read for context but must never rewrite. */
338
338
  references?: FileRefineReference[];
@@ -1008,7 +1008,7 @@ export declare class DaemonClient {
1008
1008
  * the whole archived set. Pass `dryRun: true` first to get the count the
1009
1009
  * confirm dialog quotes, then the same call with `dryRun: false` to delete.
1010
1010
  *
1011
- * Removes Otto's records only provider transcripts are left on disk. Gated
1011
+ * Removes Otto's records only - provider transcripts are left on disk. Gated
1012
1012
  * by `server_info.features.historyDelete`; there is no fallback path, so check
1013
1013
  * the flag before offering the action.
1014
1014
  */
@@ -1029,7 +1029,7 @@ export declare class DaemonClient {
1029
1029
  * `dryRun: false` to delete.
1030
1030
  *
1031
1031
  * Cleared images do not come back: a message that referenced one renders its
1032
- * alt text from then on. Scope is the whole host filenames are a content
1032
+ * alt text from then on. Scope is the whole host - filenames are a content
1033
1033
  * hash, so per-chat or per-workspace scope does not exist. Gated by
1034
1034
  * `server_info.features.attachmentStorage`.
1035
1035
  */
@@ -1050,7 +1050,7 @@ export declare class DaemonClient {
1050
1050
  stopObservedSubagent(agentId: string): Promise<void>;
1051
1051
  /**
1052
1052
  * Stop a running background shell task (Claude Bash tool run_in_background).
1053
- * Not an AI subagent the daemon resolves it to its owning provider task and
1053
+ * Not an AI subagent - the daemon resolves it to its owning provider task and
1054
1054
  * calls the provider's stopTask, same mechanism as stopObservedSubagent.
1055
1055
  */
1056
1056
  stopBackgroundShellTask(parentAgentId: string, taskId: string): Promise<void>;
@@ -1061,7 +1061,7 @@ export declare class DaemonClient {
1061
1061
  clearBackgroundShellTasks(parentAgentId: string, taskIds: readonly string[]): Promise<void>;
1062
1062
  /**
1063
1063
  * Start one or more suggested tasks (from `spawn_task` chips), applying the
1064
- * same mode to each a new worktree workspace per task, a new local session
1064
+ * same mode to each - a new worktree workspace per task, a new local session
1065
1065
  * per task, or steering the parent's current session. Individual actions pass
1066
1066
  * a single-element array; the "Start all" collective action passes the whole
1067
1067
  * pending queue. Resolves to the count started; throws only if all failed.
@@ -1087,7 +1087,7 @@ export declare class DaemonClient {
1087
1087
  clearFinishedRuns(): Promise<string[]>;
1088
1088
  /**
1089
1089
  * Orchestration: delete one finished (or draft) run. Throws with the
1090
- * daemon's reason when it refuses an active run has to be canceled first.
1090
+ * daemon's reason when it refuses - an active run has to be canceled first.
1091
1091
  */
1092
1092
  deleteRun(runId: string): Promise<string>;
1093
1093
  /** Orchestration: list the host's reusable graph templates. */
@@ -1217,7 +1217,7 @@ export declare class DaemonClient {
1217
1217
  } | null>;
1218
1218
  /**
1219
1219
  * Move one queued message to a new position. Resolves false when the entry
1220
- * was already drained or was already there the authoritative order arrives
1220
+ * was already drained or was already there - the authoritative order arrives
1221
1221
  * on the agent snapshot either way. Requires
1222
1222
  * `server_info.features.steerQueueReorder`.
1223
1223
  */
@@ -1235,7 +1235,7 @@ export declare class DaemonClient {
1235
1235
  /**
1236
1236
  * Live-switch a running agent's personality (null clears it). The daemon
1237
1237
  * re-resolves the roster id against the agent's cwd and applies the full
1238
- * personality system prompt, identity, model/mode/effort restarting the
1238
+ * personality - system prompt, identity, model/mode/effort - restarting the
1239
1239
  * provider query so the prompt takes effect on the next turn. Gate on
1240
1240
  * server_info.features.setAgentPersonality.
1241
1241
  */
@@ -1436,10 +1436,10 @@ export declare class DaemonClient {
1436
1436
  expectedModifiedAt: string;
1437
1437
  expectedRevision?: string;
1438
1438
  }): Promise<FsFileWriteResult>;
1439
- /** Conditional save see FileWriteRequestSchema for the no-clobber contract. */
1439
+ /** Conditional save - see FileWriteRequestSchema for the no-clobber contract. */
1440
1440
  writeFile(options: FileWriteOptions): Promise<FileWriteResult>;
1441
1441
  /**
1442
- * Write bytes to a workspace file the path for generated artifacts the
1442
+ * Write bytes to a workspace file - the path for generated artifacts the
1443
1443
  * text write cannot carry (it refuses binary targets outright). Gated on
1444
1444
  * `features.binaryFileWrite`; there is no client-side substitute, because
1445
1445
  * the client never touches a workspace file on any platform.
@@ -1454,9 +1454,9 @@ export declare class DaemonClient {
1454
1454
  * frames leave in order and behind the JSON request that announced them.
1455
1455
  */
1456
1456
  private sendFileTransfer;
1457
- /** Create an empty file or a directory. Never overwrites see FileCreateResultSchema. */
1457
+ /** Create an empty file or a directory. Never overwrites - see FileCreateResultSchema. */
1458
1458
  createFileEntry(options: FileCreateOptions): Promise<FileCreateResult>;
1459
- /** Permanent delete an unlink, not a move to any trash. */
1459
+ /** Permanent delete - an unlink, not a move to any trash. */
1460
1460
  deleteFileEntry(options: FileDeleteOptions): Promise<FileDeleteResult>;
1461
1461
  /** Rename, which is also move. Never clobbers an occupied destination. */
1462
1462
  renameFileEntry(options: FileRenameOptions): Promise<FileRenameResult>;
@@ -1483,7 +1483,7 @@ export declare class DaemonClient {
1483
1483
  findCodeDefinition(input: CodeDefinitionQuery, requestId?: string): Promise<CodeDefinitionResult>;
1484
1484
  /**
1485
1485
  * Mirror the editor's current buffer to the daemon so definitions resolve against
1486
- * unsaved edits. Debounced by the caller this is not a per-keystroke RPC.
1486
+ * unsaved edits. Debounced by the caller - this is not a per-keystroke RPC.
1487
1487
  */
1488
1488
  syncCodeDocument(cwd: string, path: string, text: string, requestId?: string): Promise<void>;
1489
1489
  /** Release the daemon-side mirror when a file tab closes. */
@@ -1497,7 +1497,7 @@ export declare class DaemonClient {
1497
1497
  /** Every reference to the symbol at a position, for the references results tab. */
1498
1498
  findCodeReferences(input: CodeDefinitionQuery, requestId?: string): Promise<CodeReferencesResult>;
1499
1499
  /**
1500
- * A rename **dry run** every edit it would make, and nothing written. The client
1500
+ * A rename **dry run** - every edit it would make, and nothing written. The client
1501
1501
  * puts this in front of the user as a job to audit before applying.
1502
1502
  */
1503
1503
  previewCodeRename(input: CodeRenamePreviewQuery, requestId?: string): Promise<CodeRenamePlan>;
@@ -1531,7 +1531,7 @@ export declare class DaemonClient {
1531
1531
  *
1532
1532
  * Never throws and never carries an error the caller has to render. A workspace with no
1533
1533
  * solution, a host with no .NET SDK, and a host with the feature switched off all answer with an
1534
- * empty list, so the caller has one silent case "no switcher" rather than four states.
1534
+ * empty list, so the caller has one silent case - "no switcher" - rather than four states.
1535
1535
  */
1536
1536
  listSolutions(cwd: string, requestId?: string): Promise<SolutionRef[]>;
1537
1537
  /** One solution's organisation: folders, the projects inside them, configurations. */
@@ -1552,7 +1552,7 @@ export declare class DaemonClient {
1552
1552
  }, requestId?: string): Promise<SolutionProjectContents>;
1553
1553
  /** Definition symbols for a single file (document outline). */
1554
1554
  getCodeOutline(cwd: string, path: string, requestId?: string): Promise<CodeSymbolLocation[]>;
1555
- /** Preview-first project replace see FileReplaceRequestSchema. */
1555
+ /** Preview-first project replace - see FileReplaceRequestSchema. */
1556
1556
  replaceFiles(options: {
1557
1557
  cwd: string;
1558
1558
  replacement: string;
@@ -1606,7 +1606,7 @@ export declare class DaemonClient {
1606
1606
  *
1607
1607
  * Pass `workspaceId` whenever the write may be project-scoped: the daemon
1608
1608
  * binds the entry to the repo root that workspace resolves to, and an entry
1609
- * scoped to "project" with no root is filtered out of every brief stored,
1609
+ * scoped to "project" with no root is filtered out of every brief - stored,
1610
1610
  * listed, and never sent.
1611
1611
  */
1612
1612
  updatePersonalityMemory(input: {
@@ -1683,7 +1683,23 @@ export declare class DaemonClient {
1683
1683
  config: MutableDaemonConfig;
1684
1684
  }>;
1685
1685
  connectorsListTools(connectorId: string, requestId?: string): Promise<ConnectorsListToolsResponse["payload"]>;
1686
- brainHostStatus(requestId?: string): Promise<BrainHostStatus>;
1686
+ /**
1687
+ * Start a connector's OAuth login. Resolves with the URL to open, or with
1688
+ * status "authorized" when the daemon already held a usable token. The login
1689
+ * itself settles later on the `connectors.oauth.status` push, because the user
1690
+ * is in a browser by then.
1691
+ */
1692
+ connectorsOauthAuthorize(connectorId: string, scope?: string, requestId?: string): Promise<ConnectorsOauthAuthorizeResponse["payload"]>;
1693
+ /** Drop a connector's stored authorization. */
1694
+ connectorsOauthDisconnect(connectorId: string, requestId?: string): Promise<ConnectorsOauthDisconnectResponse["payload"]>;
1695
+ /**
1696
+ * The brain's status. Pass `resources` only from a surface that renders the
1697
+ * live CPU/RAM/GPU/slot numbers: it costs an `nvidia-smi` spawn plus a /slots
1698
+ * round trip on the brain, and this call is also the liveness poll.
1699
+ */
1700
+ brainHostStatus(options?: {
1701
+ resources?: boolean;
1702
+ }, requestId?: string): Promise<BrainHostStatus>;
1687
1703
  brainHostStart(model?: string | null, requestId?: string): Promise<BrainHostStatus>;
1688
1704
  brainHostStop(requestId?: string): Promise<BrainHostStatus>;
1689
1705
  brainHostRestart(model?: string | null, requestId?: string): Promise<BrainHostStatus>;
@@ -1705,6 +1721,40 @@ export declare class DaemonClient {
1705
1721
  brainBench(model?: string | null, requestId?: string): Promise<BrainJob>;
1706
1722
  brainJobsList(requestId?: string): Promise<BrainJob[]>;
1707
1723
  brainJobsCancel(jobId: string, requestId?: string): Promise<BrainJob[]>;
1724
+ /**
1725
+ * The joined model inventory: scan row, GGUF metadata, saved profile,
1726
+ * calibration state, VRAM budget and benchmark score per model, plus disk
1727
+ * usage. One call feeds the whole Models tab.
1728
+ */
1729
+ brainModelsInventory(requestId?: string): Promise<{
1730
+ models: BrainInventoryModel[];
1731
+ disk: BrainDiskUsage | null;
1732
+ }>;
1733
+ /** One model's saved profile, the field descriptors, and its warnings. */
1734
+ brainModelProfileGet(modelId: string, requestId?: string): Promise<BrainModelProfileGetResponse["payload"]>;
1735
+ /**
1736
+ * Write the editable profile fields. The reply carries the recomputed budget,
1737
+ * so an edit costs one round trip rather than a write the UI has to follow
1738
+ * with a read.
1739
+ */
1740
+ brainModelProfileSet(modelId: string, patch: Record<string, unknown>, requestId?: string): Promise<BrainModelProfileSetResponse["payload"]>;
1741
+ /**
1742
+ * The VRAM budget for a hypothetical profile. `overrides` are string-encoded
1743
+ * field values, so the UI can preview the budget while a control is mid-drag
1744
+ * without persisting a value the user is scrubbing past.
1745
+ */
1746
+ brainModelBudget(modelId: string, overrides?: Record<string, string>, requestId?: string): Promise<BrainModelBudgetGetResponse["payload"]>;
1747
+ /**
1748
+ * Load a model into the running brain. This is not `brainHostStart`, which
1749
+ * restarts the daemon's child process and has no remote equivalent.
1750
+ */
1751
+ brainModelLoad(modelId: string, requestId?: string): Promise<BrainModelLoadResponse["payload"]>;
1752
+ /** Unload the resident model, leaving the brain up and serving nothing. */
1753
+ brainModelUnload(requestId?: string): Promise<BrainHostStatus | null>;
1754
+ /** Delete a model's files. The brain refuses while that model is loaded. */
1755
+ brainModelDelete(modelId: string, requestId?: string): Promise<BrainModelDeleteResponse["payload"]>;
1756
+ /** Tail the brain's llama-server log. */
1757
+ brainLogsTail(limit?: number | null, requestId?: string): Promise<BrainLogsTailResponse["payload"]>;
1708
1758
  getSpeechSettingsOptions(requestId?: string): Promise<{
1709
1759
  requestId: string;
1710
1760
  options: SpeechSettingsOptions;
@@ -1758,7 +1808,7 @@ export declare class DaemonClient {
1758
1808
  listProviderUsage(options?: {
1759
1809
  requestId?: string;
1760
1810
  }): Promise<ProviderUsageListPayload>;
1761
- /** Daemon-wide "fun stats" see docs/data-model.md ActivityStatsStore. */
1811
+ /** Daemon-wide "fun stats" - see docs/data-model.md ActivityStatsStore. */
1762
1812
  getActivityStats(options?: {
1763
1813
  requestId?: string;
1764
1814
  }): Promise<StatsActivityGetResponseMessage["payload"]>;
@@ -1766,7 +1816,7 @@ export declare class DaemonClient {
1766
1816
  resetActivityStats(options?: {
1767
1817
  requestId?: string;
1768
1818
  }): Promise<StatsActivityResetResponseMessage["payload"]>;
1769
- /** Itemized usage ledger the scrollable rows behind the stats tiles (usage-ledger). */
1819
+ /** Itemized usage ledger - the scrollable rows behind the stats tiles (usage-ledger). */
1770
1820
  getUsageLog(options?: {
1771
1821
  limit?: number;
1772
1822
  before?: number;
@@ -1895,7 +1945,7 @@ export declare class DaemonClient {
1895
1945
  /**
1896
1946
  * Session totals for inbound daemon traffic, including the main-thread time
1897
1947
  * spent handling it. Null when runtime metrics are disabled for this client.
1898
- * Read by the app's resource monitor the wire is a first-class suspect when
1948
+ * Read by the app's resource monitor - the wire is a first-class suspect when
1899
1949
  * the UI thread degrades, so it has to be measurable rather than inferred.
1900
1950
  */
1901
1951
  getTrafficTotals(): DaemonClientTrafficTotals | null;
@@ -1932,7 +1982,7 @@ export declare class DaemonClient {
1932
1982
  /**
1933
1983
  * `includeDiscovered` also returns the Scripts the workspace's own project
1934
1984
  * files declare (package.json scripts today), each tagged with its `source`.
1935
- * Gate it on `server_info.features.workspaceScriptDiscovery` an older
1985
+ * Gate it on `server_info.features.workspaceScriptDiscovery` - an older
1936
1986
  * daemon ignores the flag and answers with the otto.json list only.
1937
1987
  */
1938
1988
  listWorkspaceScripts(workspaceId: string, options?: {
@@ -1950,7 +2000,7 @@ export declare class DaemonClient {
1950
2000
  connectHub(hubUrl: string, token: string, requestId?: string): Promise<{
1951
2001
  requestId: string;
1952
2002
  status: {
1953
- state: "connecting" | "connected" | "not_connected" | "reconnecting" | "disconnecting" | "revoked";
2003
+ state: "connected" | "connecting" | "not_connected" | "reconnecting" | "disconnecting" | "revoked";
1954
2004
  daemonId: string | null;
1955
2005
  hubOrigin: string | null;
1956
2006
  scopes: string[];
@@ -1961,7 +2011,7 @@ export declare class DaemonClient {
1961
2011
  getHubStatus(requestId?: string): Promise<{
1962
2012
  requestId: string;
1963
2013
  status: {
1964
- state: "connecting" | "connected" | "not_connected" | "reconnecting" | "disconnecting" | "revoked";
2014
+ state: "connected" | "connecting" | "not_connected" | "reconnecting" | "disconnecting" | "revoked";
1965
2015
  daemonId: string | null;
1966
2016
  hubOrigin: string | null;
1967
2017
  scopes: string[];
@@ -1972,7 +2022,7 @@ export declare class DaemonClient {
1972
2022
  disconnectHub(force?: boolean, requestId?: string): Promise<{
1973
2023
  requestId: string;
1974
2024
  status: {
1975
- state: "connecting" | "connected" | "not_connected" | "reconnecting" | "disconnecting" | "revoked";
2025
+ state: "connected" | "connecting" | "not_connected" | "reconnecting" | "disconnecting" | "revoked";
1976
2026
  daemonId: string | null;
1977
2027
  hubOrigin: string | null;
1978
2028
  scopes: string[];
@@ -293,7 +293,7 @@ export class DaemonClient {
293
293
  const runtimeMetricsIntervalMs = typeof config.runtimeMetricsIntervalMs === "number" && config.runtimeMetricsIntervalMs > 0
294
294
  ? config.runtimeMetricsIntervalMs
295
295
  : 0;
296
- // The metrics object is always constructed it is a handful of Maps keyed
296
+ // The metrics object is always constructed - it is a handful of Maps keyed
297
297
  // by message type, and its per-message cost is dwarfed by the JSON.parse it
298
298
  // is measuring. What `runtimeMetricsIntervalMs` gates is the *periodic log*,
299
299
  // which is the part that is actually noisy. Keeping the counters on
@@ -1393,7 +1393,7 @@ export class DaemonClient {
1393
1393
  * the whole archived set. Pass `dryRun: true` first to get the count the
1394
1394
  * confirm dialog quotes, then the same call with `dryRun: false` to delete.
1395
1395
  *
1396
- * Removes Otto's records only provider transcripts are left on disk. Gated
1396
+ * Removes Otto's records only - provider transcripts are left on disk. Gated
1397
1397
  * by `server_info.features.historyDelete`; there is no fallback path, so check
1398
1398
  * the flag before offering the action.
1399
1399
  */
@@ -1424,7 +1424,7 @@ export class DaemonClient {
1424
1424
  * `dryRun: false` to delete.
1425
1425
  *
1426
1426
  * Cleared images do not come back: a message that referenced one renders its
1427
- * alt text from then on. Scope is the whole host filenames are a content
1427
+ * alt text from then on. Scope is the whole host - filenames are a content
1428
1428
  * hash, so per-chat or per-workspace scope does not exist. Gated by
1429
1429
  * `server_info.features.attachmentStorage`.
1430
1430
  */
@@ -1490,7 +1490,7 @@ export class DaemonClient {
1490
1490
  }
1491
1491
  /**
1492
1492
  * Stop a running background shell task (Claude Bash tool run_in_background).
1493
- * Not an AI subagent the daemon resolves it to its owning provider task and
1493
+ * Not an AI subagent - the daemon resolves it to its owning provider task and
1494
1494
  * calls the provider's stopTask, same mechanism as stopObservedSubagent.
1495
1495
  */
1496
1496
  async stopBackgroundShellTask(parentAgentId, taskId) {
@@ -1523,7 +1523,7 @@ export class DaemonClient {
1523
1523
  }
1524
1524
  /**
1525
1525
  * Start one or more suggested tasks (from `spawn_task` chips), applying the
1526
- * same mode to each a new worktree workspace per task, a new local session
1526
+ * same mode to each - a new worktree workspace per task, a new local session
1527
1527
  * per task, or steering the parent's current session. Individual actions pass
1528
1528
  * a single-element array; the "Start all" collective action passes the whole
1529
1529
  * pending queue. Resolves to the count started; throws only if all failed.
@@ -1591,7 +1591,7 @@ export class DaemonClient {
1591
1591
  }
1592
1592
  /**
1593
1593
  * Orchestration: delete one finished (or draft) run. Throws with the
1594
- * daemon's reason when it refuses an active run has to be canceled first.
1594
+ * daemon's reason when it refuses - an active run has to be canceled first.
1595
1595
  */
1596
1596
  async deleteRun(runId) {
1597
1597
  const payload = await this.sendNamespacedCorrelatedSessionRequest({
@@ -2298,7 +2298,7 @@ export class DaemonClient {
2298
2298
  }
2299
2299
  /**
2300
2300
  * Move one queued message to a new position. Resolves false when the entry
2301
- * was already drained or was already there the authoritative order arrives
2301
+ * was already drained or was already there - the authoritative order arrives
2302
2302
  * on the agent snapshot either way. Requires
2303
2303
  * `server_info.features.steerQueueReorder`.
2304
2304
  */
@@ -2408,7 +2408,7 @@ export class DaemonClient {
2408
2408
  });
2409
2409
  // A refused cancellation comes back as an error payload, not a rejected
2410
2410
  // frame. Dropping it reported Stop as successful while the provider was
2411
- // still running and still spending tokens the exact outcome the daemon
2411
+ // still running and still spending tokens - the exact outcome the daemon
2412
2412
  // refuses the cancel to avoid.
2413
2413
  if (payload.error) {
2414
2414
  throw new Error(payload.error);
@@ -2527,7 +2527,7 @@ export class DaemonClient {
2527
2527
  /**
2528
2528
  * Live-switch a running agent's personality (null clears it). The daemon
2529
2529
  * re-resolves the roster id against the agent's cwd and applies the full
2530
- * personality system prompt, identity, model/mode/effort restarting the
2530
+ * personality - system prompt, identity, model/mode/effort - restarting the
2531
2531
  * provider query so the prompt takes effect on the next turn. Gate on
2532
2532
  * server_info.features.setAgentPersonality.
2533
2533
  */
@@ -2619,7 +2619,7 @@ export class DaemonClient {
2619
2619
  return this.sendRequest({
2620
2620
  requestId: resolvedRequestId,
2621
2621
  message,
2622
- timeout: 300000, // 5 minutes npm update can be slow on remote machines
2622
+ timeout: 300000, // 5 minutes - npm update can be slow on remote machines
2623
2623
  options: { skipQueue: true },
2624
2624
  select: (msg) => {
2625
2625
  const parsed = DaemonUpdateResponseSchema.safeParse(msg);
@@ -3017,7 +3017,7 @@ export class DaemonClient {
3017
3017
  });
3018
3018
  }
3019
3019
  // ── Git file investigation ────────────────────────────────────────────────
3020
- // Local git only: no remote, no forge, and no per-provider variant the same
3020
+ // Local git only: no remote, no forge, and no per-provider variant - the same
3021
3021
  // four calls answer for every agent provider. Gated by
3022
3022
  // server_info.features.checkoutGitFileHistory.
3023
3023
  async checkoutGitFileHistory(cwd, input, requestId) {
@@ -3510,7 +3510,7 @@ export class DaemonClient {
3510
3510
  content: file.content,
3511
3511
  size: file.size,
3512
3512
  modifiedAt: file.modifiedAt,
3513
- // COMPAT(textEditor): added in v0.4.4 the editor is gated on
3513
+ // COMPAT(textEditor): added in v0.4.4 - the editor is gated on
3514
3514
  // features.textEditor, so a gated daemon always sends both fields.
3515
3515
  eol: file.eol ?? "lf",
3516
3516
  hash: file.hash ?? null,
@@ -3528,7 +3528,7 @@ export class DaemonClient {
3528
3528
  });
3529
3529
  return payload.result;
3530
3530
  }
3531
- /** Conditional save see FileWriteRequestSchema for the no-clobber contract. */
3531
+ /** Conditional save - see FileWriteRequestSchema for the no-clobber contract. */
3532
3532
  async writeFile(options) {
3533
3533
  const payload = await this.sendCorrelatedSessionRequest({
3534
3534
  requestId: options.requestId,
@@ -3547,7 +3547,7 @@ export class DaemonClient {
3547
3547
  return payload.result;
3548
3548
  }
3549
3549
  /**
3550
- * Write bytes to a workspace file the path for generated artifacts the
3550
+ * Write bytes to a workspace file - the path for generated artifacts the
3551
3551
  * text write cannot carry (it refuses binary targets outright). Gated on
3552
3552
  * `features.binaryFileWrite`; there is no client-side substitute, because
3553
3553
  * the client never touches a workspace file on any platform.
@@ -3576,8 +3576,8 @@ export class DaemonClient {
3576
3576
  this.sendFileTransfer({
3577
3577
  requestId: resolvedRequestId,
3578
3578
  bytes,
3579
- // Nothing downstream reads the mime for a workspace write the path
3580
- // decides what the file is but the frame metadata requires one.
3579
+ // Nothing downstream reads the mime for a workspace write - the path
3580
+ // decides what the file is - but the frame metadata requires one.
3581
3581
  mime: "application/octet-stream",
3582
3582
  chunkSize: options.chunkSize,
3583
3583
  });
@@ -3613,7 +3613,7 @@ export class DaemonClient {
3613
3613
  requestId: input.requestId,
3614
3614
  }));
3615
3615
  }
3616
- /** Create an empty file or a directory. Never overwrites see FileCreateResultSchema. */
3616
+ /** Create an empty file or a directory. Never overwrites - see FileCreateResultSchema. */
3617
3617
  async createFileEntry(options) {
3618
3618
  const payload = await this.sendCorrelatedSessionRequest({
3619
3619
  requestId: options.requestId,
@@ -3627,7 +3627,7 @@ export class DaemonClient {
3627
3627
  });
3628
3628
  return payload.result;
3629
3629
  }
3630
- /** Permanent delete an unlink, not a move to any trash. */
3630
+ /** Permanent delete - an unlink, not a move to any trash. */
3631
3631
  async deleteFileEntry(options) {
3632
3632
  const payload = await this.sendCorrelatedSessionRequest({
3633
3633
  requestId: options.requestId,
@@ -3749,7 +3749,7 @@ export class DaemonClient {
3749
3749
  }
3750
3750
  /**
3751
3751
  * Mirror the editor's current buffer to the daemon so definitions resolve against
3752
- * unsaved edits. Debounced by the caller this is not a per-keystroke RPC.
3752
+ * unsaved edits. Debounced by the caller - this is not a per-keystroke RPC.
3753
3753
  */
3754
3754
  async syncCodeDocument(cwd, path, text, requestId) {
3755
3755
  const payload = await this.sendCorrelatedSessionRequest({
@@ -3813,7 +3813,7 @@ export class DaemonClient {
3813
3813
  return { status: payload.status, locations: payload.locations, error: payload.error };
3814
3814
  }
3815
3815
  /**
3816
- * A rename **dry run** every edit it would make, and nothing written. The client
3816
+ * A rename **dry run** - every edit it would make, and nothing written. The client
3817
3817
  * puts this in front of the user as a job to audit before applying.
3818
3818
  */
3819
3819
  async previewCodeRename(input, requestId) {
@@ -3924,7 +3924,7 @@ export class DaemonClient {
3924
3924
  *
3925
3925
  * Never throws and never carries an error the caller has to render. A workspace with no
3926
3926
  * solution, a host with no .NET SDK, and a host with the feature switched off all answer with an
3927
- * empty list, so the caller has one silent case "no switcher" rather than four states.
3927
+ * empty list, so the caller has one silent case - "no switcher" - rather than four states.
3928
3928
  */
3929
3929
  async listSolutions(cwd, requestId) {
3930
3930
  const payload = await this.sendCorrelatedSessionRequest({
@@ -3999,7 +3999,7 @@ export class DaemonClient {
3999
3999
  }
4000
4000
  return payload.symbols;
4001
4001
  }
4002
- /** Preview-first project replace see FileReplaceRequestSchema. */
4002
+ /** Preview-first project replace - see FileReplaceRequestSchema. */
4003
4003
  async replaceFiles(options) {
4004
4004
  return this.sendCorrelatedSessionRequest({
4005
4005
  requestId: options.requestId,
@@ -4171,7 +4171,7 @@ export class DaemonClient {
4171
4171
  *
4172
4172
  * Pass `workspaceId` whenever the write may be project-scoped: the daemon
4173
4173
  * binds the entry to the repo root that workspace resolves to, and an entry
4174
- * scoped to "project" with no root is filtered out of every brief stored,
4174
+ * scoped to "project" with no root is filtered out of every brief - stored,
4175
4175
  * listed, and never sent.
4176
4176
  */
4177
4177
  async updatePersonalityMemory(input, requestId) {
@@ -4358,11 +4358,45 @@ export class DaemonClient {
4358
4358
  responseType: "connectors.list_tools.response",
4359
4359
  });
4360
4360
  }
4361
- async brainHostStatus(requestId) {
4361
+ /**
4362
+ * Start a connector's OAuth login. Resolves with the URL to open, or with
4363
+ * status "authorized" when the daemon already held a usable token. The login
4364
+ * itself settles later on the `connectors.oauth.status` push, because the user
4365
+ * is in a browser by then.
4366
+ */
4367
+ async connectorsOauthAuthorize(connectorId, scope, requestId) {
4368
+ return this.sendCorrelatedSessionRequest({
4369
+ requestId,
4370
+ message: {
4371
+ type: "connectors.oauth.authorize.request",
4372
+ connectorId,
4373
+ ...(scope ? { scope } : {}),
4374
+ },
4375
+ responseType: "connectors.oauth.authorize.response",
4376
+ });
4377
+ }
4378
+ /** Drop a connector's stored authorization. */
4379
+ async connectorsOauthDisconnect(connectorId, requestId) {
4380
+ return this.sendCorrelatedSessionRequest({
4381
+ requestId,
4382
+ message: {
4383
+ type: "connectors.oauth.disconnect.request",
4384
+ connectorId,
4385
+ },
4386
+ responseType: "connectors.oauth.disconnect.response",
4387
+ });
4388
+ }
4389
+ /**
4390
+ * The brain's status. Pass `resources` only from a surface that renders the
4391
+ * live CPU/RAM/GPU/slot numbers: it costs an `nvidia-smi` spawn plus a /slots
4392
+ * round trip on the brain, and this call is also the liveness poll.
4393
+ */
4394
+ async brainHostStatus(options, requestId) {
4362
4395
  const payload = await this.sendCorrelatedSessionRequest({
4363
4396
  requestId,
4364
4397
  message: {
4365
4398
  type: "brain.host.status.request",
4399
+ resources: options?.resources ?? false,
4366
4400
  },
4367
4401
  responseType: "brain.host.status.response",
4368
4402
  });
@@ -4588,6 +4622,123 @@ export class DaemonClient {
4588
4622
  }
4589
4623
  return payload.jobs;
4590
4624
  }
4625
+ // --- Brain Console --------------------------------------------------------
4626
+ // These proxy the brain's own /__host/* management API and work against a
4627
+ // local or a remote brain identically. Gated by features.brainConsole on the
4628
+ // daemon, and by `capabilities` on brain.host.status for the brain itself.
4629
+ // Each throws the brain's own message on failure, because "could not delete
4630
+ // the model" with no reason is not a usable error.
4631
+ /**
4632
+ * The joined model inventory: scan row, GGUF metadata, saved profile,
4633
+ * calibration state, VRAM budget and benchmark score per model, plus disk
4634
+ * usage. One call feeds the whole Models tab.
4635
+ */
4636
+ async brainModelsInventory(requestId) {
4637
+ const payload = await this.sendCorrelatedSessionRequest({
4638
+ requestId,
4639
+ message: { type: "brain.models.inventory.request" },
4640
+ responseType: "brain.models.inventory.response",
4641
+ });
4642
+ if (payload.error) {
4643
+ throw new Error(payload.error);
4644
+ }
4645
+ return { models: payload.models, disk: payload.disk };
4646
+ }
4647
+ /** One model's saved profile, the field descriptors, and its warnings. */
4648
+ async brainModelProfileGet(modelId, requestId) {
4649
+ const payload = await this.sendCorrelatedSessionRequest({
4650
+ requestId,
4651
+ message: { type: "brain.model.profile.get.request", modelId },
4652
+ responseType: "brain.model.profile.get.response",
4653
+ });
4654
+ if (payload.error) {
4655
+ throw new Error(payload.error);
4656
+ }
4657
+ return payload;
4658
+ }
4659
+ /**
4660
+ * Write the editable profile fields. The reply carries the recomputed budget,
4661
+ * so an edit costs one round trip rather than a write the UI has to follow
4662
+ * with a read.
4663
+ */
4664
+ async brainModelProfileSet(modelId, patch, requestId) {
4665
+ const payload = await this.sendCorrelatedSessionRequest({
4666
+ requestId,
4667
+ message: { type: "brain.model.profile.set.request", modelId, patch },
4668
+ responseType: "brain.model.profile.set.response",
4669
+ });
4670
+ if (payload.error) {
4671
+ throw new Error(payload.error);
4672
+ }
4673
+ return payload;
4674
+ }
4675
+ /**
4676
+ * The VRAM budget for a hypothetical profile. `overrides` are string-encoded
4677
+ * field values, so the UI can preview the budget while a control is mid-drag
4678
+ * without persisting a value the user is scrubbing past.
4679
+ */
4680
+ async brainModelBudget(modelId, overrides, requestId) {
4681
+ const payload = await this.sendCorrelatedSessionRequest({
4682
+ requestId,
4683
+ message: { type: "brain.model.budget.get.request", modelId, overrides: overrides ?? {} },
4684
+ responseType: "brain.model.budget.get.response",
4685
+ });
4686
+ if (payload.error) {
4687
+ throw new Error(payload.error);
4688
+ }
4689
+ return payload;
4690
+ }
4691
+ /**
4692
+ * Load a model into the running brain. This is not `brainHostStart`, which
4693
+ * restarts the daemon's child process and has no remote equivalent.
4694
+ */
4695
+ async brainModelLoad(modelId, requestId) {
4696
+ const payload = await this.sendCorrelatedSessionRequest({
4697
+ requestId,
4698
+ message: { type: "brain.model.load.request", modelId },
4699
+ responseType: "brain.model.load.response",
4700
+ });
4701
+ if (payload.error) {
4702
+ throw new Error(payload.error);
4703
+ }
4704
+ return payload;
4705
+ }
4706
+ /** Unload the resident model, leaving the brain up and serving nothing. */
4707
+ async brainModelUnload(requestId) {
4708
+ const payload = await this.sendCorrelatedSessionRequest({
4709
+ requestId,
4710
+ message: { type: "brain.model.unload.request" },
4711
+ responseType: "brain.model.unload.response",
4712
+ });
4713
+ if (payload.error) {
4714
+ throw new Error(payload.error);
4715
+ }
4716
+ return payload.status;
4717
+ }
4718
+ /** Delete a model's files. The brain refuses while that model is loaded. */
4719
+ async brainModelDelete(modelId, requestId) {
4720
+ const payload = await this.sendCorrelatedSessionRequest({
4721
+ requestId,
4722
+ message: { type: "brain.model.delete.request", modelId },
4723
+ responseType: "brain.model.delete.response",
4724
+ });
4725
+ if (payload.error) {
4726
+ throw new Error(payload.error);
4727
+ }
4728
+ return payload;
4729
+ }
4730
+ /** Tail the brain's llama-server log. */
4731
+ async brainLogsTail(limit, requestId) {
4732
+ const payload = await this.sendCorrelatedSessionRequest({
4733
+ requestId,
4734
+ message: { type: "brain.logs.tail.request", limit: limit ?? null },
4735
+ responseType: "brain.logs.tail.response",
4736
+ });
4737
+ if (payload.error) {
4738
+ throw new Error(payload.error);
4739
+ }
4740
+ return payload;
4741
+ }
4591
4742
  async getSpeechSettingsOptions(requestId) {
4592
4743
  return this.sendNamespacedCorrelatedSessionRequest({
4593
4744
  requestId,
@@ -4733,7 +4884,7 @@ export class DaemonClient {
4733
4884
  },
4734
4885
  });
4735
4886
  }
4736
- /** Daemon-wide "fun stats" see docs/data-model.md ActivityStatsStore. */
4887
+ /** Daemon-wide "fun stats" - see docs/data-model.md ActivityStatsStore. */
4737
4888
  async getActivityStats(options) {
4738
4889
  return this.sendNamespacedCorrelatedSessionRequest({
4739
4890
  requestId: options?.requestId,
@@ -4751,7 +4902,7 @@ export class DaemonClient {
4751
4902
  },
4752
4903
  });
4753
4904
  }
4754
- /** Itemized usage ledger the scrollable rows behind the stats tiles (usage-ledger). */
4905
+ /** Itemized usage ledger - the scrollable rows behind the stats tiles (usage-ledger). */
4755
4906
  async getUsageLog(options) {
4756
4907
  return this.sendNamespacedCorrelatedSessionRequest({
4757
4908
  requestId: options?.requestId,
@@ -5475,7 +5626,7 @@ export class DaemonClient {
5475
5626
  /**
5476
5627
  * Session totals for inbound daemon traffic, including the main-thread time
5477
5628
  * spent handling it. Null when runtime metrics are disabled for this client.
5478
- * Read by the app's resource monitor the wire is a first-class suspect when
5629
+ * Read by the app's resource monitor - the wire is a first-class suspect when
5479
5630
  * the UI thread degrades, so it has to be measurable rather than inferred.
5480
5631
  */
5481
5632
  getTrafficTotals() {
@@ -6043,7 +6194,7 @@ export class DaemonClient {
6043
6194
  /**
6044
6195
  * `includeDiscovered` also returns the Scripts the workspace's own project
6045
6196
  * files declare (package.json scripts today), each tagged with its `source`.
6046
- * Gate it on `server_info.features.workspaceScriptDiscovery` an older
6197
+ * Gate it on `server_info.features.workspaceScriptDiscovery` - an older
6047
6198
  * daemon ignores the flag and answers with the otto.json list only.
6048
6199
  */
6049
6200
  async listWorkspaceScripts(workspaceId, options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@otto-code/client",
3
- "version": "0.7.6",
3
+ "version": "0.8.1",
4
4
  "description": "Otto client SDK package",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "files": [
@@ -36,8 +36,8 @@
36
36
  "test": "vitest run"
37
37
  },
38
38
  "dependencies": {
39
- "@otto-code/protocol": "0.7.6",
40
- "@otto-code/relay": "0.7.6",
39
+ "@otto-code/protocol": "0.8.1",
40
+ "@otto-code/relay": "0.8.1",
41
41
  "zod": "^4.4.3"
42
42
  },
43
43
  "devDependencies": {