@otto-code/client 0.7.5 → 0.7.6
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.
- package/dist/daemon-client-relay-e2ee-transport.js +4 -4
- package/dist/daemon-client-transport-types.d.ts +1 -1
- package/dist/daemon-client-transport-utils.d.ts +5 -1
- package/dist/daemon-client-transport-utils.js +8 -6
- package/dist/daemon-client-transport.d.ts +1 -1
- package/dist/daemon-client-transport.js +1 -1
- package/dist/daemon-client-websocket-transport.js +9 -1
- package/dist/daemon-client.d.ts +207 -3
- package/dist/daemon-client.js +519 -26
- package/dist/index.d.ts +1 -1
- package/package.json +3 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createClientChannel, } from "@otto-code/relay/e2ee";
|
|
2
|
-
import {
|
|
2
|
+
import { extractRelayMessage, normalizeTransportPayload } from "./daemon-client-transport-utils.js";
|
|
3
3
|
export function createRelayE2eeTransportFactory(args) {
|
|
4
4
|
return ({ url, headers }) => {
|
|
5
5
|
const base = args.baseFactory({ url, headers });
|
|
@@ -38,7 +38,7 @@ export function createEncryptedTransport(base, daemonPublicKeyB64, logger) {
|
|
|
38
38
|
if (closed) {
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
|
-
emitHandlers(messageHandlers, data);
|
|
41
|
+
emitHandlers(messageHandlers, data, data instanceof ArrayBuffer);
|
|
42
42
|
};
|
|
43
43
|
const relayTransport = {
|
|
44
44
|
send: (data) => {
|
|
@@ -81,8 +81,8 @@ export function createEncryptedTransport(base, daemonPublicKeyB64, logger) {
|
|
|
81
81
|
base.onOpen(() => {
|
|
82
82
|
void startHandshake();
|
|
83
83
|
});
|
|
84
|
-
base.onMessage((
|
|
85
|
-
relayTransport.onmessage?.(
|
|
84
|
+
base.onMessage((data, isBinary) => {
|
|
85
|
+
relayTransport.onmessage?.(extractRelayMessage(data, isBinary));
|
|
86
86
|
});
|
|
87
87
|
base.onClose((event) => {
|
|
88
88
|
const record = event;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export interface DaemonTransport {
|
|
2
2
|
send: (data: string | Uint8Array | ArrayBuffer) => void;
|
|
3
3
|
close: (code?: number, reason?: string) => void;
|
|
4
|
-
onMessage: (handler: (data: unknown) => void) => () => void;
|
|
4
|
+
onMessage: (handler: (data: unknown, isBinary: boolean) => void) => () => void;
|
|
5
5
|
onOpen: (handler: () => void) => () => void;
|
|
6
6
|
onClose: (handler: (event?: unknown) => void) => () => void;
|
|
7
7
|
onError: (handler: (event?: unknown) => void) => () => void;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export declare function copyArrayBufferViewToBuffer(data: ArrayBufferView): ArrayBuffer;
|
|
2
2
|
export declare function normalizeTransportPayload(data: string | Uint8Array | ArrayBuffer): string | ArrayBuffer;
|
|
3
|
-
export
|
|
3
|
+
export interface RelayTransportMessage {
|
|
4
|
+
data: string | ArrayBuffer;
|
|
5
|
+
isBinary: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function extractRelayMessage(event: unknown, nodeIsBinary?: boolean): RelayTransportMessage;
|
|
4
8
|
export declare function describeTransportClose(event?: unknown): string;
|
|
5
9
|
export declare function describeTransportError(event?: unknown): string;
|
|
6
10
|
export declare function safeRandomId(): string;
|
|
@@ -10,18 +10,20 @@ export function normalizeTransportPayload(data) {
|
|
|
10
10
|
}
|
|
11
11
|
return copyArrayBufferViewToBuffer(data);
|
|
12
12
|
}
|
|
13
|
-
export function
|
|
13
|
+
export function extractRelayMessage(event, nodeIsBinary) {
|
|
14
14
|
const raw = event && typeof event === "object" && "data" in event
|
|
15
15
|
? event.data
|
|
16
16
|
: event;
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
const isBinary = nodeIsBinary ?? typeof raw !== "string";
|
|
18
|
+
if (!isBinary) {
|
|
19
|
+
return { data: decodeMessageData(raw) ?? String(raw ?? ""), isBinary: false };
|
|
20
|
+
}
|
|
19
21
|
if (raw instanceof ArrayBuffer)
|
|
20
|
-
return raw;
|
|
22
|
+
return { data: raw, isBinary: true };
|
|
21
23
|
if (ArrayBuffer.isView(raw)) {
|
|
22
|
-
return copyArrayBufferViewToBuffer(raw);
|
|
24
|
+
return { data: copyArrayBufferViewToBuffer(raw), isBinary: true };
|
|
23
25
|
}
|
|
24
|
-
return String(raw ?? "");
|
|
26
|
+
return { data: String(raw ?? ""), isBinary: true };
|
|
25
27
|
}
|
|
26
28
|
export function describeTransportClose(event) {
|
|
27
29
|
if (!event) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type { DaemonTransport, DaemonTransportFactory, TransportLogger, WebSocketFactory, WebSocketLike, } from "./daemon-client-transport-types.js";
|
|
2
|
-
export { decodeMessageData, describeTransportClose, describeTransportError, encodeUtf8String,
|
|
2
|
+
export { decodeMessageData, describeTransportClose, describeTransportError, encodeUtf8String, extractRelayMessage, normalizeTransportPayload, safeRandomId, } from "./daemon-client-transport-utils.js";
|
|
3
3
|
export { createEncryptedTransport, createRelayE2eeTransportFactory, } from "./daemon-client-relay-e2ee-transport.js";
|
|
4
4
|
export { bindWsHandler, createWebSocketTransportFactory, defaultWebSocketFactory, } from "./daemon-client-websocket-transport.js";
|
|
5
5
|
//# sourceMappingURL=daemon-client-transport.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { decodeMessageData, describeTransportClose, describeTransportError, encodeUtf8String,
|
|
1
|
+
export { decodeMessageData, describeTransportClose, describeTransportError, encodeUtf8String, extractRelayMessage, normalizeTransportPayload, safeRandomId, } from "./daemon-client-transport-utils.js";
|
|
2
2
|
export { createEncryptedTransport, createRelayE2eeTransportFactory, } from "./daemon-client-relay-e2ee-transport.js";
|
|
3
3
|
export { bindWsHandler, createWebSocketTransportFactory, defaultWebSocketFactory, } from "./daemon-client-websocket-transport.js";
|
|
4
4
|
//# sourceMappingURL=daemon-client-transport.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { extractRelayMessage } from "./daemon-client-transport-utils.js";
|
|
1
2
|
export function defaultWebSocketFactory(url, options) {
|
|
2
3
|
const globalWs = globalThis.WebSocket;
|
|
3
4
|
if (!globalWs) {
|
|
@@ -40,10 +41,17 @@ export function createWebSocketTransportFactory(factory) {
|
|
|
40
41
|
onOpen: (handler) => bindWsHandler(ws, "open", handler),
|
|
41
42
|
onClose: (handler) => bindWsHandler(ws, "close", handler),
|
|
42
43
|
onError: (handler) => bindWsHandler(ws, "error", handler),
|
|
43
|
-
onMessage: (handler) =>
|
|
44
|
+
onMessage: (handler) => bindWsMessageHandler(ws, handler),
|
|
44
45
|
};
|
|
45
46
|
};
|
|
46
47
|
}
|
|
48
|
+
function bindWsMessageHandler(ws, handler) {
|
|
49
|
+
const listener = (...args) => {
|
|
50
|
+
const message = extractRelayMessage(args[0], typeof args[1] === "boolean" ? args[1] : undefined);
|
|
51
|
+
handler(message.data, message.isBinary);
|
|
52
|
+
};
|
|
53
|
+
return bindWsHandler(ws, "message", listener);
|
|
54
|
+
}
|
|
47
55
|
function bindTemporaryEarlyCloseErrorHandler(ws) {
|
|
48
56
|
const noop = () => { };
|
|
49
57
|
if (typeof ws.addEventListener === "function") {
|
package/dist/daemon-client.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import type { AgentAttentionNotificationPayload } from "@otto-code/protocol/agent-attention-notification";
|
|
1
2
|
import type { z } from "zod";
|
|
3
|
+
import type { ProjectGithubCloneProtocol } from "@otto-code/protocol/messages";
|
|
2
4
|
import { type ClientCapability } from "@otto-code/protocol/client-capabilities";
|
|
3
5
|
import { AgentRefreshedStatusPayloadSchema, CheckoutRenameBranchResponseSchema, RenameTerminalResponseSchema, RestartRequestedStatusPayloadSchema, ShutdownRequestedStatusPayloadSchema, DaemonUpdateResponseSchema, type ServerInfoStatusPayload } from "@otto-code/protocol/messages";
|
|
4
|
-
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, GitHubSearchResponse, GitHubSearchRequest, 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";
|
|
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";
|
|
5
7
|
import type { AgentPermissionRequest, AgentPermissionResponse, AgentPersistenceHandle, AgentProviderNotice, AgentProvider, AgentSessionConfig } from "@otto-code/protocol/agent-types";
|
|
6
8
|
import type { OrchestrationGraph, PromptTemplate, Run } from "@otto-code/protocol/orchestration";
|
|
7
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";
|
|
@@ -156,6 +158,12 @@ export interface CreateAgentRequestOptions extends AgentConfigOverrides {
|
|
|
156
158
|
personality?: CreateAgentRequestMessage["personality"];
|
|
157
159
|
env?: CreateAgentRequestMessage["env"];
|
|
158
160
|
workspaceId?: string;
|
|
161
|
+
/**
|
|
162
|
+
* Caller agent making this request. The daemon resolves the caller's own
|
|
163
|
+
* workspace and parentage from it, so a managed CLI run nests under its
|
|
164
|
+
* parent exactly as agent-scoped MCP creation does.
|
|
165
|
+
*/
|
|
166
|
+
callerAgentId?: string;
|
|
159
167
|
initialPrompt?: string;
|
|
160
168
|
clientMessageId?: string;
|
|
161
169
|
outputSchema?: Record<string, unknown>;
|
|
@@ -168,7 +176,7 @@ export interface CreateAgentRequestOptions extends AgentConfigOverrides {
|
|
|
168
176
|
requestId?: string;
|
|
169
177
|
labels?: Record<string, string>;
|
|
170
178
|
}
|
|
171
|
-
export interface CreateOttoWorktreeInput extends Pick<CreateOttoWorktreeRequest, "cwd" | "projectId" | "worktreeSlug" | "firstAgentContext" | "refName" | "action" | "githubPrNumber"> {
|
|
179
|
+
export interface CreateOttoWorktreeInput extends Pick<CreateOttoWorktreeRequest, "cwd" | "projectId" | "worktreeSlug" | "firstAgentContext" | "refName" | "action" | "githubPrNumber" | "checkoutSource"> {
|
|
172
180
|
}
|
|
173
181
|
type CheckoutStatusPayload = CheckoutStatusResponse["payload"];
|
|
174
182
|
type SubscribeCheckoutDiffPayload = Extract<SessionOutboundMessage, {
|
|
@@ -207,6 +215,23 @@ type StashListPayload = StashListResponse["payload"];
|
|
|
207
215
|
type ValidateBranchPayload = ValidateBranchResponse["payload"];
|
|
208
216
|
type BranchSuggestionsPayload = BranchSuggestionsResponse["payload"];
|
|
209
217
|
type GitHubSearchPayload = GitHubSearchResponse["payload"];
|
|
218
|
+
type CheckoutForgeGetCheckDetailsPayload = CheckoutForgeGetCheckDetailsResponse["payload"];
|
|
219
|
+
type CheckoutForgeSetAutoMergePayload = CheckoutForgeSetAutoMergeResponse["payload"];
|
|
220
|
+
export type ProjectCreateDirectoryPayload = ProjectCreateDirectoryResponse["payload"];
|
|
221
|
+
export type ProjectListPayload = Extract<SessionOutboundMessage, {
|
|
222
|
+
type: "project.list.response";
|
|
223
|
+
}>["payload"];
|
|
224
|
+
export type WorkspaceGithubSearchRepositoriesPayload = Extract<SessionOutboundMessage, {
|
|
225
|
+
type: "workspace.github.search_repositories.response";
|
|
226
|
+
}>["payload"];
|
|
227
|
+
export interface AgentAttentionRequiredNotification {
|
|
228
|
+
agentId: string;
|
|
229
|
+
reason: "finished" | "error" | "permission";
|
|
230
|
+
timestamp: string;
|
|
231
|
+
shouldNotify: boolean;
|
|
232
|
+
notification?: AgentAttentionNotificationPayload;
|
|
233
|
+
}
|
|
234
|
+
type ForgeSearchPayload = ForgeSearchResponse["payload"];
|
|
210
235
|
export type HostingSearchPayload = HostingSearchResponse["payload"];
|
|
211
236
|
export type HostingAuthStatusPayload = HostingAuthStatusResponse["payload"];
|
|
212
237
|
type DirectorySuggestionsPayload = DirectorySuggestionsResponse["payload"];
|
|
@@ -234,6 +259,8 @@ export interface FileReadResult {
|
|
|
234
259
|
* daemons older than v0.4.4. Absent means "not reported", not "LF".
|
|
235
260
|
*/
|
|
236
261
|
eol?: FileEol;
|
|
262
|
+
/** Opaque version tag for optimistic-concurrency writes; same caveat as eol. */
|
|
263
|
+
revision?: string;
|
|
237
264
|
}
|
|
238
265
|
export interface TextFileReadResult {
|
|
239
266
|
path: string;
|
|
@@ -254,6 +281,23 @@ export interface FileWriteOptions {
|
|
|
254
281
|
eol?: FileEol;
|
|
255
282
|
requestId?: string;
|
|
256
283
|
}
|
|
284
|
+
/**
|
|
285
|
+
* Bytes to a workspace path. Gated on `features.binaryFileWrite`. Unlike
|
|
286
|
+
* {@link FileWriteOptions} this carries no precondition: `overwrite` is the
|
|
287
|
+
* whole policy — see `FsFileWriteBinaryRequestSchema` for why a generated
|
|
288
|
+
* artifact has nothing to reconcile against.
|
|
289
|
+
*/
|
|
290
|
+
export interface FileWriteBinaryOptions {
|
|
291
|
+
cwd: string;
|
|
292
|
+
/** Workspace-relative, like every other file RPC. */
|
|
293
|
+
path: string;
|
|
294
|
+
/** Sent as file-transfer frames, not inside the request. */
|
|
295
|
+
bytes: Uint8Array | ArrayBuffer;
|
|
296
|
+
overwrite?: boolean;
|
|
297
|
+
requestId?: string;
|
|
298
|
+
/** Frame size, for tests that want to observe chunking. Defaults to 1 MB. */
|
|
299
|
+
chunkSize?: number;
|
|
300
|
+
}
|
|
257
301
|
/**
|
|
258
302
|
* The general file-mutation surface — what exists in a directory, rather than
|
|
259
303
|
* what is inside a file. Gated on `features.fileMutations`; there is no
|
|
@@ -541,7 +585,21 @@ export interface FetchAgentTimelineOptions {
|
|
|
541
585
|
requestId?: string;
|
|
542
586
|
timeout?: number;
|
|
543
587
|
}
|
|
588
|
+
export type ProviderSubagentListPayload = Extract<SessionOutboundMessage, {
|
|
589
|
+
type: "agent.provider_subagents.list.response";
|
|
590
|
+
}>["payload"];
|
|
591
|
+
export type ProviderSubagentTimelinePayload = Extract<SessionOutboundMessage, {
|
|
592
|
+
type: "agent.provider_subagents.timeline.get.response";
|
|
593
|
+
}>["payload"];
|
|
594
|
+
export interface FetchProviderSubagentTimelineOptions {
|
|
595
|
+
direction?: ProviderSubagentTimelinePayload["direction"];
|
|
596
|
+
cursor?: FetchAgentTimelineCursor;
|
|
597
|
+
limit?: number;
|
|
598
|
+
requestId?: string;
|
|
599
|
+
timeout?: number;
|
|
600
|
+
}
|
|
544
601
|
export interface AgentForkContextOptions {
|
|
602
|
+
boundaryCursor?: FetchAgentTimelineCursor;
|
|
545
603
|
boundaryMessageId?: string;
|
|
546
604
|
requestId?: string;
|
|
547
605
|
}
|
|
@@ -779,6 +837,9 @@ export interface WaitForFinishResult {
|
|
|
779
837
|
error: string | null;
|
|
780
838
|
lastMessage: string | null;
|
|
781
839
|
}
|
|
840
|
+
type ProjectGithubClonePayload = Extract<SessionOutboundMessage, {
|
|
841
|
+
type: "project.github.clone.response";
|
|
842
|
+
}>["payload"];
|
|
782
843
|
export declare class DaemonClient {
|
|
783
844
|
private config;
|
|
784
845
|
private transport;
|
|
@@ -801,6 +862,7 @@ export declare class DaemonClient {
|
|
|
801
862
|
private connectionState;
|
|
802
863
|
private checkoutDiffSubscriptions;
|
|
803
864
|
private terminalDirectorySubscriptions;
|
|
865
|
+
private fileSubscriptions;
|
|
804
866
|
private readonly terminalStreams;
|
|
805
867
|
private readonly scaffoldProgressListeners;
|
|
806
868
|
private pendingBinaryFileReads;
|
|
@@ -1091,6 +1153,55 @@ export declare class DaemonClient {
|
|
|
1091
1153
|
resumeAgent(handle: AgentPersistenceHandle, overrides?: Partial<AgentSessionConfig>): Promise<AgentSnapshotPayload>;
|
|
1092
1154
|
importAgent(input: ImportAgentInput): Promise<AgentSnapshotPayload>;
|
|
1093
1155
|
refreshAgent(agentId: string, requestId?: string): Promise<AgentRefreshedStatusPayload>;
|
|
1156
|
+
listProviderSubagents(parentAgentId: string, options?: {
|
|
1157
|
+
requestId?: string;
|
|
1158
|
+
timeout?: number;
|
|
1159
|
+
}): Promise<ProviderSubagentListPayload>;
|
|
1160
|
+
fetchProviderSubagentTimeline(parentAgentId: string, subagentId: string, options?: FetchProviderSubagentTimelineOptions): Promise<ProviderSubagentTimelinePayload>;
|
|
1161
|
+
checkoutForgeGetCheckDetails(input: {
|
|
1162
|
+
cwd: string;
|
|
1163
|
+
repoOwner?: string;
|
|
1164
|
+
repoName?: string;
|
|
1165
|
+
checkRunId?: number;
|
|
1166
|
+
workflowRunId?: number;
|
|
1167
|
+
changeRequestNumber?: number;
|
|
1168
|
+
}, requestId?: string): Promise<CheckoutForgeGetCheckDetailsPayload>;
|
|
1169
|
+
checkoutForgeSetAutoMerge(cwd: string, input: {
|
|
1170
|
+
enabled: true;
|
|
1171
|
+
method: CheckoutPrMergeMethod;
|
|
1172
|
+
} | {
|
|
1173
|
+
enabled: false;
|
|
1174
|
+
}, requestId?: string): Promise<CheckoutForgeSetAutoMergePayload>;
|
|
1175
|
+
createProjectDirectory(input: {
|
|
1176
|
+
parentPath: string;
|
|
1177
|
+
name: string;
|
|
1178
|
+
}, requestId?: string): Promise<ProjectCreateDirectoryPayload>;
|
|
1179
|
+
getCommitFileDiff(cwd: string, sha: string, path: string, requestId?: string): Promise<{
|
|
1180
|
+
file: ParsedDiffFile | null;
|
|
1181
|
+
}>;
|
|
1182
|
+
inspectWorkspaceRecovery(workspaceId: string, requestId?: string): Promise<WorkspaceRecoveryState>;
|
|
1183
|
+
listCheckoutCommits(cwd: string, requestId?: string): Promise<{
|
|
1184
|
+
baseRef: string | null;
|
|
1185
|
+
commits: CheckoutCommit[];
|
|
1186
|
+
}>;
|
|
1187
|
+
listProjects(requestId?: string): Promise<ProjectListPayload>;
|
|
1188
|
+
onAgentAttentionRequired(handler: (notification: AgentAttentionRequiredNotification) => void): () => void;
|
|
1189
|
+
restoreWorkspace(workspaceId: string, requestId?: string): Promise<void>;
|
|
1190
|
+
searchGithubRepositories(input: {
|
|
1191
|
+
query: string;
|
|
1192
|
+
limit?: number;
|
|
1193
|
+
}, requestId?: string): Promise<WorkspaceGithubSearchRepositoriesPayload>;
|
|
1194
|
+
setAgentTimelineSubscription(agentIds: string[]): Promise<void>;
|
|
1195
|
+
setWorkspacePinned(workspaceId: string, pinned: boolean, requestId?: string): Promise<{
|
|
1196
|
+
pinnedAt: string | null;
|
|
1197
|
+
}>;
|
|
1198
|
+
subscribeFile(input: {
|
|
1199
|
+
cwd: string;
|
|
1200
|
+
path: string;
|
|
1201
|
+
}, onUpdate: (version: FileVersion) => void): Promise<{
|
|
1202
|
+
initial: FileVersion;
|
|
1203
|
+
unsubscribe: () => void;
|
|
1204
|
+
}>;
|
|
1094
1205
|
fetchAgentTimeline(agentId: string, options?: FetchAgentTimelineOptions): Promise<FetchAgentTimelinePayload>;
|
|
1095
1206
|
buildAgentForkContext(agentId: string, options?: AgentForkContextOptions): Promise<AgentForkContextPayload>;
|
|
1096
1207
|
sendAgentMessage(agentId: string, text: string, options?: SendMessageOptions): Promise<SendAgentMessageResult>;
|
|
@@ -1118,7 +1229,7 @@ export declare class DaemonClient {
|
|
|
1118
1229
|
cancelled?: boolean;
|
|
1119
1230
|
}>;
|
|
1120
1231
|
setAgentMode(agentId: string, modeId: string): Promise<AgentProviderNotice | null>;
|
|
1121
|
-
setAgentModel(agentId: string, modelId: string | null): Promise<
|
|
1232
|
+
setAgentModel(agentId: string, modelId: string | null): Promise<AgentProviderNotice | null>;
|
|
1122
1233
|
setAgentFeature(agentId: string, featureId: string, value: unknown): Promise<void>;
|
|
1123
1234
|
setAgentThinkingOption(agentId: string, thinkingOptionId: string | null): Promise<AgentProviderNotice | null>;
|
|
1124
1235
|
/**
|
|
@@ -1276,6 +1387,12 @@ export declare class DaemonClient {
|
|
|
1276
1387
|
query?: string;
|
|
1277
1388
|
limit?: number;
|
|
1278
1389
|
}, requestId?: string): Promise<BranchSuggestionsPayload>;
|
|
1390
|
+
searchForge(options: {
|
|
1391
|
+
cwd: string;
|
|
1392
|
+
query: string;
|
|
1393
|
+
limit?: number;
|
|
1394
|
+
kinds?: ForgeSearchRequest["kinds"];
|
|
1395
|
+
}, requestId?: string): Promise<ForgeSearchPayload>;
|
|
1279
1396
|
searchGitHub(options: {
|
|
1280
1397
|
cwd: string;
|
|
1281
1398
|
query: string;
|
|
@@ -1307,8 +1424,36 @@ export declare class DaemonClient {
|
|
|
1307
1424
|
* save-precondition baseline (modifiedAt, eol, hash) alongside the content.
|
|
1308
1425
|
*/
|
|
1309
1426
|
readTextFile(cwd: string, path: string, requestId?: string): Promise<TextFileReadResult>;
|
|
1427
|
+
/**
|
|
1428
|
+
* The fs.file.write RPC: optimistic-concurrency writes keyed by an opaque
|
|
1429
|
+
* revision. The hash-keyed `writeFile` below is kept for callers
|
|
1430
|
+
* that have not moved over.
|
|
1431
|
+
*/
|
|
1432
|
+
writeFsFile(input: {
|
|
1433
|
+
cwd: string;
|
|
1434
|
+
path: string;
|
|
1435
|
+
content: string;
|
|
1436
|
+
expectedModifiedAt: string;
|
|
1437
|
+
expectedRevision?: string;
|
|
1438
|
+
}): Promise<FsFileWriteResult>;
|
|
1310
1439
|
/** Conditional save — see FileWriteRequestSchema for the no-clobber contract. */
|
|
1311
1440
|
writeFile(options: FileWriteOptions): Promise<FileWriteResult>;
|
|
1441
|
+
/**
|
|
1442
|
+
* Write bytes to a workspace file — the path for generated artifacts the
|
|
1443
|
+
* text write cannot carry (it refuses binary targets outright). Gated on
|
|
1444
|
+
* `features.binaryFileWrite`; there is no client-side substitute, because
|
|
1445
|
+
* the client never touches a workspace file on any platform.
|
|
1446
|
+
*
|
|
1447
|
+
* Shaped like {@link uploadFile}: the JSON request says where the bytes go
|
|
1448
|
+
* and how many to expect, then the bytes follow as file-transfer frames
|
|
1449
|
+
* correlated on the same `requestId`. The daemon answers at FileEnd.
|
|
1450
|
+
*/
|
|
1451
|
+
writeBinaryFile(options: FileWriteBinaryOptions): Promise<FsFileWriteBinaryResult>;
|
|
1452
|
+
/**
|
|
1453
|
+
* FileBegin, chunks, FileEnd. Synchronous through `sendBinaryFrame`, so the
|
|
1454
|
+
* frames leave in order and behind the JSON request that announced them.
|
|
1455
|
+
*/
|
|
1456
|
+
private sendFileTransfer;
|
|
1312
1457
|
/** Create an empty file or a directory. Never overwrites — see FileCreateResultSchema. */
|
|
1313
1458
|
createFileEntry(options: FileCreateOptions): Promise<FileCreateResult>;
|
|
1314
1459
|
/** Permanent delete — an unlink, not a move to any trash. */
|
|
@@ -1775,8 +1920,67 @@ export declare class DaemonClient {
|
|
|
1775
1920
|
private recordLivenessFailure;
|
|
1776
1921
|
private handleSessionMessage;
|
|
1777
1922
|
private resolveWaiters;
|
|
1923
|
+
private rejectWaitersForRequestId;
|
|
1778
1924
|
private clearWaiters;
|
|
1779
1925
|
private toEvent;
|
|
1780
1926
|
private waitForWithCancel;
|
|
1927
|
+
cloneGithubProject(input: {
|
|
1928
|
+
repo: string;
|
|
1929
|
+
targetDirectory: string;
|
|
1930
|
+
cloneProtocol?: ProjectGithubCloneProtocol;
|
|
1931
|
+
}, requestId?: string): Promise<ProjectGithubClonePayload>;
|
|
1932
|
+
/**
|
|
1933
|
+
* `includeDiscovered` also returns the Scripts the workspace's own project
|
|
1934
|
+
* files declare (package.json scripts today), each tagged with its `source`.
|
|
1935
|
+
* Gate it on `server_info.features.workspaceScriptDiscovery` — an older
|
|
1936
|
+
* daemon ignores the flag and answers with the otto.json list only.
|
|
1937
|
+
*/
|
|
1938
|
+
listWorkspaceScripts(workspaceId: string, options?: {
|
|
1939
|
+
includeDiscovered?: boolean;
|
|
1940
|
+
requestId?: string;
|
|
1941
|
+
}): Promise<Extract<SessionOutboundMessage, {
|
|
1942
|
+
type: "workspace.script.list.response";
|
|
1943
|
+
}>["payload"]>;
|
|
1944
|
+
startWorkspaceScriptWithStatus(workspaceId: string, scriptName: string, requestId?: string): Promise<Extract<SessionOutboundMessage, {
|
|
1945
|
+
type: "workspace.script.start.response";
|
|
1946
|
+
}>["payload"]>;
|
|
1947
|
+
stopWorkspaceScript(workspaceId: string, scriptName: string, requestId?: string): Promise<Extract<SessionOutboundMessage, {
|
|
1948
|
+
type: "workspace.script.stop.response";
|
|
1949
|
+
}>["payload"]>;
|
|
1950
|
+
connectHub(hubUrl: string, token: string, requestId?: string): Promise<{
|
|
1951
|
+
requestId: string;
|
|
1952
|
+
status: {
|
|
1953
|
+
state: "connecting" | "connected" | "not_connected" | "reconnecting" | "disconnecting" | "revoked";
|
|
1954
|
+
daemonId: string | null;
|
|
1955
|
+
hubOrigin: string | null;
|
|
1956
|
+
scopes: string[];
|
|
1957
|
+
connectedAt: string | null;
|
|
1958
|
+
lastError: string | null;
|
|
1959
|
+
};
|
|
1960
|
+
}>;
|
|
1961
|
+
getHubStatus(requestId?: string): Promise<{
|
|
1962
|
+
requestId: string;
|
|
1963
|
+
status: {
|
|
1964
|
+
state: "connecting" | "connected" | "not_connected" | "reconnecting" | "disconnecting" | "revoked";
|
|
1965
|
+
daemonId: string | null;
|
|
1966
|
+
hubOrigin: string | null;
|
|
1967
|
+
scopes: string[];
|
|
1968
|
+
connectedAt: string | null;
|
|
1969
|
+
lastError: string | null;
|
|
1970
|
+
};
|
|
1971
|
+
}>;
|
|
1972
|
+
disconnectHub(force?: boolean, requestId?: string): Promise<{
|
|
1973
|
+
requestId: string;
|
|
1974
|
+
status: {
|
|
1975
|
+
state: "connecting" | "connected" | "not_connected" | "reconnecting" | "disconnecting" | "revoked";
|
|
1976
|
+
daemonId: string | null;
|
|
1977
|
+
hubOrigin: string | null;
|
|
1978
|
+
scopes: string[];
|
|
1979
|
+
connectedAt: string | null;
|
|
1980
|
+
lastError: string | null;
|
|
1981
|
+
};
|
|
1982
|
+
warning?: string | undefined;
|
|
1983
|
+
}>;
|
|
1984
|
+
private requireHubRelationshipSupport;
|
|
1781
1985
|
}
|
|
1782
1986
|
//# sourceMappingURL=daemon-client.d.ts.map
|
package/dist/daemon-client.js
CHANGED
|
@@ -57,6 +57,16 @@ class DaemonRpcError extends Error {
|
|
|
57
57
|
this.code = params.code;
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
+
class DaemonProtocolError extends Error {
|
|
61
|
+
constructor(identity) {
|
|
62
|
+
const responseLabel = identity.responseType ?? "unknown response";
|
|
63
|
+
super(`Response validation failed for ${responseLabel}`);
|
|
64
|
+
this.code = "invalid_response";
|
|
65
|
+
this.name = "DaemonProtocolError";
|
|
66
|
+
this.requestId = identity.requestId;
|
|
67
|
+
this.responseType = identity.responseType;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
60
70
|
class PingTimeoutError extends Error {
|
|
61
71
|
constructor(timeoutMs) {
|
|
62
72
|
super(`Ping timed out (${timeoutMs}ms)`);
|
|
@@ -64,6 +74,40 @@ class PingTimeoutError extends Error {
|
|
|
64
74
|
this.name = "PingTimeoutError";
|
|
65
75
|
}
|
|
66
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Pull the request correlation out of a frame that failed schema validation.
|
|
79
|
+
* Reads only the envelope and `payload.requestId`, which is exactly the part a
|
|
80
|
+
* malformed response still gets right, so the waiting caller can be failed with
|
|
81
|
+
* a real reason rather than left to time out.
|
|
82
|
+
*/
|
|
83
|
+
function extractCorrelatedResponseIdentity(input) {
|
|
84
|
+
if (!input || typeof input !== "object") {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const envelope = input;
|
|
88
|
+
if (envelope.type !== "session" || !envelope.message || typeof envelope.message !== "object") {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const message = envelope.message;
|
|
92
|
+
if (typeof message.type !== "string" ||
|
|
93
|
+
!(message.type === "rpc_error" ||
|
|
94
|
+
message.type.endsWith("_response") ||
|
|
95
|
+
message.type.endsWith(".response") ||
|
|
96
|
+
message.type.endsWith("/response"))) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
if (!message.payload || typeof message.payload !== "object") {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const payload = message.payload;
|
|
103
|
+
if (typeof payload.requestId !== "string") {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
requestId: payload.requestId,
|
|
108
|
+
responseType: message.type,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
67
111
|
function toTimeoutError(error, label, timeoutMs) {
|
|
68
112
|
if (error instanceof PingTimeoutError) {
|
|
69
113
|
return new Error(`${label} timed out (${timeoutMs}ms)`);
|
|
@@ -120,6 +164,7 @@ function legacyExplorerFileToBytes(file) {
|
|
|
120
164
|
kind: file.kind,
|
|
121
165
|
modifiedAt: file.modifiedAt,
|
|
122
166
|
eol: file.eol,
|
|
167
|
+
revision: file.revision,
|
|
123
168
|
};
|
|
124
169
|
}
|
|
125
170
|
function binaryFileKind(mime, encoding) {
|
|
@@ -180,6 +225,9 @@ function unwrapBrainJob(payload) {
|
|
|
180
225
|
}
|
|
181
226
|
return payload.job;
|
|
182
227
|
}
|
|
228
|
+
// A repo clone can take minutes on a large history, so it gets its own budget
|
|
229
|
+
// rather than the default request timeout.
|
|
230
|
+
const PROJECT_GITHUB_CLONE_TIMEOUT_MS = 5 * 60 * 1000;
|
|
183
231
|
export class DaemonClient {
|
|
184
232
|
constructor(config) {
|
|
185
233
|
this.config = config;
|
|
@@ -203,6 +251,7 @@ export class DaemonClient {
|
|
|
203
251
|
this.connectionState = { status: "idle" };
|
|
204
252
|
this.checkoutDiffSubscriptions = new Map();
|
|
205
253
|
this.terminalDirectorySubscriptions = new Map();
|
|
254
|
+
this.fileSubscriptions = new Map();
|
|
206
255
|
this.terminalStreams = new TerminalStreamRouter();
|
|
207
256
|
// requestId -> progress listener for an in-flight project.scaffold.request.
|
|
208
257
|
// Entries are always removed in scaffoldProject's finally block.
|
|
@@ -670,7 +719,7 @@ export class DaemonClient {
|
|
|
670
719
|
return null;
|
|
671
720
|
}
|
|
672
721
|
return { kind: "ok", value };
|
|
673
|
-
}, timeout, params.options);
|
|
722
|
+
}, timeout, { ...params.options, requestId: params.requestId });
|
|
674
723
|
try {
|
|
675
724
|
await this.sendSessionMessageOrThrow(params.message);
|
|
676
725
|
}
|
|
@@ -1276,6 +1325,7 @@ export class DaemonClient {
|
|
|
1276
1325
|
...(options.personality ? { personality: options.personality } : {}),
|
|
1277
1326
|
...(options.env ? { env: options.env } : {}),
|
|
1278
1327
|
...(options.workspaceId !== undefined ? { workspaceId: options.workspaceId } : {}),
|
|
1328
|
+
...(options.callerAgentId !== undefined ? { callerAgentId: options.callerAgentId } : {}),
|
|
1279
1329
|
...(options.initialPrompt ? { initialPrompt: options.initialPrompt } : {}),
|
|
1280
1330
|
...(options.clientMessageId ? { clientMessageId: options.clientMessageId } : {}),
|
|
1281
1331
|
...(options.outputSchema ? { outputSchema: options.outputSchema } : {}),
|
|
@@ -1853,6 +1903,266 @@ export class DaemonClient {
|
|
|
1853
1903
|
},
|
|
1854
1904
|
});
|
|
1855
1905
|
}
|
|
1906
|
+
async listProviderSubagents(parentAgentId, options = {}) {
|
|
1907
|
+
const requestId = this.createRequestId(options.requestId);
|
|
1908
|
+
const message = SessionInboundMessageSchema.parse({
|
|
1909
|
+
type: "agent.provider_subagents.list.request",
|
|
1910
|
+
parentAgentId,
|
|
1911
|
+
requestId,
|
|
1912
|
+
});
|
|
1913
|
+
const payload = await this.sendRequest({
|
|
1914
|
+
requestId,
|
|
1915
|
+
message,
|
|
1916
|
+
timeout: options.timeout,
|
|
1917
|
+
options: { skipQueue: true },
|
|
1918
|
+
select: (response) => response.type === "agent.provider_subagents.list.response" &&
|
|
1919
|
+
response.payload.requestId === requestId
|
|
1920
|
+
? response.payload
|
|
1921
|
+
: null,
|
|
1922
|
+
});
|
|
1923
|
+
if (payload.error) {
|
|
1924
|
+
throw new Error(payload.error);
|
|
1925
|
+
}
|
|
1926
|
+
return payload;
|
|
1927
|
+
}
|
|
1928
|
+
async fetchProviderSubagentTimeline(parentAgentId, subagentId, options = {}) {
|
|
1929
|
+
const requestId = this.createRequestId(options.requestId);
|
|
1930
|
+
const message = SessionInboundMessageSchema.parse({
|
|
1931
|
+
type: "agent.provider_subagents.timeline.get.request",
|
|
1932
|
+
parentAgentId,
|
|
1933
|
+
subagentId,
|
|
1934
|
+
requestId,
|
|
1935
|
+
...(options.direction ? { direction: options.direction } : {}),
|
|
1936
|
+
...(options.cursor ? { cursor: options.cursor } : {}),
|
|
1937
|
+
...(typeof options.limit === "number" ? { limit: options.limit } : {}),
|
|
1938
|
+
});
|
|
1939
|
+
const payload = await this.sendRequest({
|
|
1940
|
+
requestId,
|
|
1941
|
+
message,
|
|
1942
|
+
timeout: options.timeout,
|
|
1943
|
+
options: { skipQueue: true },
|
|
1944
|
+
select: (response) => response.type === "agent.provider_subagents.timeline.get.response" &&
|
|
1945
|
+
response.payload.requestId === requestId
|
|
1946
|
+
? response.payload
|
|
1947
|
+
: null,
|
|
1948
|
+
});
|
|
1949
|
+
if (payload.error) {
|
|
1950
|
+
throw new Error(payload.error);
|
|
1951
|
+
}
|
|
1952
|
+
return payload;
|
|
1953
|
+
}
|
|
1954
|
+
async checkoutForgeGetCheckDetails(input, requestId) {
|
|
1955
|
+
return this.sendNamespacedCorrelatedSessionRequest({
|
|
1956
|
+
requestId,
|
|
1957
|
+
message: {
|
|
1958
|
+
type: "checkout.forge.get_check_details.request",
|
|
1959
|
+
cwd: input.cwd,
|
|
1960
|
+
repoOwner: input.repoOwner,
|
|
1961
|
+
repoName: input.repoName,
|
|
1962
|
+
checkRunId: input.checkRunId,
|
|
1963
|
+
workflowRunId: input.workflowRunId,
|
|
1964
|
+
changeRequestNumber: input.changeRequestNumber,
|
|
1965
|
+
},
|
|
1966
|
+
timeout: 60000,
|
|
1967
|
+
});
|
|
1968
|
+
}
|
|
1969
|
+
async checkoutForgeSetAutoMerge(cwd, input, requestId) {
|
|
1970
|
+
return this.sendNamespacedCorrelatedSessionRequest({
|
|
1971
|
+
requestId,
|
|
1972
|
+
message: {
|
|
1973
|
+
type: "checkout.forge.set_auto_merge.request",
|
|
1974
|
+
cwd,
|
|
1975
|
+
enabled: input.enabled,
|
|
1976
|
+
...(input.enabled ? { mergeMethod: input.method } : {}),
|
|
1977
|
+
},
|
|
1978
|
+
timeout: 60000,
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1981
|
+
async createProjectDirectory(input, requestId) {
|
|
1982
|
+
return this.sendNamespacedCorrelatedSessionRequest({
|
|
1983
|
+
requestId,
|
|
1984
|
+
message: {
|
|
1985
|
+
type: "project.create_directory.request",
|
|
1986
|
+
parentPath: input.parentPath,
|
|
1987
|
+
name: input.name,
|
|
1988
|
+
},
|
|
1989
|
+
});
|
|
1990
|
+
}
|
|
1991
|
+
async getCommitFileDiff(cwd, sha, path, requestId) {
|
|
1992
|
+
const payload = await this.sendNamespacedCorrelatedSessionRequest({
|
|
1993
|
+
requestId,
|
|
1994
|
+
message: {
|
|
1995
|
+
type: "checkout.commits.file_diff.request",
|
|
1996
|
+
cwd,
|
|
1997
|
+
sha,
|
|
1998
|
+
path,
|
|
1999
|
+
},
|
|
2000
|
+
timeout: 60000,
|
|
2001
|
+
});
|
|
2002
|
+
if (payload.error) {
|
|
2003
|
+
throw new Error(payload.error.message);
|
|
2004
|
+
}
|
|
2005
|
+
return { file: payload.file };
|
|
2006
|
+
}
|
|
2007
|
+
async inspectWorkspaceRecovery(workspaceId, requestId) {
|
|
2008
|
+
const payload = await this.sendNamespacedCorrelatedSessionRequest({
|
|
2009
|
+
requestId,
|
|
2010
|
+
message: {
|
|
2011
|
+
type: "workspace.recovery.inspect.request",
|
|
2012
|
+
workspaceId,
|
|
2013
|
+
},
|
|
2014
|
+
});
|
|
2015
|
+
return payload.state;
|
|
2016
|
+
}
|
|
2017
|
+
async listCheckoutCommits(cwd, requestId) {
|
|
2018
|
+
const payload = await this.sendNamespacedCorrelatedSessionRequest({
|
|
2019
|
+
requestId,
|
|
2020
|
+
message: {
|
|
2021
|
+
type: "checkout.commits.list.request",
|
|
2022
|
+
cwd,
|
|
2023
|
+
},
|
|
2024
|
+
timeout: 60000,
|
|
2025
|
+
});
|
|
2026
|
+
if (payload.error) {
|
|
2027
|
+
throw new Error(payload.error.message);
|
|
2028
|
+
}
|
|
2029
|
+
return { baseRef: payload.baseRef, commits: payload.commits };
|
|
2030
|
+
}
|
|
2031
|
+
async listProjects(requestId) {
|
|
2032
|
+
const resolvedRequestId = this.createRequestId(requestId);
|
|
2033
|
+
const message = SessionInboundMessageSchema.parse({
|
|
2034
|
+
type: "project.list.request",
|
|
2035
|
+
requestId: resolvedRequestId,
|
|
2036
|
+
});
|
|
2037
|
+
return this.sendRequest({
|
|
2038
|
+
requestId: resolvedRequestId,
|
|
2039
|
+
message,
|
|
2040
|
+
options: { skipQueue: true },
|
|
2041
|
+
select: (msg) => {
|
|
2042
|
+
if (msg.type !== "project.list.response")
|
|
2043
|
+
return null;
|
|
2044
|
+
if (msg.payload.requestId !== resolvedRequestId)
|
|
2045
|
+
return null;
|
|
2046
|
+
return msg.payload;
|
|
2047
|
+
},
|
|
2048
|
+
});
|
|
2049
|
+
}
|
|
2050
|
+
onAgentAttentionRequired(handler) {
|
|
2051
|
+
const unsubscribeLegacy = this.on("agent_stream", (message) => {
|
|
2052
|
+
if (message.payload.event.type !== "attention_required") {
|
|
2053
|
+
return;
|
|
2054
|
+
}
|
|
2055
|
+
const event = message.payload.event;
|
|
2056
|
+
handler({
|
|
2057
|
+
agentId: message.payload.agentId,
|
|
2058
|
+
reason: event.reason,
|
|
2059
|
+
timestamp: event.timestamp,
|
|
2060
|
+
shouldNotify: event.shouldNotify,
|
|
2061
|
+
...(event.notification ? { notification: event.notification } : {}),
|
|
2062
|
+
});
|
|
2063
|
+
});
|
|
2064
|
+
const unsubscribeDedicated = this.on("agent_attention_required", (message) => {
|
|
2065
|
+
handler(message.payload);
|
|
2066
|
+
});
|
|
2067
|
+
return () => {
|
|
2068
|
+
unsubscribeLegacy();
|
|
2069
|
+
unsubscribeDedicated();
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
async restoreWorkspace(workspaceId, requestId) {
|
|
2073
|
+
const payload = await this.sendNamespacedCorrelatedSessionRequest({
|
|
2074
|
+
requestId,
|
|
2075
|
+
message: {
|
|
2076
|
+
type: "workspace.recovery.restore.request",
|
|
2077
|
+
workspaceId,
|
|
2078
|
+
},
|
|
2079
|
+
timeout: 150000,
|
|
2080
|
+
});
|
|
2081
|
+
if (!payload.accepted) {
|
|
2082
|
+
throw new Error(payload.error ?? "Workspace recovery was rejected by the host");
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
async searchGithubRepositories(input, requestId) {
|
|
2086
|
+
return this.sendNamespacedCorrelatedSessionRequest({
|
|
2087
|
+
requestId,
|
|
2088
|
+
message: {
|
|
2089
|
+
type: "workspace.github.search_repositories.request",
|
|
2090
|
+
query: input.query,
|
|
2091
|
+
limit: input.limit,
|
|
2092
|
+
},
|
|
2093
|
+
});
|
|
2094
|
+
}
|
|
2095
|
+
async setAgentTimelineSubscription(agentIds) {
|
|
2096
|
+
// COMPAT(selectiveAgentTimeline): added in v0.1.106. Old daemons keep their
|
|
2097
|
+
// legacy global stream and do not understand this RPC. Remove after
|
|
2098
|
+
// 2027-01-12 once the supported daemon floor is >= v0.1.106.
|
|
2099
|
+
if (!this.lastServerInfoMessage?.features?.selectiveAgentTimeline) {
|
|
2100
|
+
return;
|
|
2101
|
+
}
|
|
2102
|
+
const requestId = this.createRequestId();
|
|
2103
|
+
const normalizedAgentIds = [...new Set(agentIds)].sort();
|
|
2104
|
+
const message = SessionInboundMessageSchema.parse({
|
|
2105
|
+
type: "agent.timeline.set_subscription.request",
|
|
2106
|
+
agentIds: normalizedAgentIds,
|
|
2107
|
+
requestId,
|
|
2108
|
+
});
|
|
2109
|
+
await this.sendRequest({
|
|
2110
|
+
requestId,
|
|
2111
|
+
message,
|
|
2112
|
+
options: { skipQueue: true },
|
|
2113
|
+
select: (response) => {
|
|
2114
|
+
if (response.type !== "agent.timeline.set_subscription.response") {
|
|
2115
|
+
return null;
|
|
2116
|
+
}
|
|
2117
|
+
return response.payload.requestId === requestId ? response.payload : null;
|
|
2118
|
+
},
|
|
2119
|
+
});
|
|
2120
|
+
}
|
|
2121
|
+
async setWorkspacePinned(workspaceId, pinned, requestId) {
|
|
2122
|
+
const payload = await this.sendCorrelatedSessionRequest({
|
|
2123
|
+
requestId,
|
|
2124
|
+
message: {
|
|
2125
|
+
type: "workspace.pin.set.request",
|
|
2126
|
+
workspaceId,
|
|
2127
|
+
pinned,
|
|
2128
|
+
},
|
|
2129
|
+
responseType: "workspace.pin.set.response",
|
|
2130
|
+
});
|
|
2131
|
+
if (!payload.accepted) {
|
|
2132
|
+
throw new Error(payload.error ?? "setWorkspacePinned rejected");
|
|
2133
|
+
}
|
|
2134
|
+
return { pinnedAt: payload.pinnedAt };
|
|
2135
|
+
}
|
|
2136
|
+
async subscribeFile(input, onUpdate) {
|
|
2137
|
+
const subscriptionId = this.createRequestId();
|
|
2138
|
+
this.fileSubscriptions.set(subscriptionId, { ...input, onUpdate });
|
|
2139
|
+
try {
|
|
2140
|
+
const payload = await this.sendCorrelatedSessionRequest({
|
|
2141
|
+
message: {
|
|
2142
|
+
type: "fs.file.subscribe.request",
|
|
2143
|
+
cwd: input.cwd,
|
|
2144
|
+
path: input.path,
|
|
2145
|
+
subscriptionId,
|
|
2146
|
+
},
|
|
2147
|
+
responseType: "fs.file.subscribe.response",
|
|
2148
|
+
});
|
|
2149
|
+
return {
|
|
2150
|
+
initial: payload.initial,
|
|
2151
|
+
unsubscribe: () => {
|
|
2152
|
+
if (!this.fileSubscriptions.delete(subscriptionId))
|
|
2153
|
+
return;
|
|
2154
|
+
void this.sendCorrelatedSessionRequest({
|
|
2155
|
+
message: { type: "fs.file.unsubscribe.request", subscriptionId },
|
|
2156
|
+
responseType: "fs.file.unsubscribe.response",
|
|
2157
|
+
}).catch(() => undefined);
|
|
2158
|
+
},
|
|
2159
|
+
};
|
|
2160
|
+
}
|
|
2161
|
+
catch (error) {
|
|
2162
|
+
this.fileSubscriptions.delete(subscriptionId);
|
|
2163
|
+
throw error;
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
1856
2166
|
async fetchAgentTimeline(agentId, options = {}) {
|
|
1857
2167
|
const resolvedRequestId = this.createRequestId(options.requestId);
|
|
1858
2168
|
const message = SessionInboundMessageSchema.parse({
|
|
@@ -1890,6 +2200,7 @@ export class DaemonClient {
|
|
|
1890
2200
|
type: "agent.fork_context.request",
|
|
1891
2201
|
agentId,
|
|
1892
2202
|
requestId: resolvedRequestId,
|
|
2203
|
+
...(options.boundaryCursor ? { boundaryCursor: options.boundaryCursor } : {}),
|
|
1893
2204
|
...(options.boundaryMessageId ? { boundaryMessageId: options.boundaryMessageId } : {}),
|
|
1894
2205
|
});
|
|
1895
2206
|
const payload = await this.sendRequest({
|
|
@@ -2095,6 +2406,13 @@ export class DaemonClient {
|
|
|
2095
2406
|
return msg.payload;
|
|
2096
2407
|
},
|
|
2097
2408
|
});
|
|
2409
|
+
// A refused cancellation comes back as an error payload, not a rejected
|
|
2410
|
+
// frame. Dropping it reported Stop as successful while the provider was
|
|
2411
|
+
// still running and still spending tokens — the exact outcome the daemon
|
|
2412
|
+
// refuses the cancel to avoid.
|
|
2413
|
+
if (payload.error) {
|
|
2414
|
+
throw new Error(payload.error);
|
|
2415
|
+
}
|
|
2098
2416
|
// Absent ⇒ old daemon that doesn't report whether a run was interrupted.
|
|
2099
2417
|
return payload.cancelled !== undefined ? { cancelled: payload.cancelled } : {};
|
|
2100
2418
|
}
|
|
@@ -2150,6 +2468,7 @@ export class DaemonClient {
|
|
|
2150
2468
|
if (!payload.accepted) {
|
|
2151
2469
|
throw new Error(payload.error ?? "setAgentModel rejected");
|
|
2152
2470
|
}
|
|
2471
|
+
return payload.notice ?? null;
|
|
2153
2472
|
}
|
|
2154
2473
|
async setAgentFeature(agentId, featureId, value) {
|
|
2155
2474
|
const requestId = this.createRequestId();
|
|
@@ -3049,6 +3368,20 @@ export class DaemonClient {
|
|
|
3049
3368
|
responseType: "branch_suggestions_response",
|
|
3050
3369
|
});
|
|
3051
3370
|
}
|
|
3371
|
+
async searchForge(options, requestId) {
|
|
3372
|
+
return this.sendCorrelatedSessionRequest({
|
|
3373
|
+
requestId,
|
|
3374
|
+
message: {
|
|
3375
|
+
type: "forge.search.request",
|
|
3376
|
+
cwd: options.cwd,
|
|
3377
|
+
query: options.query,
|
|
3378
|
+
limit: options.limit,
|
|
3379
|
+
kinds: options.kinds,
|
|
3380
|
+
},
|
|
3381
|
+
responseType: "forge.search.response",
|
|
3382
|
+
timeout: 15000,
|
|
3383
|
+
});
|
|
3384
|
+
}
|
|
3052
3385
|
async searchGitHub(options, requestId) {
|
|
3053
3386
|
return this.sendCorrelatedSessionRequest({
|
|
3054
3387
|
requestId,
|
|
@@ -3183,6 +3516,18 @@ export class DaemonClient {
|
|
|
3183
3516
|
hash: file.hash ?? null,
|
|
3184
3517
|
};
|
|
3185
3518
|
}
|
|
3519
|
+
/**
|
|
3520
|
+
* The fs.file.write RPC: optimistic-concurrency writes keyed by an opaque
|
|
3521
|
+
* revision. The hash-keyed `writeFile` below is kept for callers
|
|
3522
|
+
* that have not moved over.
|
|
3523
|
+
*/
|
|
3524
|
+
async writeFsFile(input) {
|
|
3525
|
+
const payload = await this.sendCorrelatedSessionRequest({
|
|
3526
|
+
message: { type: "fs.file.write.request", ...input },
|
|
3527
|
+
responseType: "fs.file.write.response",
|
|
3528
|
+
});
|
|
3529
|
+
return payload.result;
|
|
3530
|
+
}
|
|
3186
3531
|
/** Conditional save — see FileWriteRequestSchema for the no-clobber contract. */
|
|
3187
3532
|
async writeFile(options) {
|
|
3188
3533
|
const payload = await this.sendCorrelatedSessionRequest({
|
|
@@ -3201,6 +3546,73 @@ export class DaemonClient {
|
|
|
3201
3546
|
});
|
|
3202
3547
|
return payload.result;
|
|
3203
3548
|
}
|
|
3549
|
+
/**
|
|
3550
|
+
* Write bytes to a workspace file — the path for generated artifacts the
|
|
3551
|
+
* text write cannot carry (it refuses binary targets outright). Gated on
|
|
3552
|
+
* `features.binaryFileWrite`; there is no client-side substitute, because
|
|
3553
|
+
* the client never touches a workspace file on any platform.
|
|
3554
|
+
*
|
|
3555
|
+
* Shaped like {@link uploadFile}: the JSON request says where the bytes go
|
|
3556
|
+
* and how many to expect, then the bytes follow as file-transfer frames
|
|
3557
|
+
* correlated on the same `requestId`. The daemon answers at FileEnd.
|
|
3558
|
+
*/
|
|
3559
|
+
async writeBinaryFile(options) {
|
|
3560
|
+
const bytes = asUint8Array(options.bytes);
|
|
3561
|
+
if (!bytes) {
|
|
3562
|
+
throw new Error("File bytes are required.");
|
|
3563
|
+
}
|
|
3564
|
+
const resolvedRequestId = this.createRequestId(options.requestId);
|
|
3565
|
+
const responsePromise = this.sendCorrelatedSessionRequest({
|
|
3566
|
+
requestId: resolvedRequestId,
|
|
3567
|
+
message: {
|
|
3568
|
+
type: "fs.file.write_binary.request",
|
|
3569
|
+
cwd: options.cwd,
|
|
3570
|
+
path: options.path,
|
|
3571
|
+
size: bytes.byteLength,
|
|
3572
|
+
overwrite: options.overwrite,
|
|
3573
|
+
},
|
|
3574
|
+
responseType: "fs.file.write_binary.response",
|
|
3575
|
+
});
|
|
3576
|
+
this.sendFileTransfer({
|
|
3577
|
+
requestId: resolvedRequestId,
|
|
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.
|
|
3581
|
+
mime: "application/octet-stream",
|
|
3582
|
+
chunkSize: options.chunkSize,
|
|
3583
|
+
});
|
|
3584
|
+
const payload = await responsePromise;
|
|
3585
|
+
return payload.result;
|
|
3586
|
+
}
|
|
3587
|
+
/**
|
|
3588
|
+
* FileBegin, chunks, FileEnd. Synchronous through `sendBinaryFrame`, so the
|
|
3589
|
+
* frames leave in order and behind the JSON request that announced them.
|
|
3590
|
+
*/
|
|
3591
|
+
sendFileTransfer(input) {
|
|
3592
|
+
this.sendBinaryFrame(encodeFileTransferFrame({
|
|
3593
|
+
opcode: FileTransferOpcode.FileBegin,
|
|
3594
|
+
requestId: input.requestId,
|
|
3595
|
+
metadata: {
|
|
3596
|
+
mime: input.mime,
|
|
3597
|
+
size: input.bytes.byteLength,
|
|
3598
|
+
encoding: "binary",
|
|
3599
|
+
modifiedAt: input.modifiedAt ?? new Date().toISOString(),
|
|
3600
|
+
...(input.fileName ? { fileName: input.fileName } : {}),
|
|
3601
|
+
},
|
|
3602
|
+
}));
|
|
3603
|
+
const chunkSize = input.chunkSize ?? 1024 * 1024;
|
|
3604
|
+
for (let offset = 0; offset < input.bytes.byteLength; offset += chunkSize) {
|
|
3605
|
+
this.sendBinaryFrame(encodeFileTransferFrame({
|
|
3606
|
+
opcode: FileTransferOpcode.FileChunk,
|
|
3607
|
+
requestId: input.requestId,
|
|
3608
|
+
payload: input.bytes.subarray(offset, Math.min(offset + chunkSize, input.bytes.byteLength)),
|
|
3609
|
+
}));
|
|
3610
|
+
}
|
|
3611
|
+
this.sendBinaryFrame(encodeFileTransferFrame({
|
|
3612
|
+
opcode: FileTransferOpcode.FileEnd,
|
|
3613
|
+
requestId: input.requestId,
|
|
3614
|
+
}));
|
|
3615
|
+
}
|
|
3204
3616
|
/** Create an empty file or a directory. Never overwrites — see FileCreateResultSchema. */
|
|
3205
3617
|
async createFileEntry(options) {
|
|
3206
3618
|
const payload = await this.sendCorrelatedSessionRequest({
|
|
@@ -3606,7 +4018,7 @@ export class DaemonClient {
|
|
|
3606
4018
|
* torn down when the last disposer runs; events fan out to every caller.
|
|
3607
4019
|
*/
|
|
3608
4020
|
watchFile(cwd, path, onEvent) {
|
|
3609
|
-
const key = `${cwd}
|
|
4021
|
+
const key = `${cwd}${path}`;
|
|
3610
4022
|
const offMessage = this.on("file.watch.event", (message) => {
|
|
3611
4023
|
if (message.type !== "file.watch.event") {
|
|
3612
4024
|
return;
|
|
@@ -3665,29 +4077,14 @@ export class DaemonClient {
|
|
|
3665
4077
|
responseType: "file.upload.response",
|
|
3666
4078
|
options: { skipQueue: true },
|
|
3667
4079
|
});
|
|
3668
|
-
this.
|
|
3669
|
-
opcode: FileTransferOpcode.FileBegin,
|
|
3670
|
-
requestId: resolvedRequestId,
|
|
3671
|
-
metadata: {
|
|
3672
|
-
mime: input.mimeType,
|
|
3673
|
-
size: bytes.byteLength,
|
|
3674
|
-
encoding: "binary",
|
|
3675
|
-
modifiedAt,
|
|
3676
|
-
fileName: input.fileName,
|
|
3677
|
-
},
|
|
3678
|
-
}));
|
|
3679
|
-
const chunkSize = input.chunkSize ?? 1024 * 1024;
|
|
3680
|
-
for (let offset = 0; offset < bytes.byteLength; offset += chunkSize) {
|
|
3681
|
-
this.sendBinaryFrame(encodeFileTransferFrame({
|
|
3682
|
-
opcode: FileTransferOpcode.FileChunk,
|
|
3683
|
-
requestId: resolvedRequestId,
|
|
3684
|
-
payload: bytes.subarray(offset, Math.min(offset + chunkSize, bytes.byteLength)),
|
|
3685
|
-
}));
|
|
3686
|
-
}
|
|
3687
|
-
this.sendBinaryFrame(encodeFileTransferFrame({
|
|
3688
|
-
opcode: FileTransferOpcode.FileEnd,
|
|
4080
|
+
this.sendFileTransfer({
|
|
3689
4081
|
requestId: resolvedRequestId,
|
|
3690
|
-
|
|
4082
|
+
bytes,
|
|
4083
|
+
mime: input.mimeType,
|
|
4084
|
+
fileName: input.fileName,
|
|
4085
|
+
modifiedAt,
|
|
4086
|
+
...(input.chunkSize === undefined ? {} : { chunkSize: input.chunkSize }),
|
|
4087
|
+
});
|
|
3691
4088
|
return responsePromise;
|
|
3692
4089
|
}
|
|
3693
4090
|
async requestDownloadToken(cwd, path, requestId) {
|
|
@@ -5109,6 +5506,10 @@ export class DaemonClient {
|
|
|
5109
5506
|
[CLIENT_CAPS.customModeIcons]: true,
|
|
5110
5507
|
[CLIENT_CAPS.reasoningMergeEnum]: true,
|
|
5111
5508
|
[CLIENT_CAPS.terminalReflowableSnapshot]: true,
|
|
5509
|
+
[CLIENT_CAPS.providerSubagents]: true,
|
|
5510
|
+
// The daemon gates project.updated.notification on this (session.ts),
|
|
5511
|
+
// so dropping it silently kills cross-session project renames.
|
|
5512
|
+
[CLIENT_CAPS.projectUpdates]: true,
|
|
5112
5513
|
...this.config.capabilities,
|
|
5113
5514
|
},
|
|
5114
5515
|
...(this.config.appVersion ? { appVersion: this.config.appVersion } : {}),
|
|
@@ -5198,13 +5599,18 @@ export class DaemonClient {
|
|
|
5198
5599
|
}
|
|
5199
5600
|
const parsed = validateWSOutboundMessage(parsedJson);
|
|
5200
5601
|
if (!parsed.success) {
|
|
5201
|
-
const
|
|
5602
|
+
const responseIdentity = extractCorrelatedResponseIdentity(parsedJson);
|
|
5603
|
+
const envelopeType = parsedJson != null &&
|
|
5202
5604
|
typeof parsedJson === "object" &&
|
|
5203
5605
|
"type" in parsedJson &&
|
|
5204
5606
|
typeof parsedJson.type === "string"
|
|
5205
5607
|
? parsedJson.type
|
|
5206
5608
|
: "unknown";
|
|
5609
|
+
const msgType = responseIdentity?.responseType ?? envelopeType;
|
|
5207
5610
|
this.logger.warn({ msgType, error: parsed.error.message }, "Message validation failed");
|
|
5611
|
+
if (responseIdentity) {
|
|
5612
|
+
this.rejectWaitersForRequestId(responseIdentity.requestId, new DaemonProtocolError(responseIdentity));
|
|
5613
|
+
}
|
|
5208
5614
|
return;
|
|
5209
5615
|
}
|
|
5210
5616
|
this.consecutiveLivenessFailures = 0;
|
|
@@ -5485,6 +5891,18 @@ export class DaemonClient {
|
|
|
5485
5891
|
}
|
|
5486
5892
|
}
|
|
5487
5893
|
}
|
|
5894
|
+
rejectWaitersForRequestId(requestId, error) {
|
|
5895
|
+
for (const waiter of Array.from(this.waiters)) {
|
|
5896
|
+
if (waiter.requestId !== requestId) {
|
|
5897
|
+
continue;
|
|
5898
|
+
}
|
|
5899
|
+
this.waiters.delete(waiter);
|
|
5900
|
+
if (waiter.timeoutHandle) {
|
|
5901
|
+
clearTimeout(waiter.timeoutHandle);
|
|
5902
|
+
}
|
|
5903
|
+
waiter.reject(error);
|
|
5904
|
+
}
|
|
5905
|
+
}
|
|
5488
5906
|
clearWaiters(error) {
|
|
5489
5907
|
for (const waiter of Array.from(this.waiters)) {
|
|
5490
5908
|
if (waiter.timeoutHandle) {
|
|
@@ -5549,7 +5967,7 @@ export class DaemonClient {
|
|
|
5549
5967
|
return null;
|
|
5550
5968
|
}
|
|
5551
5969
|
}
|
|
5552
|
-
waitForWithCancel(predicate, timeout = 30000,
|
|
5970
|
+
waitForWithCancel(predicate, timeout = 30000, options) {
|
|
5553
5971
|
// Capture stack trace at call site, not inside setTimeout
|
|
5554
5972
|
const timeoutError = new Error(`Timeout waiting for message (${timeout}ms)`);
|
|
5555
5973
|
let waiter = null;
|
|
@@ -5582,6 +6000,7 @@ export class DaemonClient {
|
|
|
5582
6000
|
resolve: wrappedResolve,
|
|
5583
6001
|
reject: wrappedReject,
|
|
5584
6002
|
timeoutHandle,
|
|
6003
|
+
requestId: options?.requestId,
|
|
5585
6004
|
};
|
|
5586
6005
|
this.waiters.add(waiter);
|
|
5587
6006
|
});
|
|
@@ -5608,6 +6027,80 @@ export class DaemonClient {
|
|
|
5608
6027
|
};
|
|
5609
6028
|
return { promise, cancel };
|
|
5610
6029
|
}
|
|
6030
|
+
async cloneGithubProject(input, requestId) {
|
|
6031
|
+
const message = {
|
|
6032
|
+
type: "project.github.clone.request",
|
|
6033
|
+
repo: input.repo,
|
|
6034
|
+
targetDirectory: input.targetDirectory,
|
|
6035
|
+
...(input.cloneProtocol ? { cloneProtocol: input.cloneProtocol } : {}),
|
|
6036
|
+
};
|
|
6037
|
+
return this.sendNamespacedCorrelatedSessionRequest({
|
|
6038
|
+
requestId,
|
|
6039
|
+
message,
|
|
6040
|
+
timeout: PROJECT_GITHUB_CLONE_TIMEOUT_MS,
|
|
6041
|
+
});
|
|
6042
|
+
}
|
|
6043
|
+
/**
|
|
6044
|
+
* `includeDiscovered` also returns the Scripts the workspace's own project
|
|
6045
|
+
* files declare (package.json scripts today), each tagged with its `source`.
|
|
6046
|
+
* Gate it on `server_info.features.workspaceScriptDiscovery` — an older
|
|
6047
|
+
* daemon ignores the flag and answers with the otto.json list only.
|
|
6048
|
+
*/
|
|
6049
|
+
async listWorkspaceScripts(workspaceId, options) {
|
|
6050
|
+
return this.sendCorrelatedSessionRequest({
|
|
6051
|
+
requestId: options?.requestId,
|
|
6052
|
+
message: {
|
|
6053
|
+
type: "workspace.script.list.request",
|
|
6054
|
+
workspaceId,
|
|
6055
|
+
includeDiscovered: options?.includeDiscovered ?? false,
|
|
6056
|
+
},
|
|
6057
|
+
responseType: "workspace.script.list.response",
|
|
6058
|
+
});
|
|
6059
|
+
}
|
|
6060
|
+
async startWorkspaceScriptWithStatus(workspaceId, scriptName, requestId) {
|
|
6061
|
+
return this.sendCorrelatedSessionRequest({
|
|
6062
|
+
requestId,
|
|
6063
|
+
message: { type: "workspace.script.start.request", workspaceId, scriptName },
|
|
6064
|
+
responseType: "workspace.script.start.response",
|
|
6065
|
+
});
|
|
6066
|
+
}
|
|
6067
|
+
async stopWorkspaceScript(workspaceId, scriptName, requestId) {
|
|
6068
|
+
return this.sendCorrelatedSessionRequest({
|
|
6069
|
+
requestId,
|
|
6070
|
+
message: { type: "workspace.script.stop.request", workspaceId, scriptName },
|
|
6071
|
+
responseType: "workspace.script.stop.response",
|
|
6072
|
+
});
|
|
6073
|
+
}
|
|
6074
|
+
async connectHub(hubUrl, token, requestId) {
|
|
6075
|
+
this.requireHubRelationshipSupport();
|
|
6076
|
+
return this.sendCorrelatedSessionRequest({
|
|
6077
|
+
requestId,
|
|
6078
|
+
message: { type: "hub.management.daemon.connect.request", hubUrl, token },
|
|
6079
|
+
responseType: "hub.management.daemon.connect.response",
|
|
6080
|
+
});
|
|
6081
|
+
}
|
|
6082
|
+
async getHubStatus(requestId) {
|
|
6083
|
+
this.requireHubRelationshipSupport();
|
|
6084
|
+
return this.sendCorrelatedSessionRequest({
|
|
6085
|
+
requestId,
|
|
6086
|
+
message: { type: "hub.management.daemon.get_status.request" },
|
|
6087
|
+
responseType: "hub.management.daemon.get_status.response",
|
|
6088
|
+
});
|
|
6089
|
+
}
|
|
6090
|
+
async disconnectHub(force = false, requestId) {
|
|
6091
|
+
this.requireHubRelationshipSupport();
|
|
6092
|
+
return this.sendCorrelatedSessionRequest({
|
|
6093
|
+
requestId,
|
|
6094
|
+
message: { type: "hub.management.daemon.disconnect.request", force },
|
|
6095
|
+
responseType: "hub.management.daemon.disconnect.response",
|
|
6096
|
+
});
|
|
6097
|
+
}
|
|
6098
|
+
requireHubRelationshipSupport() {
|
|
6099
|
+
// COMPAT(hubRelationship): added in v0.2.5, drop the gate when floor >= v0.2.5.
|
|
6100
|
+
if (this.lastServerInfoMessage?.features?.hubRelationship !== true) {
|
|
6101
|
+
throw new Error("Update the host to use Hub relationship management.");
|
|
6102
|
+
}
|
|
6103
|
+
}
|
|
5611
6104
|
}
|
|
5612
6105
|
function resolveAgentConfig(options) {
|
|
5613
6106
|
const { config, provider, cwd, env: _env, workspaceId: _workspaceId, initialPrompt: _initialPrompt, images: _images, git: _git, worktreeName: _worktreeName, requestId: _requestId, labels: _labels, ...overrides } = options;
|
package/dist/index.d.ts
CHANGED
|
@@ -118,13 +118,13 @@ export interface OttoAgentCreateOptions extends OttoAgentConfigOverrides {
|
|
|
118
118
|
provider?: CreateAgentRequestMessage["config"]["provider"];
|
|
119
119
|
cwd?: string;
|
|
120
120
|
workspaceId?: string;
|
|
121
|
+
callerAgentId?: string;
|
|
121
122
|
initialPrompt?: string;
|
|
122
123
|
clientMessageId?: string;
|
|
123
124
|
outputSchema?: Record<string, unknown>;
|
|
124
125
|
images?: CreateAgentRequestMessage["images"];
|
|
125
126
|
attachments?: CreateAgentRequestMessage["attachments"];
|
|
126
127
|
git?: CreateAgentRequestMessage["git"];
|
|
127
|
-
worktreeName?: string;
|
|
128
128
|
requestId?: string;
|
|
129
129
|
labels?: Record<string, string>;
|
|
130
130
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@otto-code/client",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.6",
|
|
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.
|
|
40
|
-
"@otto-code/relay": "0.7.
|
|
39
|
+
"@otto-code/protocol": "0.7.6",
|
|
40
|
+
"@otto-code/relay": "0.7.6",
|
|
41
41
|
"zod": "^4.4.3"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|