@getpaseo/client 0.3.1 → 0.4.0-beta.2

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/README.md CHANGED
@@ -1,12 +1,39 @@
1
1
  # @getpaseo/client
2
2
 
3
- Paseo's JavaScript/TypeScript client package.
3
+ TypeScript SDK for building integrations on top of a Paseo daemon.
4
4
 
5
- ## Stability
5
+ ```bash
6
+ npm install @getpaseo/client
7
+ ```
8
+
9
+ ```ts
10
+ import { createPaseoClient } from "@getpaseo/client";
11
+
12
+ const client = createPaseoClient({ url: "ws://127.0.0.1:6767/ws" });
13
+ await client.connect();
14
+
15
+ const agent = await client.agents.create({
16
+ config: { provider: "codex/gpt-5.5" },
17
+ cwd: "/Users/me/dev/storefront",
18
+ prompt: "Review the current diff and name the riskiest change.",
19
+ });
20
+
21
+ const result = await agent.waitForFinish();
22
+ console.log(result.lastMessage);
23
+
24
+ await client.close();
25
+ ```
6
26
 
7
- This package is public so Paseo's published packages can depend on it cleanly.
8
- It is not a stable public SDK yet.
27
+ The public API is the package root. Imports under `@getpaseo/client/internal/*` are unsupported implementation details used by Paseo's own packages.
28
+
29
+ Read the [SDK documentation](https://paseo.sh/docs/sdk) for agents, workspaces, provider discovery, events, recipes, and the API reference. Runnable TypeScript patterns also live in [`examples/`](./examples/README.md).
30
+
31
+ ## Runtime
32
+
33
+ The client needs a WebSocket implementation. Modern browsers and Node.js 22 provide one globally.
34
+
35
+ Use a WebSocket URL ending in `/ws`, such as `ws://127.0.0.1:6767/ws`. Pass `password` when the daemon requires authentication.
36
+
37
+ ## Stability
9
38
 
10
- APIs, exports, runtime behavior, and types may change or disappear in any
11
- release without advance notice. Use it outside Paseo at your own risk until the
12
- package is explicitly documented as stable.
39
+ The high-level API exported from `@getpaseo/client` is the supported SDK surface. The SDK and daemon remain protocol-compatible across versions, but newly added capabilities can require a newer daemon.
@@ -4,7 +4,7 @@ import type { AgentAttentionNotificationPayload } from "@getpaseo/protocol/agent
4
4
  import { AgentRefreshedStatusPayloadSchema, CheckoutRenameBranchResponseSchema, RenameTerminalResponseSchema, RestartRequestedStatusPayloadSchema, ShutdownRequestedStatusPayloadSchema, DaemonUpdateResponseSchema, type ServerInfoStatusPayload } from "@getpaseo/protocol/messages";
5
5
  import type { AgentStreamEventPayload, AgentSnapshotPayload, ProjectPlacementPayload, AgentPermissionResolvedMessage, CreateAgentRequestMessage, CreatePaseoWorktreeRequest, FileDownloadTokenResponse, FileUploadResponse, FileExplorerResponse, FileVersion, FileWriteResult, FetchAgentTimelineResponseMessage, AgentForkContextResponseMessage, GitSetupOptions, CheckoutStatusResponse, CheckoutCommit, ParsedDiffFile, CheckoutCommitResponse, CheckoutMergeResponse, CheckoutMergeFromBaseResponse, CheckoutPullResponse, CheckoutPushResponse, CheckoutRefreshResponse, CheckoutPrCreateResponse, CheckoutPrMergeResponse, CheckoutPrMergeMethod, CheckoutForgeSetAutoMergeResponse, CheckoutGithubSetAutoMergeResponse, CheckoutForgeGetCheckDetailsResponse, CheckoutGithubGetCheckDetailsResponse, CheckoutPrStatusResponse, PullRequestTimelineResponse, CheckoutSwitchBranchResponse, StashSaveResponse, StashPopResponse, StashListResponse, ValidateBranchResponse, BranchSuggestionsResponse, ForgeSearchResponse, ForgeSearchRequest, GitHubSearchResponse, GitHubSearchRequest, DirectorySuggestionsResponse, PaseoWorktreeListResponse, PaseoWorktreeArchiveResponse, ProjectIconSource, ProjectIconResponse, ProjectIconGetResponse, ProjectAddResponse, ProjectCreateDirectoryResponse, OpenProjectResponseMessage, WorkspaceGithubSearchRepositoriesResponse, ProjectGithubCloneProtocol, ProjectGithubCloneResponse, ArchiveWorkspaceResponseMessage, WorkspaceSetupStatusResponseMessage, ListCommandsResponse, ListProviderFeaturesResponseMessage, ListProviderModelsResponseMessage, ListProviderModesResponseMessage, ListAvailableProvidersResponse, GetProvidersSnapshotResponseMessage, RefreshProvidersSnapshotResponseMessage, ProviderDiagnosticResponseMessage, ProviderUsageListResponseMessage, DaemonGetStatusResponse, DaemonGetPairingOfferResponse, DiagnosticsResponse, AgentRewindResponseMessage, ListTerminalsResponse, CreateTerminalResponse, SubscribeTerminalResponse, SubscribeTerminalRequest, CloseItemsResponse, KillTerminalResponse, CaptureTerminalResponse, TerminalInput, SessionInboundMessage, SessionOutboundMessage, SendAgentMessageRequest, PaseoConfigRaw, PaseoConfigRevision, WorkspaceCreateRequest, WorkspaceRecoveryState } from "@getpaseo/protocol/messages";
6
6
  import type { AgentPermissionRequest, AgentPermissionResponse, AgentPersistenceHandle, AgentProviderNotice, AgentProvider, AgentSessionConfig } from "@getpaseo/protocol/agent-types";
7
- import type { MutableDaemonConfig, MutableDaemonConfigPatch } from "@getpaseo/protocol/messages";
7
+ import type { AgentConfigApply, MutableDaemonConfig, MutableDaemonConfigPatch } from "@getpaseo/protocol/messages";
8
8
  import { type DaemonTransportFactory, type WebSocketFactory } from "./daemon-client-transport.js";
9
9
  import { type TerminalStreamEvent } from "./terminal-stream-router.js";
10
10
  import type { BrowserAutomationExecuteRequest, BrowserAutomationExecuteResponse } from "@getpaseo/protocol/browser-automation/rpc-schemas";
@@ -270,42 +270,6 @@ type SubscribeTerminalPayload = SubscribeTerminalResponse["payload"];
270
270
  type CloseItemsPayload = CloseItemsResponse["payload"];
271
271
  type KillTerminalPayload = KillTerminalResponse["payload"];
272
272
  type CaptureTerminalPayload = CaptureTerminalResponse["payload"];
273
- type ChatCreatePayload = Extract<SessionOutboundMessage, {
274
- type: "chat/create/response";
275
- }>["payload"];
276
- type ChatListPayload = Extract<SessionOutboundMessage, {
277
- type: "chat/list/response";
278
- }>["payload"];
279
- type ChatInspectPayload = Extract<SessionOutboundMessage, {
280
- type: "chat/inspect/response";
281
- }>["payload"];
282
- type ChatDeletePayload = Extract<SessionOutboundMessage, {
283
- type: "chat/delete/response";
284
- }>["payload"];
285
- type ChatPostPayload = Extract<SessionOutboundMessage, {
286
- type: "chat/post/response";
287
- }>["payload"];
288
- type ChatReadPayload = Extract<SessionOutboundMessage, {
289
- type: "chat/read/response";
290
- }>["payload"];
291
- type ChatWaitPayload = Extract<SessionOutboundMessage, {
292
- type: "chat/wait/response";
293
- }>["payload"];
294
- type LoopRunPayload = Extract<SessionOutboundMessage, {
295
- type: "loop/run/response";
296
- }>["payload"];
297
- type LoopListPayload = Extract<SessionOutboundMessage, {
298
- type: "loop/list/response";
299
- }>["payload"];
300
- type LoopInspectPayload = Extract<SessionOutboundMessage, {
301
- type: "loop/inspect/response";
302
- }>["payload"];
303
- type LoopLogsPayload = Extract<SessionOutboundMessage, {
304
- type: "loop/logs/response";
305
- }>["payload"];
306
- type LoopStopPayload = Extract<SessionOutboundMessage, {
307
- type: "loop/stop/response";
308
- }>["payload"];
309
273
  type ScheduleCreatePayload = Extract<SessionOutboundMessage, {
310
274
  type: "schedule/create/response";
311
275
  }>["payload"];
@@ -437,70 +401,6 @@ export type FetchWorkspacesPageInfo = FetchWorkspacesPayload["pageInfo"];
437
401
  export type ProjectListPayload = Extract<SessionOutboundMessage, {
438
402
  type: "project.list.response";
439
403
  }>["payload"];
440
- export interface CreateChatRoomOptions {
441
- name: string;
442
- purpose?: string | null;
443
- requestId?: string;
444
- }
445
- export interface InspectChatRoomOptions {
446
- room: string;
447
- requestId?: string;
448
- }
449
- export interface DeleteChatRoomOptions {
450
- room: string;
451
- requestId?: string;
452
- }
453
- export interface PostChatMessageOptions {
454
- room: string;
455
- body: string;
456
- authorAgentId?: string;
457
- replyToMessageId?: string | null;
458
- requestId?: string;
459
- }
460
- export interface ReadChatMessagesOptions {
461
- room: string;
462
- limit?: number;
463
- since?: string;
464
- authorAgentId?: string;
465
- requestId?: string;
466
- timeout?: number;
467
- }
468
- export interface WaitForChatMessagesOptions {
469
- room: string;
470
- afterMessageId?: string | null;
471
- timeoutMs?: number;
472
- requestId?: string;
473
- }
474
- export interface RunLoopOptions {
475
- prompt: string;
476
- cwd: string;
477
- provider?: string;
478
- model?: string;
479
- modeId?: string;
480
- verifierProvider?: string;
481
- verifierModel?: string;
482
- verifierModeId?: string;
483
- verifyPrompt?: string | null;
484
- verifyChecks?: string[];
485
- name?: string | null;
486
- sleepMs?: number;
487
- maxIterations?: number;
488
- maxTimeMs?: number;
489
- requestId?: string;
490
- }
491
- export interface InspectLoopOptions {
492
- id: string;
493
- requestId?: string;
494
- }
495
- export interface LoopLogsOptions {
496
- id: string;
497
- afterSeq?: number;
498
- requestId?: string;
499
- }
500
- export interface StopLoopOptions {
501
- id: string;
502
- requestId?: string;
503
- }
504
404
  export interface CreateScheduleOptions {
505
405
  prompt: string;
506
406
  name?: string | null;
@@ -590,6 +490,21 @@ export interface WaitForFinishResult {
590
490
  error: string | null;
591
491
  lastMessage: string | null;
592
492
  }
493
+ type GetDaemonConfigResponse = Extract<SessionOutboundMessage, {
494
+ type: "get_daemon_config_response";
495
+ }>;
496
+ type SetDaemonConfigResponse = Extract<SessionOutboundMessage, {
497
+ type: "set_daemon_config_response";
498
+ }>;
499
+ type CorrelatedResponseMessage = Extract<SessionOutboundMessage, {
500
+ payload: {
501
+ requestId: string;
502
+ };
503
+ }> | GetDaemonConfigResponse | SetDaemonConfigResponse;
504
+ type CorrelatedResponseType = CorrelatedResponseMessage["type"];
505
+ type CorrelatedResponsePayload<TType extends CorrelatedResponseType> = Extract<CorrelatedResponseMessage, {
506
+ type: TType;
507
+ }>["payload"];
593
508
  export declare class DaemonClient {
594
509
  private config;
595
510
  private transport;
@@ -693,6 +608,7 @@ export declare class DaemonClient {
693
608
  appVisibilityChangedAt?: string;
694
609
  }): void;
695
610
  registerPushToken(token: string): void;
611
+ unregisterPushToken(token: string): Promise<void>;
696
612
  ping(params?: {
697
613
  requestId?: string;
698
614
  timeoutMs?: number;
@@ -799,6 +715,14 @@ export declare class DaemonClient {
799
715
  setAgentModel(agentId: string, modelId: string | null): Promise<void>;
800
716
  setAgentFeature(agentId: string, featureId: string, value: unknown): Promise<void>;
801
717
  setAgentThinkingOption(agentId: string, thinkingOptionId: string | null): Promise<AgentProviderNotice | null>;
718
+ /**
719
+ * Applies a whole agent-config bundle in one request. Use this instead of
720
+ * chaining the single-field setters when the values belong together so client
721
+ * interruption and other mutations cannot interleave between steps. A
722
+ * provider rejection can still leave earlier steps applied.
723
+ * Gated on `server_info.features.agentConfigApply`.
724
+ */
725
+ applyAgentConfig(agentId: string, config: AgentConfigApply): Promise<AgentProviderNotice | null>;
802
726
  restartServer(reason?: string, requestId?: string): Promise<RestartRequestedStatusPayload>;
803
727
  shutdownServer(options?: ShutdownServerOptions): Promise<ShutdownRequestedStatusPayload>;
804
728
  updateDaemon(requestId?: string): Promise<DaemonUpdateResponse["payload"]>;
@@ -968,6 +892,28 @@ export declare class DaemonClient {
968
892
  expectedModifiedAt: string;
969
893
  expectedRevision?: string;
970
894
  }): Promise<FileWriteResult>;
895
+ createFileEntry(input: {
896
+ cwd: string;
897
+ parentPath: string;
898
+ name: string;
899
+ kind: "file" | "directory";
900
+ }): Promise<CorrelatedResponsePayload<"fs.entry.create.response">>;
901
+ renameFileEntry(input: {
902
+ cwd: string;
903
+ path: string;
904
+ name: string;
905
+ }): Promise<CorrelatedResponsePayload<"fs.entry.rename.response">>;
906
+ duplicateFileEntry(input: {
907
+ cwd: string;
908
+ path: string;
909
+ }): Promise<CorrelatedResponsePayload<"fs.entry.duplicate.response">>;
910
+ deleteFileEntry(input: {
911
+ cwd: string;
912
+ path: string;
913
+ }): Promise<CorrelatedResponsePayload<"fs.entry.delete.response">>;
914
+ checkoutDiscardChanges(cwd: string, input: {
915
+ paths: string[];
916
+ }): Promise<CorrelatedResponsePayload<"checkout.discard_changes.response">>;
971
917
  uploadFile(input: FileUploadInput): Promise<FileUploadResult>;
972
918
  requestDownloadToken(cwd: string, path: string, requestId?: string): Promise<FileDownloadTokenPayload>;
973
919
  requestProjectIcon(cwd: string, requestId?: string): Promise<ProjectIconResponse["payload"]>;
@@ -1095,13 +1041,6 @@ export declare class DaemonClient {
1095
1041
  end?: number;
1096
1042
  stripAnsi?: boolean;
1097
1043
  }, requestId?: string): Promise<CaptureTerminalPayload>;
1098
- createChatRoom(options: CreateChatRoomOptions): Promise<ChatCreatePayload>;
1099
- listChatRooms(requestId?: string): Promise<ChatListPayload>;
1100
- inspectChatRoom(options: InspectChatRoomOptions): Promise<ChatInspectPayload>;
1101
- deleteChatRoom(options: DeleteChatRoomOptions): Promise<ChatDeletePayload>;
1102
- postChatMessage(options: PostChatMessageOptions): Promise<ChatPostPayload>;
1103
- readChatMessages(options: ReadChatMessagesOptions): Promise<ChatReadPayload>;
1104
- waitForChatMessages(options: WaitForChatMessagesOptions): Promise<ChatWaitPayload>;
1105
1044
  scheduleCreate(options: CreateScheduleOptions): Promise<ScheduleCreatePayload>;
1106
1045
  scheduleList(requestId?: string): Promise<ScheduleListPayload>;
1107
1046
  scheduleInspect(options: InspectScheduleOptions): Promise<ScheduleInspectPayload>;
@@ -1111,11 +1050,6 @@ export declare class DaemonClient {
1111
1050
  scheduleDelete(options: InspectScheduleOptions): Promise<ScheduleDeletePayload>;
1112
1051
  scheduleRunOnce(options: InspectScheduleOptions): Promise<ScheduleRunOncePayload>;
1113
1052
  scheduleUpdate(options: UpdateScheduleOptions): Promise<ScheduleUpdatePayload>;
1114
- loopRun(options: RunLoopOptions): Promise<LoopRunPayload>;
1115
- loopList(requestId?: string): Promise<LoopListPayload>;
1116
- loopInspect(options: string | InspectLoopOptions): Promise<LoopInspectPayload>;
1117
- loopLogs(options: string | LoopLogsOptions, afterSeq?: number): Promise<LoopLogsPayload>;
1118
- loopStop(options: string | StopLoopOptions): Promise<LoopStopPayload>;
1119
1053
  onTerminalStreamEvent(handler: (event: TerminalStreamEvent) => void): () => void;
1120
1054
  waitForTerminalStreamEvent(predicate: (event: TerminalStreamEvent) => boolean, timeout?: number): Promise<TerminalStreamEvent>;
1121
1055
  private createRequestId;
@@ -112,6 +112,7 @@ function toTimeoutError(error, label, timeoutMs) {
112
112
  const DEFAULT_RECONNECT_BASE_DELAY_MS = 1500;
113
113
  const DEFAULT_RECONNECT_MAX_DELAY_MS = 30000;
114
114
  const DEFAULT_SESSION_RPC_TIMEOUT_MS = 60000;
115
+ const PUSH_TOKEN_REVOCATION_TIMEOUT_MS = 2000;
115
116
  const DEFAULT_CONNECT_TIMEOUT_MS = 15000;
116
117
  const DEFAULT_LIVENESS_TIMEOUT_MS = 5000;
117
118
  const LIVENESS_HEARTBEAT_INTERVAL_MS = 10000;
@@ -902,6 +903,15 @@ export class DaemonClient {
902
903
  token,
903
904
  });
904
905
  }
906
+ async unregisterPushToken(token) {
907
+ const requestId = this.createRequestId();
908
+ await this.sendCorrelatedSessionRequest({
909
+ requestId,
910
+ message: { type: "push.unregister.request", token, requestId },
911
+ responseType: "push.unregister.response",
912
+ timeout: PUSH_TOKEN_REVOCATION_TIMEOUT_MS,
913
+ });
914
+ }
905
915
  async ping(params) {
906
916
  const requestId = params?.requestId ?? `ping-${Date.now()}-${Math.random().toString(36).slice(2)}`;
907
917
  const clientSentAt = Date.now();
@@ -1985,6 +1995,40 @@ export class DaemonClient {
1985
1995
  }
1986
1996
  return payload.notice ?? null;
1987
1997
  }
1998
+ /**
1999
+ * Applies a whole agent-config bundle in one request. Use this instead of
2000
+ * chaining the single-field setters when the values belong together so client
2001
+ * interruption and other mutations cannot interleave between steps. A
2002
+ * provider rejection can still leave earlier steps applied.
2003
+ * Gated on `server_info.features.agentConfigApply`.
2004
+ */
2005
+ async applyAgentConfig(agentId, config) {
2006
+ const requestId = this.createRequestId();
2007
+ const message = SessionInboundMessageSchema.parse({
2008
+ type: "agent.config.apply.request",
2009
+ agentId,
2010
+ config,
2011
+ requestId,
2012
+ });
2013
+ const payload = await this.sendRequest({
2014
+ requestId,
2015
+ message,
2016
+ options: { skipQueue: true },
2017
+ select: (msg) => {
2018
+ if (msg.type !== "agent.config.apply.response") {
2019
+ return null;
2020
+ }
2021
+ if (msg.payload.requestId !== requestId) {
2022
+ return null;
2023
+ }
2024
+ return msg.payload;
2025
+ },
2026
+ });
2027
+ if (!payload.accepted) {
2028
+ throw new Error(payload.error ?? "applyAgentConfig rejected");
2029
+ }
2030
+ return payload.notice ?? null;
2031
+ }
1988
2032
  async restartServer(reason, requestId) {
1989
2033
  const resolvedRequestId = this.createRequestId(requestId);
1990
2034
  const message = SessionInboundMessageSchema.parse({
@@ -2851,6 +2895,31 @@ export class DaemonClient {
2851
2895
  });
2852
2896
  return payload.result;
2853
2897
  }
2898
+ async createFileEntry(input) {
2899
+ return this.sendNamespacedCorrelatedSessionRequest({
2900
+ message: { type: "fs.entry.create.request", ...input },
2901
+ });
2902
+ }
2903
+ async renameFileEntry(input) {
2904
+ return this.sendNamespacedCorrelatedSessionRequest({
2905
+ message: { type: "fs.entry.rename.request", ...input },
2906
+ });
2907
+ }
2908
+ async duplicateFileEntry(input) {
2909
+ return this.sendNamespacedCorrelatedSessionRequest({
2910
+ message: { type: "fs.entry.duplicate.request", ...input },
2911
+ });
2912
+ }
2913
+ async deleteFileEntry(input) {
2914
+ return this.sendNamespacedCorrelatedSessionRequest({
2915
+ message: { type: "fs.entry.delete.request", ...input },
2916
+ });
2917
+ }
2918
+ async checkoutDiscardChanges(cwd, input) {
2919
+ return this.sendNamespacedCorrelatedSessionRequest({
2920
+ message: { type: "checkout.discard_changes.request", cwd, paths: input.paths },
2921
+ });
2922
+ }
2854
2923
  async uploadFile(input) {
2855
2924
  const bytes = asUint8Array(input.bytes);
2856
2925
  if (!bytes) {
@@ -3436,86 +3505,6 @@ export class DaemonClient {
3436
3505
  options: { skipQueue: true },
3437
3506
  });
3438
3507
  }
3439
- async createChatRoom(options) {
3440
- return this.sendCorrelatedSessionRequest({
3441
- requestId: options.requestId,
3442
- message: {
3443
- type: "chat/create",
3444
- name: options.name,
3445
- ...(options.purpose ? { purpose: options.purpose } : {}),
3446
- },
3447
- responseType: "chat/create/response",
3448
- });
3449
- }
3450
- async listChatRooms(requestId) {
3451
- return this.sendCorrelatedSessionRequest({
3452
- requestId,
3453
- message: {
3454
- type: "chat/list",
3455
- },
3456
- responseType: "chat/list/response",
3457
- });
3458
- }
3459
- async inspectChatRoom(options) {
3460
- return this.sendCorrelatedSessionRequest({
3461
- requestId: options.requestId,
3462
- message: {
3463
- type: "chat/inspect",
3464
- room: options.room,
3465
- },
3466
- responseType: "chat/inspect/response",
3467
- });
3468
- }
3469
- async deleteChatRoom(options) {
3470
- return this.sendCorrelatedSessionRequest({
3471
- requestId: options.requestId,
3472
- message: {
3473
- type: "chat/delete",
3474
- room: options.room,
3475
- },
3476
- responseType: "chat/delete/response",
3477
- });
3478
- }
3479
- async postChatMessage(options) {
3480
- return this.sendCorrelatedSessionRequest({
3481
- requestId: options.requestId,
3482
- message: {
3483
- type: "chat/post",
3484
- room: options.room,
3485
- body: options.body,
3486
- ...(options.authorAgentId ? { authorAgentId: options.authorAgentId } : {}),
3487
- ...(options.replyToMessageId ? { replyToMessageId: options.replyToMessageId } : {}),
3488
- },
3489
- responseType: "chat/post/response",
3490
- });
3491
- }
3492
- async readChatMessages(options) {
3493
- return this.sendCorrelatedSessionRequest({
3494
- requestId: options.requestId,
3495
- message: {
3496
- type: "chat/read",
3497
- room: options.room,
3498
- ...(typeof options.limit === "number" ? { limit: options.limit } : {}),
3499
- ...(options.since ? { since: options.since } : {}),
3500
- ...(options.authorAgentId ? { authorAgentId: options.authorAgentId } : {}),
3501
- },
3502
- responseType: "chat/read/response",
3503
- timeout: options.timeout,
3504
- });
3505
- }
3506
- async waitForChatMessages(options) {
3507
- return this.sendCorrelatedSessionRequest({
3508
- requestId: options.requestId,
3509
- message: {
3510
- type: "chat/wait",
3511
- room: options.room,
3512
- ...(options.afterMessageId ? { afterMessageId: options.afterMessageId } : {}),
3513
- ...(typeof options.timeoutMs === "number" ? { timeoutMs: options.timeoutMs } : {}),
3514
- },
3515
- responseType: "chat/wait/response",
3516
- timeout: (options.timeoutMs ?? 0) + 10000,
3517
- });
3518
- }
3519
3508
  async scheduleCreate(options) {
3520
3509
  return this.sendCorrelatedSessionRequest({
3521
3510
  requestId: options.requestId,
@@ -3617,76 +3606,6 @@ export class DaemonClient {
3617
3606
  responseType: "schedule/update/response",
3618
3607
  });
3619
3608
  }
3620
- async loopRun(options) {
3621
- return this.sendCorrelatedSessionRequest({
3622
- requestId: options.requestId,
3623
- message: {
3624
- type: "loop/run",
3625
- prompt: options.prompt,
3626
- cwd: options.cwd,
3627
- ...(options.provider ? { provider: options.provider } : {}),
3628
- ...(options.model ? { model: options.model } : {}),
3629
- ...(options.modeId ? { modeId: options.modeId } : {}),
3630
- ...(options.verifierProvider ? { verifierProvider: options.verifierProvider } : {}),
3631
- ...(options.verifierModel ? { verifierModel: options.verifierModel } : {}),
3632
- ...(options.verifierModeId ? { verifierModeId: options.verifierModeId } : {}),
3633
- ...(options.verifyPrompt ? { verifyPrompt: options.verifyPrompt } : {}),
3634
- ...(options.verifyChecks && options.verifyChecks.length > 0
3635
- ? { verifyChecks: options.verifyChecks }
3636
- : {}),
3637
- ...(options.name ? { name: options.name } : {}),
3638
- ...(typeof options.sleepMs === "number" ? { sleepMs: options.sleepMs } : {}),
3639
- ...(typeof options.maxIterations === "number"
3640
- ? { maxIterations: options.maxIterations }
3641
- : {}),
3642
- ...(typeof options.maxTimeMs === "number" ? { maxTimeMs: options.maxTimeMs } : {}),
3643
- },
3644
- responseType: "loop/run/response",
3645
- });
3646
- }
3647
- async loopList(requestId) {
3648
- return this.sendCorrelatedSessionRequest({
3649
- requestId,
3650
- message: {
3651
- type: "loop/list",
3652
- },
3653
- responseType: "loop/list/response",
3654
- });
3655
- }
3656
- async loopInspect(options) {
3657
- const normalized = typeof options === "string" ? { id: options } : options;
3658
- return this.sendCorrelatedSessionRequest({
3659
- requestId: normalized.requestId,
3660
- message: {
3661
- type: "loop/inspect",
3662
- id: normalized.id,
3663
- },
3664
- responseType: "loop/inspect/response",
3665
- });
3666
- }
3667
- async loopLogs(options, afterSeq) {
3668
- const normalized = typeof options === "string" ? { id: options, afterSeq } : options;
3669
- return this.sendCorrelatedSessionRequest({
3670
- requestId: normalized.requestId,
3671
- message: {
3672
- type: "loop/logs",
3673
- id: normalized.id,
3674
- ...(typeof normalized.afterSeq === "number" ? { afterSeq: normalized.afterSeq } : {}),
3675
- },
3676
- responseType: "loop/logs/response",
3677
- });
3678
- }
3679
- async loopStop(options) {
3680
- const normalized = typeof options === "string" ? { id: options } : options;
3681
- return this.sendCorrelatedSessionRequest({
3682
- requestId: normalized.requestId,
3683
- message: {
3684
- type: "loop/stop",
3685
- id: normalized.id,
3686
- },
3687
- responseType: "loop/stop/response",
3688
- });
3689
- }
3690
3609
  onTerminalStreamEvent(handler) {
3691
3610
  return this.terminalStreams.onEvent(handler);
3692
3611
  }
package/dist/index.d.ts CHANGED
@@ -1,8 +1,5 @@
1
- import type { AgentSnapshotPayload, CreateAgentRequestMessage, FetchWorkspacesRequestMessage, FetchWorkspacesResponseMessage, GetProvidersSnapshotResponseMessage, ListAvailableProvidersResponse, ListProviderFeaturesRequestMessage, ListProviderFeaturesResponseMessage, ListProviderModelsResponseMessage, ListProviderModesResponseMessage, MutableDaemonConfig, MutableDaemonConfigPatch, ProviderDiagnosticResponseMessage, ProjectPlacementPayload, RefreshProvidersSnapshotResponseMessage, SendAgentMessageRequest, SessionOutboundMessage, WorkspaceDescriptorPayload } from "@getpaseo/protocol/messages";
2
- import { DaemonClient } from "./daemon-client.js";
3
- import type { FetchAgentTimelineCursor, FetchAgentTimelineDirection, FetchAgentTimelinePayload, FetchAgentTimelineProjection } from "./daemon-client.js";
4
- export { DaemonClient };
5
- export type { DaemonClientConfig, DaemonEvent, BrowserAutomationExecuteRequestMessage, BrowserAutomationExecuteResponseMessage, WebSocketFactory, WebSocketLike, } from "./daemon-client.js";
1
+ import type { AgentSnapshotPayload, CreateAgentRequestMessage, FetchWorkspacesRequestMessage, FetchWorkspacesResponseMessage, GetProvidersSnapshotResponseMessage, ListAvailableProvidersResponse, ListProviderFeaturesRequestMessage, ListProviderFeaturesResponseMessage, ListProviderModelsResponseMessage, ListProviderModesResponseMessage, MutableDaemonConfig, MutableDaemonConfigPatch, ProviderDiagnosticResponseMessage, ProjectPlacementPayload, RefreshProvidersSnapshotResponseMessage, SendAgentMessageRequest, SessionOutboundMessage, WorkspaceDescriptorPayload, WorkspaceCreateRequest } from "@getpaseo/protocol/messages";
2
+ import type { FetchAgentsEntry, FetchAgentsOptions, FetchAgentsPageInfo, FetchAgentTimelineCursor, FetchAgentTimelineDirection, FetchAgentTimelinePayload, FetchAgentTimelineProjection, WaitForFinishResult } from "./daemon-client.js";
6
3
  export type ConnectionState = {
7
4
  status: "idle";
8
5
  } | {
@@ -46,6 +43,13 @@ export interface PaseoClientConfig {
46
43
  }
47
44
  export type PaseoWorkspace = WorkspaceDescriptorPayload;
48
45
  export type PaseoAgent = AgentSnapshotPayload;
46
+ export type PaseoAgentListOptions = FetchAgentsOptions;
47
+ export interface PaseoAgentListResult {
48
+ requestId: string;
49
+ subscriptionId?: string | null;
50
+ entries: FetchAgentsEntry[];
51
+ pageInfo: FetchAgentsPageInfo;
52
+ }
49
53
  export type PaseoWorkspaceListOptions = Omit<FetchWorkspacesRequestMessage, "type" | "requestId"> & {
50
54
  requestId?: string;
51
55
  };
@@ -59,11 +63,9 @@ export interface PaseoWorkspaceOpenOptions {
59
63
  cwd: string;
60
64
  requestId?: string;
61
65
  }
62
- export interface PaseoWorkspaceOpenResult {
63
- requestId: string;
64
- workspace: PaseoWorkspaceHandle | null;
65
- error: string | null;
66
- }
66
+ export type PaseoWorkspaceCreateOptions = Omit<WorkspaceCreateRequest, "type" | "requestId"> & {
67
+ requestId?: string;
68
+ };
67
69
  export interface PaseoWorkspaceArchiveResult {
68
70
  requestId: string;
69
71
  workspaceId: string;
@@ -74,19 +76,17 @@ export type PaseoWorkspaceUpdate = Extract<SessionOutboundMessage, {
74
76
  type: "workspace_update";
75
77
  }>["payload"];
76
78
  export type PaseoWorkspaceUpdateHandler = (update: PaseoWorkspaceUpdate) => void;
77
- /**
78
- * A handle is a stable typed reference to a daemon resource. Its identity is the
79
- * daemon id, and `latest()` only returns the most recent snapshot this handle has
80
- * seen through construction, `refetch()`, or this handle's local subscription.
81
- */
82
79
  export interface PaseoWorkspaceHandle {
83
80
  readonly id: string;
84
- latest(): PaseoWorkspace | null;
85
- /**
86
- * Fetches a fresh workspace snapshot through the existing workspace list RPC,
87
- * exact-matches this handle id from the result, and updates `latest()`.
88
- */
89
- refetch(options?: {
81
+ readonly projectId: string | null;
82
+ readonly directory: string | null;
83
+ readonly name: string | null;
84
+ readonly status: PaseoWorkspace["status"] | null;
85
+ readonly agents: {
86
+ create(options: PaseoWorkspaceAgentCreateOptions): Promise<PaseoAgentHandle>;
87
+ };
88
+ current(): PaseoWorkspace | null;
89
+ refresh(options?: {
90
90
  requestId?: string;
91
91
  }): Promise<PaseoWorkspace | null>;
92
92
  archive(requestId?: string): Promise<PaseoWorkspaceArchiveResult>;
@@ -101,8 +101,8 @@ export interface PaseoWorkspaceHandle {
101
101
  export interface PaseoWorkspaceActions {
102
102
  list(options?: PaseoWorkspaceListOptions): Promise<PaseoWorkspaceListResult>;
103
103
  ref(workspace: string | PaseoWorkspace): PaseoWorkspaceHandle;
104
- open(input: string | PaseoWorkspaceOpenOptions, requestId?: string): Promise<PaseoWorkspaceOpenResult>;
105
- create(input: string | PaseoWorkspaceOpenOptions, requestId?: string): Promise<PaseoWorkspaceOpenResult>;
104
+ open(input: string | PaseoWorkspaceOpenOptions, requestId?: string): Promise<PaseoWorkspaceHandle>;
105
+ create(options: PaseoWorkspaceCreateOptions): Promise<PaseoWorkspaceHandle>;
106
106
  archive(workspace: string | PaseoWorkspaceHandle, requestId?: string): Promise<PaseoWorkspaceArchiveResult>;
107
107
  /**
108
108
  * Local event subscription over the low-level driver's workspace_update stream.
@@ -111,23 +111,38 @@ export interface PaseoWorkspaceActions {
111
111
  subscribe(handler: PaseoWorkspaceUpdateHandler): () => void;
112
112
  }
113
113
  type PaseoAgentSessionConfig = CreateAgentRequestMessage["config"];
114
- type PaseoAgentProvider = PaseoAgentSessionConfig["provider"];
115
- type PaseoAgentConfigOverrides = Partial<Omit<PaseoAgentSessionConfig, "provider" | "cwd">>;
116
- export interface PaseoAgentCreateOptions extends PaseoAgentConfigOverrides {
117
- config?: PaseoAgentSessionConfig;
118
- provider?: CreateAgentRequestMessage["config"]["provider"];
119
- cwd?: string;
120
- workspaceId?: string;
121
- callerAgentId?: string;
122
- initialPrompt?: string;
114
+ export type PaseoAgentProvider = PaseoAgentSessionConfig["provider"];
115
+ export type PaseoProviderFeatureValues = Record<string, unknown>;
116
+ export interface PaseoAgentConfig {
117
+ /** Provider and model in `provider/model` format. */
118
+ provider: string;
119
+ modeId?: PaseoAgentSessionConfig["modeId"];
120
+ thinkingOptionId?: PaseoAgentSessionConfig["thinkingOptionId"];
121
+ featureValues?: PaseoProviderFeatureValues;
122
+ /** JSON-safe provider-native settings, validated by the selected provider. */
123
+ options?: PaseoAgentSessionConfig["providerOptions"];
124
+ systemPrompt?: PaseoAgentSessionConfig["systemPrompt"];
125
+ toolPolicy?: PaseoAgentSessionConfig["toolPolicy"];
126
+ mcpServers?: PaseoAgentSessionConfig["mcpServers"];
127
+ }
128
+ export interface PaseoAgentCreateOptions {
129
+ config: PaseoAgentConfig;
130
+ cwd: string;
131
+ parent?: string | PaseoAgentHandle;
132
+ title?: PaseoAgentSessionConfig["title"];
133
+ env?: CreateAgentRequestMessage["env"];
134
+ prompt?: string;
123
135
  clientMessageId?: string;
124
136
  outputSchema?: Record<string, unknown>;
125
137
  images?: CreateAgentRequestMessage["images"];
126
138
  attachments?: CreateAgentRequestMessage["attachments"];
127
139
  git?: CreateAgentRequestMessage["git"];
140
+ worktree?: CreateAgentRequestMessage["worktree"];
141
+ autoArchive?: CreateAgentRequestMessage["autoArchive"];
128
142
  requestId?: string;
129
143
  labels?: Record<string, string>;
130
144
  }
145
+ export type PaseoWorkspaceAgentCreateOptions = Omit<PaseoAgentCreateOptions, "cwd">;
131
146
  export interface PaseoAgentRefetchResult {
132
147
  agent: PaseoAgent;
133
148
  project: ProjectPlacementPayload | null;
@@ -147,6 +162,10 @@ export interface PaseoAgentSendOptions {
147
162
  }>;
148
163
  attachments?: SendAgentMessageRequest["attachments"];
149
164
  }
165
+ export interface PaseoAgentRunOptions extends PaseoAgentSendOptions {
166
+ timeoutMs?: number;
167
+ }
168
+ export type PaseoAgentRunResult = WaitForFinishResult;
150
169
  export type PaseoAgentUpdate = Extract<SessionOutboundMessage, {
151
170
  type: "agent_update";
152
171
  }>["payload"];
@@ -157,8 +176,8 @@ export type PaseoAgentUpdateHandler = (update: PaseoAgentUpdate) => void;
157
176
  export interface PaseoAgentTimelineHandle {
158
177
  /**
159
178
  * Fetches a fresh timeline page through the existing daemon RPC. If the daemon
160
- * includes an agent snapshot in the response, the parent handle's `latest()`
161
- * is updated to that snapshot.
179
+ * includes an agent snapshot in the response, the parent handle is updated to
180
+ * that value.
162
181
  */
163
182
  refetch(options?: PaseoAgentTimelineRefetchOptions): Promise<FetchAgentTimelinePayload>;
164
183
  /**
@@ -167,18 +186,19 @@ export interface PaseoAgentTimelineHandle {
167
186
  */
168
187
  subscribe(handler: (event: PaseoAgentStream) => void): () => void;
169
188
  }
170
- /**
171
- * Agent handles follow the same identity/snapshot rule as workspace handles:
172
- * `id` is stable, while `latest()` is only the newest snapshot observed by this
173
- * handle through construction, `refetch()`, timeline refetch, archive, or local
174
- * agent_update subscription.
175
- */
176
189
  export interface PaseoAgentHandle {
177
190
  readonly id: string;
191
+ readonly workspaceId: string | null;
192
+ readonly cwd: string | null;
193
+ readonly status: PaseoAgent["status"] | null;
178
194
  readonly timeline: PaseoAgentTimelineHandle;
179
- latest(): PaseoAgent | null;
180
- refetch(requestId?: string): Promise<PaseoAgentRefetchResult | null>;
195
+ current(): PaseoAgent | null;
196
+ refresh(requestId?: string): Promise<PaseoAgentRefetchResult | null>;
181
197
  send(text: string, options?: PaseoAgentSendOptions): Promise<void>;
198
+ /** Sends a prompt and resolves when that turn finishes or needs attention. */
199
+ run(text: string, options?: PaseoAgentRunOptions): Promise<PaseoAgentRunResult>;
200
+ /** Waits for the current turn, including one started with `prompt`. */
201
+ waitForFinish(timeoutMs?: number): Promise<PaseoAgentRunResult>;
182
202
  archive(): Promise<{
183
203
  archivedAt: string;
184
204
  }>;
@@ -186,6 +206,7 @@ export interface PaseoAgentHandle {
186
206
  subscribe(handler: (update: PaseoAgentUpdate) => void): () => void;
187
207
  }
188
208
  export interface PaseoAgentActions {
209
+ list(options?: PaseoAgentListOptions): Promise<PaseoAgentListResult>;
189
210
  ref(agent: string | PaseoAgent): PaseoAgentHandle;
190
211
  create(options: PaseoAgentCreateOptions): Promise<PaseoAgentHandle>;
191
212
  /**
@@ -194,19 +215,13 @@ export interface PaseoAgentActions {
194
215
  */
195
216
  subscribe(handler: PaseoAgentUpdateHandler): () => void;
196
217
  }
197
- export interface PaseoProviderConfig extends PaseoProviderConfigInput {
198
- provider: PaseoAgentProvider;
199
- }
200
- export type PaseoProviderFeatureValues = Record<string, unknown>;
201
- export interface PaseoProviderConfigInput {
202
- model?: string;
203
- modeId?: string;
204
- thinkingOptionId?: string;
205
- featureValues?: PaseoProviderFeatureValues;
206
- }
207
218
  export type PaseoProviderModelsResult = ListProviderModelsResponseMessage["payload"];
208
219
  export type PaseoProviderModesResult = ListProviderModesResponseMessage["payload"];
209
- export type PaseoProviderFeaturesInput = ListProviderFeaturesRequestMessage["draftConfig"];
220
+ type PaseoProviderFeaturesDraft = ListProviderFeaturesRequestMessage["draftConfig"];
221
+ export interface PaseoProviderFeaturesInput extends Omit<PaseoProviderFeaturesDraft, "provider" | "model"> {
222
+ /** Provider and model in `provider/model` format. */
223
+ provider: string;
224
+ }
210
225
  export type PaseoProviderFeaturesResult = ListProviderFeaturesResponseMessage["payload"];
211
226
  export type PaseoProviderAvailabilityResult = ListAvailableProvidersResponse["payload"];
212
227
  export type PaseoProviderSnapshotResult = GetProvidersSnapshotResponseMessage["payload"];
@@ -224,12 +239,10 @@ export interface PaseoProviderRefreshOptions {
224
239
  providers?: PaseoAgentProvider[];
225
240
  requestId?: string;
226
241
  }
242
+ export interface PaseoProviderWaitOptions extends PaseoProviderListOptions {
243
+ timeoutMs?: number;
244
+ }
227
245
  export interface PaseoProviderActions {
228
- codex(input?: PaseoProviderConfigInput): PaseoProviderConfig;
229
- claude(input?: PaseoProviderConfigInput): PaseoProviderConfig;
230
- opencode(input?: PaseoProviderConfigInput): PaseoProviderConfig;
231
- copilot(input?: PaseoProviderConfigInput): PaseoProviderConfig;
232
- config(provider: PaseoAgentProvider, input?: PaseoProviderConfigInput): PaseoProviderConfig;
233
246
  listModels(provider: PaseoAgentProvider, options?: PaseoProviderListOptions): Promise<PaseoProviderModelsResult>;
234
247
  listModes(provider: PaseoAgentProvider, options?: PaseoProviderListOptions): Promise<PaseoProviderModesResult>;
235
248
  listFeatures(draftConfig: PaseoProviderFeaturesInput, options?: {
@@ -239,6 +252,8 @@ export interface PaseoProviderActions {
239
252
  requestId?: string;
240
253
  }): Promise<PaseoProviderAvailabilityResult>;
241
254
  snapshot(options?: PaseoProviderListOptions): Promise<PaseoProviderSnapshotResult>;
255
+ /** Resolves after the daemon's lazy provider discovery has finished. */
256
+ waitForReady(options?: PaseoProviderWaitOptions): Promise<PaseoProviderSnapshotResult>;
242
257
  refresh(options?: PaseoProviderRefreshOptions): Promise<PaseoProviderRefreshResult>;
243
258
  diagnostic(provider: PaseoAgentProvider, options?: {
244
259
  requestId?: string;
@@ -278,4 +293,5 @@ export interface PaseoClient {
278
293
  getConnectionState(): ConnectionState;
279
294
  }
280
295
  export declare function createPaseoClient(config: PaseoClientConfig): PaseoClient;
296
+ export {};
281
297
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,45 +1,73 @@
1
1
  import { DaemonClient } from "./daemon-client.js";
2
- export { DaemonClient };
2
+ /**
3
+ * Coding turns routinely run for minutes, so the handle waits far longer than
4
+ * the transport's own conservative default.
5
+ */
6
+ const DEFAULT_WAIT_FOR_FINISH_MS = 10 * 60000;
3
7
  export function createPaseoClient(config) {
4
8
  const daemonClient = new DaemonClient({
5
9
  ...config,
6
10
  clientId: config.clientId ?? createGeneratedClientId(),
7
11
  clientType: "cli",
8
12
  });
9
- const createWorkspaceHandle = createWorkspaceHandleFactory(daemonClient);
10
13
  const createAgentHandle = createAgentHandleFactory(daemonClient);
14
+ const createAgent = async (options, placement) => {
15
+ const { config: agentConfig, cwd, parent, title, prompt, ...requestOptions } = options;
16
+ const { provider: providerModel, options: providerOptions, ...runtimeConfig } = agentConfig;
17
+ const { provider, model } = parseProviderModel(providerModel);
18
+ const effectiveCwd = placement?.cwd ?? cwd;
19
+ const agent = await daemonClient.createAgent({
20
+ ...requestOptions,
21
+ config: {
22
+ ...runtimeConfig,
23
+ provider,
24
+ model,
25
+ cwd: effectiveCwd,
26
+ ...(title !== undefined ? { title } : {}),
27
+ ...(providerOptions !== undefined ? { providerOptions } : {}),
28
+ },
29
+ ...(placement ? { workspaceId: placement.workspaceId } : {}),
30
+ ...(parent ? { callerAgentId: resolveAgentId(parent) } : {}),
31
+ ...(prompt !== undefined ? { initialPrompt: prompt } : {}),
32
+ });
33
+ return createAgentHandle(agent);
34
+ };
35
+ const createWorkspaceHandle = createWorkspaceHandleFactory(daemonClient, createAgent);
11
36
  return {
12
37
  workspaces: {
13
38
  list: (options) => daemonClient.fetchWorkspaces(options),
14
39
  ref: (workspace) => createWorkspaceHandle(workspace),
15
40
  open: (input, requestId) => openWorkspace(daemonClient, createWorkspaceHandle, input, requestId),
16
- create: (input, requestId) => openWorkspace(daemonClient, createWorkspaceHandle, input, requestId),
41
+ create: async ({ requestId, ...options }) => {
42
+ const result = await daemonClient.createWorkspace(options, requestId);
43
+ if (result.error || !result.workspace) {
44
+ throw new Error(result.error ?? "The daemon did not create a workspace");
45
+ }
46
+ return createWorkspaceHandle(result.workspace);
47
+ },
17
48
  archive: (workspace, requestId) => daemonClient.archiveWorkspace(resolveWorkspaceId(workspace), requestId),
18
49
  subscribe: (handler) => daemonClient.on("workspace_update", (message) => {
19
50
  handler(message.payload);
20
51
  }),
21
52
  },
22
53
  agents: {
54
+ list: (options) => daemonClient.fetchAgents(options),
23
55
  ref: (agent) => createAgentHandle(agent),
24
- create: async (options) => {
25
- const agent = await daemonClient.createAgent(options);
26
- return createAgentHandle(agent);
27
- },
56
+ create: (options) => createAgent(options),
28
57
  subscribe: (handler) => daemonClient.on("agent_update", (message) => {
29
58
  handler(message.payload);
30
59
  }),
31
60
  },
32
61
  providers: {
33
- codex: (input) => providerConfig("codex", input),
34
- claude: (input) => providerConfig("claude", input),
35
- opencode: (input) => providerConfig("opencode", input),
36
- copilot: (input) => providerConfig("copilot", input),
37
- config: (provider, input) => providerConfig(provider, input),
38
62
  listModels: (provider, options) => daemonClient.listProviderModels(provider, options),
39
63
  listModes: (provider, options) => daemonClient.listProviderModes(provider, options),
40
- listFeatures: (draftConfig, options) => daemonClient.listProviderFeatures(draftConfig, options),
64
+ listFeatures: ({ provider: providerModel, ...draftConfig }, options) => {
65
+ const { provider, model } = parseProviderModel(providerModel);
66
+ return daemonClient.listProviderFeatures({ ...draftConfig, provider, model }, options);
67
+ },
41
68
  listAvailable: (options) => daemonClient.listAvailableProviders(options),
42
69
  snapshot: (options) => daemonClient.getProvidersSnapshot(options),
70
+ waitForReady: (options) => waitForProvidersReady(daemonClient, options),
43
71
  refresh: (options) => daemonClient.refreshProvidersSnapshot(options),
44
72
  diagnostic: (provider, options) => daemonClient.getProviderDiagnostic(provider, options),
45
73
  subscribe: (handler) => daemonClient.on("providers_snapshot_update", (message) => {
@@ -56,39 +84,69 @@ export function createPaseoClient(config) {
56
84
  getConnectionState: () => daemonClient.getConnectionState(),
57
85
  };
58
86
  }
59
- function createWorkspaceHandleFactory(daemonClient) {
87
+ function createWorkspaceHandleFactory(daemonClient, createAgent) {
60
88
  return (workspace) => {
61
89
  const id = typeof workspace === "string" ? workspace : workspace.id;
62
- let latest = typeof workspace === "string" ? null : workspace;
63
- return {
64
- id,
65
- latest: () => latest,
66
- refetch: async (options) => {
67
- // Best-effort: fetches one page and matches by id client-side, so a workspace beyond
68
- // the first page won't be found. TODO: add a "get workspace by id" lookup and resolve
69
- // by exact id instead of paging.
90
+ let current = typeof workspace === "string" ? null : workspace;
91
+ const refresh = async (options) => {
92
+ let cursor;
93
+ let requestId = options?.requestId;
94
+ do {
70
95
  const result = await daemonClient.fetchWorkspaces({
71
- requestId: options?.requestId,
72
- page: { limit: 25 },
96
+ requestId,
97
+ page: { limit: 200, ...(cursor ? { cursor } : {}) },
73
98
  });
74
- latest = result.entries.find((entry) => entry.id === id) ?? null;
75
- return latest;
99
+ const match = result.entries.find((entry) => entry.id === id);
100
+ if (match) {
101
+ current = match;
102
+ return current;
103
+ }
104
+ cursor = result.pageInfo.nextCursor ?? undefined;
105
+ requestId = undefined;
106
+ } while (cursor);
107
+ current = null;
108
+ return current;
109
+ };
110
+ return {
111
+ id,
112
+ get projectId() {
113
+ return current?.projectId ?? null;
114
+ },
115
+ get directory() {
116
+ return current?.workspaceDirectory ?? null;
117
+ },
118
+ get name() {
119
+ return current?.name ?? null;
120
+ },
121
+ get status() {
122
+ return current?.status ?? null;
123
+ },
124
+ agents: {
125
+ create: async (options) => {
126
+ const snapshot = current ?? (await refresh());
127
+ if (!snapshot?.workspaceDirectory) {
128
+ throw new Error(`Workspace ${id} has no available directory`);
129
+ }
130
+ return createAgent({ ...options, cwd: snapshot.workspaceDirectory }, { workspaceId: id, cwd: snapshot.workspaceDirectory });
131
+ },
76
132
  },
133
+ current: () => current,
134
+ refresh,
77
135
  archive: async (requestId) => {
78
136
  const result = await daemonClient.archiveWorkspace(id, requestId);
79
- if (latest) {
80
- latest = { ...latest, archivingAt: result.archivedAt };
137
+ if (current) {
138
+ current = { ...current, archivingAt: result.archivedAt };
81
139
  }
82
140
  return result;
83
141
  },
84
142
  subscribe: (handler) => daemonClient.on("workspace_update", (message) => {
85
143
  const update = message.payload;
86
144
  if (update.kind === "upsert" && update.workspace.id === id) {
87
- latest = update.workspace;
145
+ current = update.workspace;
88
146
  handler(update);
89
147
  }
90
148
  if (update.kind === "remove" && update.id === id) {
91
- latest = null;
149
+ current = null;
92
150
  handler(update);
93
151
  }
94
152
  }),
@@ -98,14 +156,14 @@ function createWorkspaceHandleFactory(daemonClient) {
98
156
  function createAgentHandleFactory(daemonClient) {
99
157
  return (agent) => {
100
158
  const id = typeof agent === "string" ? agent : agent.id;
101
- let latest = typeof agent === "string" ? null : agent;
159
+ let current = typeof agent === "string" ? null : agent;
102
160
  const handle = {
103
161
  id,
104
162
  timeline: {
105
163
  refetch: async (options) => {
106
164
  const result = await daemonClient.fetchAgentTimeline(id, options);
107
165
  if (result.agent) {
108
- latest = result.agent;
166
+ current = result.agent;
109
167
  }
110
168
  return result;
111
169
  },
@@ -115,19 +173,44 @@ function createAgentHandleFactory(daemonClient) {
115
173
  }
116
174
  }),
117
175
  },
118
- latest: () => latest,
119
- refetch: async (requestId) => {
176
+ get workspaceId() {
177
+ return current?.workspaceId ?? null;
178
+ },
179
+ get cwd() {
180
+ return current?.cwd ?? null;
181
+ },
182
+ get status() {
183
+ return current?.status ?? null;
184
+ },
185
+ current: () => current,
186
+ refresh: async (requestId) => {
120
187
  const result = await daemonClient.fetchAgent({ agentId: id, requestId });
121
- latest = result?.agent ?? null;
188
+ current = result?.agent ?? null;
122
189
  return result;
123
190
  },
124
191
  send: async (text, options) => {
125
192
  await daemonClient.sendAgentMessage(id, text, options);
126
193
  },
194
+ run: async (text, options) => {
195
+ const { timeoutMs, ...sendOptions } = options ?? {};
196
+ await daemonClient.sendAgentMessage(id, text, sendOptions);
197
+ const result = await daemonClient.waitForFinish(id, timeoutMs ?? DEFAULT_WAIT_FOR_FINISH_MS);
198
+ if (result.final) {
199
+ current = result.final;
200
+ }
201
+ return result;
202
+ },
203
+ waitForFinish: async (timeoutMs) => {
204
+ const result = await daemonClient.waitForFinish(id, timeoutMs ?? DEFAULT_WAIT_FOR_FINISH_MS);
205
+ if (result.final) {
206
+ current = result.final;
207
+ }
208
+ return result;
209
+ },
127
210
  archive: async () => {
128
211
  const result = await daemonClient.archiveAgent(id);
129
- if (latest) {
130
- latest = { ...latest, archivedAt: result.archivedAt };
212
+ if (current) {
213
+ current = { ...current, archivedAt: result.archivedAt };
131
214
  }
132
215
  return result;
133
216
  },
@@ -137,11 +220,11 @@ function createAgentHandleFactory(daemonClient) {
137
220
  subscribe: (handler) => daemonClient.on("agent_update", (message) => {
138
221
  const update = message.payload;
139
222
  if (update.kind === "upsert" && update.agent.id === id) {
140
- latest = update.agent;
223
+ current = update.agent;
141
224
  handler(update);
142
225
  }
143
226
  if (update.kind === "remove" && update.agentId === id) {
144
- latest = null;
227
+ current = null;
145
228
  handler(update);
146
229
  }
147
230
  }),
@@ -152,23 +235,99 @@ function createAgentHandleFactory(daemonClient) {
152
235
  async function openWorkspace(daemonClient, createWorkspaceHandle, input, requestId) {
153
236
  const options = typeof input === "string" ? { cwd: input, requestId } : input;
154
237
  const result = await daemonClient.openProject(options.cwd, options.requestId);
155
- return {
156
- ...result,
157
- workspace: result.workspace ? createWorkspaceHandle(result.workspace) : null,
158
- };
238
+ if (result.error || !result.workspace) {
239
+ throw new Error(result.error ?? `The daemon did not open a workspace for ${options.cwd}`);
240
+ }
241
+ return createWorkspaceHandle(result.workspace);
159
242
  }
160
243
  function resolveWorkspaceId(workspace) {
161
244
  return typeof workspace === "string" ? workspace : workspace.id;
162
245
  }
163
- function providerConfig(provider, input = {}) {
246
+ function resolveAgentId(agent) {
247
+ return typeof agent === "string" ? agent : agent.id;
248
+ }
249
+ function parseProviderModel(selection) {
250
+ const separator = selection.indexOf("/");
251
+ if (separator <= 0 || separator === selection.length - 1) {
252
+ throw new Error('Expected config.provider in "provider/model" format');
253
+ }
164
254
  return {
165
- provider,
166
- ...(input.model !== undefined ? { model: input.model } : {}),
167
- ...(input.modeId !== undefined ? { modeId: input.modeId } : {}),
168
- ...(input.thinkingOptionId !== undefined ? { thinkingOptionId: input.thinkingOptionId } : {}),
169
- ...(input.featureValues !== undefined ? { featureValues: input.featureValues } : {}),
255
+ provider: selection.slice(0, separator),
256
+ model: selection.slice(separator + 1),
170
257
  };
171
258
  }
259
+ function waitForProvidersReady(daemonClient, options = {}) {
260
+ // COMPAT(providersSnapshotCwd): added in v0.3.2, remove gate after 2027-02-10.
261
+ if (daemonClient.getLastServerInfoMessage()?.features?.providersSnapshotCwd !== true) {
262
+ return Promise.reject(new Error("Update the host to wait for provider discovery."));
263
+ }
264
+ const { timeoutMs = 60000, ...snapshotOptions } = options;
265
+ return new Promise((resolve, reject) => {
266
+ let settled = false;
267
+ let requestId = null;
268
+ let snapshotCwd;
269
+ const pendingUpdates = new Map();
270
+ let latestEntries = [];
271
+ const cleanup = () => {
272
+ clearTimeout(timeout);
273
+ unsubscribe();
274
+ };
275
+ const finish = (snapshot) => {
276
+ if (settled)
277
+ return;
278
+ settled = true;
279
+ cleanup();
280
+ resolve(snapshot);
281
+ };
282
+ const fail = (error) => {
283
+ if (settled)
284
+ return;
285
+ settled = true;
286
+ cleanup();
287
+ reject(error instanceof Error ? error : new Error(String(error)));
288
+ };
289
+ const updateMatches = (update) => update.cwd === snapshotCwd;
290
+ const unsubscribe = daemonClient.on("providers_snapshot_update", (message) => {
291
+ const update = message.payload;
292
+ if (!requestId) {
293
+ pendingUpdates.set(update.cwd, update);
294
+ return;
295
+ }
296
+ if (!updateMatches(update))
297
+ return;
298
+ latestEntries = update.entries;
299
+ if (update.entries.some((entry) => entry.status === "loading"))
300
+ return;
301
+ finish({ ...update, requestId });
302
+ });
303
+ const timeout = setTimeout(() => {
304
+ const loading = latestEntries
305
+ .filter((entry) => entry.status === "loading")
306
+ .map((entry) => entry.provider)
307
+ .join(", ");
308
+ fail(new Error(loading
309
+ ? `Timed out waiting for providers: ${loading}`
310
+ : "Timed out waiting for provider discovery"));
311
+ }, timeoutMs);
312
+ void daemonClient
313
+ .getProvidersSnapshot(snapshotOptions)
314
+ .then((snapshot) => {
315
+ requestId = snapshot.requestId;
316
+ snapshotCwd = snapshot.cwd;
317
+ latestEntries = snapshot.entries;
318
+ if (!snapshot.entries.some((entry) => entry.status === "loading")) {
319
+ finish(snapshot);
320
+ return;
321
+ }
322
+ const pendingUpdate = pendingUpdates.get(snapshotCwd);
323
+ if (pendingUpdate && !pendingUpdate.entries.some((entry) => entry.status === "loading")) {
324
+ finish({ ...pendingUpdate, requestId });
325
+ }
326
+ return undefined;
327
+ })
328
+ .catch(fail);
329
+ });
330
+ }
172
331
  function createGeneratedClientId() {
173
332
  const randomId = typeof globalThis.crypto?.randomUUID === "function"
174
333
  ? globalThis.crypto.randomUUID()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getpaseo/client",
3
- "version": "0.3.1",
3
+ "version": "0.4.0-beta.2",
4
4
  "description": "Paseo client SDK package",
5
5
  "files": [
6
6
  "dist",
@@ -39,8 +39,8 @@
39
39
  "test": "vitest run"
40
40
  },
41
41
  "dependencies": {
42
- "@getpaseo/protocol": "0.3.1",
43
- "@getpaseo/relay": "0.3.1",
42
+ "@getpaseo/protocol": "0.4.0-beta.2",
43
+ "@getpaseo/relay": "0.4.0-beta.2",
44
44
  "zod": "^4.4.3"
45
45
  },
46
46
  "devDependencies": {