@camerontaylor/paseo-client 0.11.2-fork.2 → 0.12.0-fork.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
+ import { MAX_RELAY_WEBSOCKET_MESSAGE_BYTES } from "@camerontaylor/paseo-protocol/websocket-message-limit";
1
2
  import { createClientChannel, } from "@camerontaylor/paseo-relay/e2ee";
2
- import { extractRelayMessage, normalizeTransportPayload } from "./daemon-client-transport-utils.js";
3
+ import { extractRelayMessage, normalizeTransportPayload, messageTooLargeError, } from "./daemon-client-transport-utils.js";
3
4
  export function createRelayE2eeTransportFactory(args) {
4
5
  return ({ url }) => {
5
6
  const base = args.baseFactory({ url });
@@ -102,7 +103,13 @@ export function createEncryptedTransport(base, daemonPublicKeyB64, logger) {
102
103
  if (!channel) {
103
104
  throw new Error("Encrypted channel not ready");
104
105
  }
105
- void channel.send(normalizeTransportPayload(data)).catch((error) => {
106
+ const payload = normalizeTransportPayload(data);
107
+ // Reject synchronously so the requesting operation fails without closing the transport.
108
+ const wireBytes = channel.outboundWireByteLength(payload);
109
+ if (wireBytes > MAX_RELAY_WEBSOCKET_MESSAGE_BYTES) {
110
+ throw messageTooLargeError(wireBytes, MAX_RELAY_WEBSOCKET_MESSAGE_BYTES);
111
+ }
112
+ void channel.send(payload).catch((error) => {
106
113
  emitError(error);
107
114
  });
108
115
  },
@@ -10,4 +10,5 @@ export declare function describeTransportError(event?: unknown): string;
10
10
  export declare function safeRandomId(): string;
11
11
  export declare function decodeMessageData(data: unknown): string | null;
12
12
  export declare function encodeUtf8String(value: string): Uint8Array;
13
+ export declare function messageTooLargeError(size: number, limit: number): Error;
13
14
  //# sourceMappingURL=daemon-client-transport-utils.d.ts.map
@@ -120,4 +120,8 @@ export function encodeUtf8String(value) {
120
120
  }
121
121
  return out;
122
122
  }
123
+ export function messageTooLargeError(size, limit) {
124
+ const toMiB = (bytes) => (bytes / (1024 * 1024)).toFixed(1);
125
+ return new Error(`Message is too large to send (${toMiB(size)} MiB). The limit is ${toMiB(limit)} MiB, so send fewer or smaller attachments.`);
126
+ }
123
127
  //# sourceMappingURL=daemon-client-transport-utils.js.map
@@ -147,6 +147,7 @@ export interface DaemonClientTrace {
147
147
  endSection(): void;
148
148
  }
149
149
  export interface SendMessageOptions {
150
+ sourceAgentId?: string;
150
151
  messageId?: string;
151
152
  /** What happens when the agent is mid-turn. The daemon interrupts the turn when omitted. */
152
153
  activeTurnBehavior?: ActiveTurnBehavior;
@@ -182,6 +183,7 @@ export interface CreateAgentRequestOptions extends AgentConfigOverrides {
182
183
  git?: GitSetupOptions;
183
184
  worktree?: CreateAgentRequestMessage["worktree"];
184
185
  autoArchive?: CreateAgentRequestMessage["autoArchive"];
186
+ background?: CreateAgentRequestMessage["background"];
185
187
  worktreeName?: string;
186
188
  requestId?: string;
187
189
  labels?: Record<string, string>;
@@ -189,6 +191,9 @@ export interface CreateAgentRequestOptions extends AgentConfigOverrides {
189
191
  export interface CreateWorkspaceRequestOptions {
190
192
  source: WorkspaceCreateRequest["source"];
191
193
  title?: string;
194
+ /** Workspace discovery visibility; contents retain normal durability. */
195
+ background?: boolean;
196
+ callerAgentId?: string;
192
197
  idempotencyKey?: string;
193
198
  workspaceId?: string;
194
199
  agent?: Omit<CreateAgentRequestOptions, "workspaceId" | "onEvent" | "worktree" | "git" | "worktreeName" | "idempotencyKey" | "requestId">;
@@ -740,6 +745,7 @@ export declare class DaemonClient {
740
745
  signal?: AbortSignal;
741
746
  notifications?: boolean;
742
747
  }): OwnedSubscription<CorrelatedResponsePayload<"session.events.set_subscription.response">>;
748
+ private filterSupportedEventSelectors;
743
749
  observeAgents(options?: Omit<FetchAgentsOptions, "subscribe">): OwnedSubscription<FetchAgentsPayload>;
744
750
  observeWorkspaces(options?: Omit<FetchWorkspacesOptions, "subscribe">): OwnedSubscription<FetchWorkspacesPayload>;
745
751
  fetchAgents(options: FetchAgentsOptions & {
@@ -827,6 +833,8 @@ export declare class DaemonClient {
827
833
  fetchAgent(agentId: string, requestId?: string): Promise<FetchAgentResult | null>;
828
834
  fetchAgent(agentId: string, options?: LegacyFetchAgentOptions): Promise<FetchAgentResult | null>;
829
835
  private readonly creations;
836
+ private requireBackgroundWorkspaces;
837
+ supportsBackgroundWorkspaces(): boolean;
830
838
  createAgent(options: CreateAgentRequestOptions): Promise<AgentSnapshotPayload>;
831
839
  private createLegacyAgent;
832
840
  deleteAgent(agentId: string): Promise<void>;
@@ -1139,7 +1147,12 @@ export declare class DaemonClient {
1139
1147
  }, requestId?: string): Promise<DirectorySuggestionsPayload>;
1140
1148
  private requestFileExplorer;
1141
1149
  listDirectory(cwd: string, path: string, requestId?: string): Promise<FileExplorerDirectoryPayload>;
1142
- readFile(cwd: string, path: string, requestId?: string, maxBytes?: number): Promise<FileReadResult>;
1150
+ /**
1151
+ * `timeout` bounds the whole transfer, not each frame. `0` disables the timer (as
1152
+ * `workspace.create` does) for files that legitimately take longer than the default over a
1153
+ * slow link; a dead connection still rejects on disconnect.
1154
+ */
1155
+ readFile(cwd: string, path: string, requestId?: string, maxBytes?: number, timeout?: number): Promise<FileReadResult>;
1143
1156
  observeFile(input: {
1144
1157
  cwd: string;
1145
1158
  path: string;
@@ -1397,6 +1410,7 @@ export declare class DaemonClient {
1397
1410
  private requireHubRelationshipSupport;
1398
1411
  private requireDaemonConfigReloadSupport;
1399
1412
  private requireSideConversationSupport;
1413
+ private requireCheckoutBaseRefSupport;
1400
1414
  private resolveTransportUrlForAttempt;
1401
1415
  private sendHelloMessage;
1402
1416
  private disposeTransport;
@@ -1,9 +1,11 @@
1
+ import { messageTooLargeError } from "./daemon-client-transport-utils.js";
1
2
  import { legacyUsageIcon } from "./legacy-usage-icons.js";
2
3
  import { subscribeTimeline } from "./timeline-subscription/index.js";
3
4
  import { ProviderSnapshotUpdates } from "./provider-snapshots/index.js";
4
5
  import { ConnectionSubscriptions, DEFAULT_CLIENT_CAPABILITIES, } from "./connection/index.js";
5
6
  import { CreationClient } from "./creation/index.js";
6
7
  import { CLIENT_CAPS } from "@camerontaylor/paseo-protocol/client-capabilities";
8
+ import { MAX_WEBSOCKET_MESSAGE_BYTES } from "@camerontaylor/paseo-protocol/websocket-message-limit";
7
9
  import { AgentCreateFailedStatusPayloadSchema, AgentCreatedStatusPayloadSchema, AgentRefreshedStatusPayloadSchema, AgentResumedStatusPayloadSchema, parseServerInfoStatusPayload, RestartRequestedStatusPayloadSchema, ShutdownRequestedStatusPayloadSchema, DaemonUpdateResponseSchema, SessionInboundMessageSchema, } from "@camerontaylor/paseo-protocol/messages";
8
10
  import { validateWSOutboundMessage } from "@camerontaylor/paseo-protocol/validation/ws-outbound";
9
11
  import { isRelayClientWebSocketUrl } from "@camerontaylor/paseo-protocol/daemon-endpoints";
@@ -246,6 +248,19 @@ function getTransportFrameSize(frame) {
246
248
  }
247
249
  return frame.byteLength;
248
250
  }
251
+ function assertTransportFrameFits(frame) {
252
+ if (typeof frame !== "string") {
253
+ if (frame.byteLength > MAX_WEBSOCKET_MESSAGE_BYTES)
254
+ throw messageTooLargeError(frame.byteLength, MAX_WEBSOCKET_MESSAGE_BYTES);
255
+ return;
256
+ }
257
+ // UTF-8 needs at most three bytes per UTF-16 unit, so only a string near the limit is measured.
258
+ if (frame.length * 3 <= MAX_WEBSOCKET_MESSAGE_BYTES)
259
+ return;
260
+ const size = new TextEncoder().encode(frame).byteLength;
261
+ if (size > MAX_WEBSOCKET_MESSAGE_BYTES)
262
+ throw messageTooLargeError(size, MAX_WEBSOCKET_MESSAGE_BYTES);
263
+ }
249
264
  function describeInboundTransportFrame(frame, rawBytes) {
250
265
  if (typeof frame === "string") {
251
266
  return { kind: "text", size: String(frame.length) };
@@ -288,6 +303,34 @@ function toReasonCode(reason) {
288
303
  export function supportsUsageReports(features) {
289
304
  return features?.usageSources === true || features?.providerUsageList === true;
290
305
  }
306
+ /**
307
+ * Event selectors a host only understands behind a handshake feature. Anything absent from this
308
+ * map is part of the base protocol and always sent. The client drops mapped selectors the
309
+ * connected host does not advertise, so an older daemon never parses a selector it does not know.
310
+ */
311
+ const EVENT_SELECTOR_FEATURES = {
312
+ // COMPAT(sideConversationEvents): added in 0.12.0-beta.1.fork.1.
313
+ "agent.side_conversation.update": "sideConversationEvents",
314
+ "agent.side_conversation.removed": "sideConversationEvents",
315
+ // COMPAT(durableAgentQueueV1): added in 0.12.0-beta.1.fork.1. An old daemon rejects the whole
316
+ // subscription request when it parses an unknown selector, so this one is dropped before send.
317
+ "agent.queue.update": "durableAgentQueueV1",
318
+ };
319
+ function buildFetchAgentsRequest(options, requestId) {
320
+ return SessionInboundMessageSchema.parse({
321
+ type: "fetch_agents_request",
322
+ requestId,
323
+ ...(options?.scope ? { scope: options.scope } : {}),
324
+ ...(options?.filter ? { filter: options.filter } : {}),
325
+ ...(options?.sort ? { sort: options.sort } : {}),
326
+ ...(options?.page ? { page: options.page } : {}),
327
+ ...(options?.subscribe ? { subscribe: options.subscribe } : {}),
328
+ ...(options?.sync ? { sync: options.sync } : {}),
329
+ ...(options?.includeBackground !== undefined
330
+ ? { includeBackground: options.includeBackground }
331
+ : {}),
332
+ });
333
+ }
291
334
  export class DaemonClient {
292
335
  constructor(config) {
293
336
  this.config = config;
@@ -813,6 +856,8 @@ export class DaemonClient {
813
856
  if (!this.transport) {
814
857
  throw new DaemonConnectionError("Transport not connected");
815
858
  }
859
+ // The daemon closes the connection on an oversized message, and creations resubmit on reconnect.
860
+ assertTransportFrameFits(frame);
816
861
  const isOpen = this.beginTraceSection("paseo.ws.frame.outbound", {
817
862
  kind: typeof frame === "string" ? "text" : "binary",
818
863
  size: String(getTransportFrameSize(frame)),
@@ -1246,7 +1291,8 @@ export class DaemonClient {
1246
1291
  return this.owned.observeRequest(message, async (query, accept) => {
1247
1292
  resetSource = false;
1248
1293
  const requestId = this.createRequestId(options?.requestId);
1249
- const request = SessionInboundMessageSchema.parse({ ...query, requestId });
1294
+ const prepared = options?.prepare ? options.prepare({ ...query }) : query;
1295
+ const request = SessionInboundMessageSchema.parse({ ...prepared, requestId });
1250
1296
  try {
1251
1297
  return await this.sendCorrelatedRequest({
1252
1298
  message: request,
@@ -1287,17 +1333,37 @@ export class DaemonClient {
1287
1333
  type: "session.events.set_subscription.request",
1288
1334
  events,
1289
1335
  notifications: options?.notifications,
1290
- }, options);
1336
+ }, {
1337
+ signal: options?.signal,
1338
+ prepare: (message) => ({
1339
+ ...message,
1340
+ // Re-evaluated on every send, including the restore after a reconnect, so a downgrade
1341
+ // to a host without the feature never sees the selector.
1342
+ events: this.filterSupportedEventSelectors(message.events),
1343
+ }),
1344
+ });
1345
+ }
1346
+ filterSupportedEventSelectors(events) {
1347
+ return events.filter((event) => {
1348
+ const feature = EVENT_SELECTOR_FEATURES[event];
1349
+ return feature === undefined || this.lastServerInfoMessage?.features?.[feature] === true;
1350
+ });
1291
1351
  }
1292
1352
  observeAgents(options = {}) {
1353
+ if (options.includeBackground || options.filter?.includeBackground)
1354
+ this.requireBackgroundWorkspaces();
1293
1355
  const { signal, requestId, timeout, ...query } = options;
1294
1356
  return this.observe("fetch_agents_response", { ...query, type: "fetch_agents_request", subscribe: {} }, { signal, requestId, timeout });
1295
1357
  }
1296
1358
  observeWorkspaces(options = {}) {
1359
+ if (options.includeBackground || options.filter?.includeBackground)
1360
+ this.requireBackgroundWorkspaces();
1297
1361
  const { signal, requestId, ...query } = options;
1298
1362
  return this.observe("fetch_workspaces_response", { ...query, type: "fetch_workspaces_request", subscribe: {} }, { signal, requestId });
1299
1363
  }
1300
1364
  async fetchAgents(options) {
1365
+ if (options?.includeBackground || options?.filter?.includeBackground)
1366
+ this.requireBackgroundWorkspaces();
1301
1367
  if (options?.subscribe) {
1302
1368
  if (options.subscribe.subscriptionId !== undefined)
1303
1369
  throw new Error("Subscription IDs are assigned by the host");
@@ -1305,16 +1371,7 @@ export class DaemonClient {
1305
1371
  return { ...(await subscription.ready), subscription };
1306
1372
  }
1307
1373
  const resolvedRequestId = this.createRequestId(options?.requestId);
1308
- const message = SessionInboundMessageSchema.parse({
1309
- type: "fetch_agents_request",
1310
- requestId: resolvedRequestId,
1311
- ...(options?.scope ? { scope: options.scope } : {}),
1312
- ...(options?.filter ? { filter: options.filter } : {}),
1313
- ...(options?.sort ? { sort: options.sort } : {}),
1314
- ...(options?.page ? { page: options.page } : {}),
1315
- ...(options?.subscribe ? { subscribe: options.subscribe } : {}),
1316
- ...(options?.sync ? { sync: options.sync } : {}),
1317
- });
1374
+ const message = buildFetchAgentsRequest(options, resolvedRequestId);
1318
1375
  return this.sendRequest({
1319
1376
  requestId: resolvedRequestId,
1320
1377
  message,
@@ -1332,6 +1389,8 @@ export class DaemonClient {
1332
1389
  });
1333
1390
  }
1334
1391
  async fetchAgentHistory(options) {
1392
+ if (options?.filter?.includeBackground)
1393
+ this.requireBackgroundWorkspaces();
1335
1394
  const resolvedRequestId = this.createRequestId(options?.requestId);
1336
1395
  const message = SessionInboundMessageSchema.parse({
1337
1396
  type: "fetch_agent_history_request",
@@ -1383,6 +1442,8 @@ export class DaemonClient {
1383
1442
  });
1384
1443
  }
1385
1444
  async fetchWorkspaces(options) {
1445
+ if (options?.includeBackground || options?.filter?.includeBackground)
1446
+ this.requireBackgroundWorkspaces();
1386
1447
  if (options?.subscribe) {
1387
1448
  if (options.subscribe.subscriptionId !== undefined)
1388
1449
  throw new Error("Subscription IDs are assigned by the host");
@@ -1392,12 +1453,8 @@ export class DaemonClient {
1392
1453
  const resolvedRequestId = this.createRequestId(options?.requestId);
1393
1454
  const message = SessionInboundMessageSchema.parse({
1394
1455
  type: "fetch_workspaces_request",
1456
+ ...options,
1395
1457
  requestId: resolvedRequestId,
1396
- ...(options?.filter ? { filter: options.filter } : {}),
1397
- ...(options?.sort ? { sort: options.sort } : {}),
1398
- ...(options?.page ? { page: options.page } : {}),
1399
- ...(options?.subscribe ? { subscribe: options.subscribe } : {}),
1400
- ...(options?.sync ? { sync: options.sync } : {}),
1401
1458
  });
1402
1459
  return this.sendRequest({
1403
1460
  requestId: resolvedRequestId,
@@ -1626,7 +1683,18 @@ export class DaemonClient {
1626
1683
  }
1627
1684
  return { agent: payload.agent, project: payload.project ?? null };
1628
1685
  }
1686
+ // Background workspaces are gated once at the transport boundary.
1687
+ requireBackgroundWorkspaces() {
1688
+ if (this.lastServerInfoMessage?.features?.backgroundWorkspaces !== true) {
1689
+ throw new Error("Update the host to use background workspaces.");
1690
+ }
1691
+ }
1692
+ supportsBackgroundWorkspaces() {
1693
+ return this.lastServerInfoMessage?.features?.backgroundWorkspaces === true;
1694
+ }
1629
1695
  async createAgent(options) {
1696
+ if (options.background !== undefined)
1697
+ this.requireBackgroundWorkspaces();
1630
1698
  const result = await this.creations.createAgent({
1631
1699
  ...options,
1632
1700
  config: resolveAgentConfig(options),
@@ -1656,6 +1724,7 @@ export class DaemonClient {
1656
1724
  ...(options.git ? { git: options.git } : {}),
1657
1725
  ...(options.worktree ? { worktree: options.worktree } : {}),
1658
1726
  ...(options.autoArchive !== undefined ? { autoArchive: options.autoArchive } : {}),
1727
+ ...(options.background !== undefined ? { background: options.background } : {}),
1659
1728
  ...(options.worktreeName ? { worktreeName: options.worktreeName } : {}),
1660
1729
  ...(options.labels && Object.keys(options.labels).length > 0
1661
1730
  ? { labels: options.labels }
@@ -2212,6 +2281,10 @@ export class DaemonClient {
2212
2281
  // Agent Interaction
2213
2282
  // ============================================================================
2214
2283
  async sendAgentMessage(agentId, text, options) {
2284
+ if (options?.sourceAgentId &&
2285
+ this.lastServerInfoMessage?.features?.agentMessageProvenance !== true) {
2286
+ throw new Error("Update the Paseo host to send messages with agent provenance.");
2287
+ }
2215
2288
  const requestId = this.createRequestId();
2216
2289
  const messageId = options?.messageId ?? crypto.randomUUID();
2217
2290
  const message = SessionInboundMessageSchema.parse({
@@ -2219,6 +2292,7 @@ export class DaemonClient {
2219
2292
  requestId,
2220
2293
  agentId,
2221
2294
  text,
2295
+ sourceAgentId: options?.sourceAgentId,
2222
2296
  ...(messageId ? { messageId } : {}),
2223
2297
  ...(options?.activeTurnBehavior ? { activeTurnBehavior: options.activeTurnBehavior } : {}),
2224
2298
  ...(options?.images ? { images: options.images } : {}),
@@ -3127,6 +3201,7 @@ export class DaemonClient {
3127
3201
  });
3128
3202
  }
3129
3203
  async setCheckoutBaseRef(input) {
3204
+ this.requireCheckoutBaseRefSupport();
3130
3205
  return this.sendCorrelatedSessionRequest({
3131
3206
  requestId: input.requestId,
3132
3207
  message: {
@@ -3216,6 +3291,10 @@ export class DaemonClient {
3216
3291
  }
3217
3292
  async createWorkspace(input, requestId) {
3218
3293
  const resolvedRequestId = this.createRequestId(requestId ?? input.requestId);
3294
+ if (input.background !== undefined || input.callerAgentId)
3295
+ this.requireBackgroundWorkspaces();
3296
+ if (input.agent?.background !== undefined)
3297
+ throw new Error("Configure background on the workspace, not its initial agent.");
3219
3298
  const result = await this.creations.createWorkspace({
3220
3299
  ...input,
3221
3300
  requestId: resolvedRequestId,
@@ -3243,6 +3322,8 @@ export class DaemonClient {
3243
3322
  ? { idempotencyKey: input.idempotencyKey }
3244
3323
  : {}),
3245
3324
  ...(input.title !== undefined ? { title: input.title } : {}),
3325
+ ...(input.background !== undefined ? { background: input.background } : {}),
3326
+ ...(input.callerAgentId ? { callerAgentId: input.callerAgentId } : {}),
3246
3327
  ...(input.firstAgentContext !== undefined
3247
3328
  ? { firstAgentContext: input.firstAgentContext }
3248
3329
  : {}),
@@ -3320,7 +3401,7 @@ export class DaemonClient {
3320
3401
  // ============================================================================
3321
3402
  // File Explorer
3322
3403
  // ============================================================================
3323
- async requestFileExplorer(cwd, path, mode, requestId, acceptBinary = false, maxBytes) {
3404
+ async requestFileExplorer(cwd, path, mode, requestId, acceptBinary = false, maxBytes, timeout) {
3324
3405
  return this.sendCorrelatedSessionRequest({
3325
3406
  requestId,
3326
3407
  message: {
@@ -3332,6 +3413,7 @@ export class DaemonClient {
3332
3413
  ...(maxBytes ? { maxBytes } : {}),
3333
3414
  },
3334
3415
  responseType: "file_explorer_response",
3416
+ timeout,
3335
3417
  });
3336
3418
  }
3337
3419
  async listDirectory(cwd, path, requestId) {
@@ -3344,11 +3426,16 @@ export class DaemonClient {
3344
3426
  }
3345
3427
  return payload.directory;
3346
3428
  }
3347
- async readFile(cwd, path, requestId, maxBytes) {
3429
+ /**
3430
+ * `timeout` bounds the whole transfer, not each frame. `0` disables the timer (as
3431
+ * `workspace.create` does) for files that legitimately take longer than the default over a
3432
+ * slow link; a dead connection still rejects on disconnect.
3433
+ */
3434
+ async readFile(cwd, path, requestId, maxBytes, timeout) {
3348
3435
  const resolvedRequestId = this.createRequestId(requestId);
3349
3436
  this.pendingBinaryFileReads.set(resolvedRequestId, { cwd, path, maxBytes });
3350
3437
  try {
3351
- const payload = await this.requestFileExplorer(cwd, path, "file", resolvedRequestId, true, maxBytes);
3438
+ const payload = await this.requestFileExplorer(cwd, path, "file", resolvedRequestId, true, maxBytes, timeout);
3352
3439
  if (payload.error) {
3353
3440
  throw new Error(payload.error);
3354
3441
  }
@@ -4039,6 +4126,10 @@ export class DaemonClient {
4039
4126
  requestId,
4040
4127
  message: { type: `plugin.${action}.request`, requestId, pluginId },
4041
4128
  responseType: `plugin.${action}.response`,
4129
+ // Reload tears the plugin down and starts it again before the daemon answers, and it queues
4130
+ // behind other plugin lifecycle work. Wait for that answer instead of failing the reload at
4131
+ // the request deadline while the daemon keeps loading it.
4132
+ ...(action === "reload" ? { timeout: 0 } : {}),
4042
4133
  });
4043
4134
  return payload.plugin;
4044
4135
  }
@@ -4512,6 +4603,13 @@ export class DaemonClient {
4512
4603
  throw new Error("Update the host to use side conversations.");
4513
4604
  }
4514
4605
  }
4606
+ requireCheckoutBaseRefSupport() {
4607
+ // COMPAT(checkoutBaseRefSet): added in fork v0.10.1, remove after 2027-09-30. An older
4608
+ // daemon cannot parse the request, so the client must not send it.
4609
+ if (this.lastServerInfoMessage?.features?.checkoutBaseRefSet !== true) {
4610
+ throw new Error("Update the host to change the comparison base.");
4611
+ }
4612
+ }
4515
4613
  resolveTransportUrlForAttempt() {
4516
4614
  return this.config.url;
4517
4615
  }
@@ -4775,6 +4873,12 @@ export class DaemonClient {
4775
4873
  });
4776
4874
  return;
4777
4875
  }
4876
+ const received = transfer.chunks.reduce((total, chunk) => total + chunk.byteLength, 0);
4877
+ if (received !== transfer.size) {
4878
+ this.activeBinaryFileTransfers.delete(frame.requestId);
4879
+ this.rejectWaitersForRequestId(frame.requestId, new Error(`File transfer incomplete: expected ${transfer.size} bytes, received ${received}.`));
4880
+ return;
4881
+ }
4778
4882
  const bytes = concatByteChunks(transfer.chunks, transfer.size);
4779
4883
  this.activeBinaryFileTransfers.delete(frame.requestId);
4780
4884
  this.completedBinaryFileReads.set(frame.requestId, {
@@ -5157,7 +5261,7 @@ export class DaemonClient {
5157
5261
  }
5158
5262
  }
5159
5263
  function resolveAgentConfig(options) {
5160
- const { config, provider, cwd, agentId: _agentId, onEvent: _onEvent, idempotencyKey: _idempotencyKey, clientMessageId: _clientMessageId, callerAgentId: _callerAgentId, outputSchema: _outputSchema, attachments: _attachments, worktree: _worktree, autoArchive: _autoArchive, env: _env, workspaceId: _workspaceId, initialPrompt: _initialPrompt, images: _images, git: _git, worktreeName: _worktreeName, requestId: _requestId, labels: _labels, ...overrides } = options;
5264
+ const { config, provider, cwd, agentId: _agentId, onEvent: _onEvent, idempotencyKey: _idempotencyKey, clientMessageId: _clientMessageId, callerAgentId: _callerAgentId, outputSchema: _outputSchema, attachments: _attachments, worktree: _worktree, autoArchive: _autoArchive, background: _background, env: _env, workspaceId: _workspaceId, initialPrompt: _initialPrompt, images: _images, git: _git, worktreeName: _worktreeName, requestId: _requestId, labels: _labels, ...overrides } = options;
5161
5265
  const baseConfig = {
5162
5266
  ...(provider ? { provider } : {}),
5163
5267
  ...(cwd ? { cwd } : {}),
package/dist/index.d.ts CHANGED
@@ -178,6 +178,8 @@ export interface PaseoAgentCreateOptions {
178
178
  git?: CreateAgentRequestMessage["git"];
179
179
  worktree?: CreateAgentRequestMessage["worktree"];
180
180
  autoArchive?: CreateAgentRequestMessage["autoArchive"];
181
+ /** Visibility intent for a NEW workspace. Existing targets reject this option. */
182
+ background?: CreateAgentRequestMessage["background"];
181
183
  requestId?: string;
182
184
  labels?: Record<string, string>;
183
185
  }
package/dist/index.js CHANGED
@@ -224,9 +224,12 @@ function createWorkspaceHandleFactory(daemonClient, createAgent, terminals, list
224
224
  const refresh = async (options) => {
225
225
  let cursor;
226
226
  let requestId = options?.requestId;
227
+ // A ref addresses one workspace by id, so a background one resolves too.
228
+ const filter = daemonClient.supportsBackgroundWorkspaces() ? { includeBackground: true } : {};
227
229
  do {
228
230
  const result = await daemonClient.fetchWorkspaces({
229
231
  requestId,
232
+ filter,
230
233
  page: { limit: 200, ...(cursor ? { cursor } : {}) },
231
234
  });
232
235
  const match = result.entries.find((entry) => entry.id === id);
@@ -450,7 +453,8 @@ async function waitForProvidersReady(daemonClient, observation, signal, options
450
453
  const { timeoutMs = 60000, ...snapshotOptions } = options;
451
454
  try {
452
455
  await observation.ready;
453
- signal.throwIfAborted();
456
+ if (signal.aborted)
457
+ throw new Error("Paseo API is disposed");
454
458
  return await new Promise((resolve, reject) => {
455
459
  let settled = false;
456
460
  let requestId = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camerontaylor/paseo-client",
3
- "version": "0.11.2-fork.2",
3
+ "version": "0.12.0-fork.1",
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
- "@camerontaylor/paseo-protocol": "0.11.2-fork.2",
43
- "@camerontaylor/paseo-relay": "0.11.2-fork.2",
42
+ "@camerontaylor/paseo-protocol": "0.12.0-fork.1",
43
+ "@camerontaylor/paseo-relay": "0.12.0-fork.1",
44
44
  "zod": "^4.4.3"
45
45
  },
46
46
  "devDependencies": {