@hyperdrive.bot/fleet-client 0.3.218 → 0.3.221

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.
Files changed (37) hide show
  1. package/README.md +39 -7
  2. package/dist/compat/normalize-provider-models.d.ts +2 -2
  3. package/dist/compat/normalize-provider-models.js +13 -4
  4. package/dist/connection/index.d.ts +50 -0
  5. package/dist/connection/index.js +119 -0
  6. package/dist/connection/legacy-workspaces.d.ts +19 -0
  7. package/dist/connection/legacy-workspaces.js +160 -0
  8. package/dist/connection/legacy.d.ts +31 -0
  9. package/dist/connection/legacy.js +245 -0
  10. package/dist/connection/owned.d.ts +36 -0
  11. package/dist/connection/owned.js +210 -0
  12. package/dist/creation/index.d.ts +40 -0
  13. package/dist/creation/index.js +197 -0
  14. package/dist/daemon-client-relay-e2ee-transport.d.ts +1 -0
  15. package/dist/daemon-client-relay-e2ee-transport.js +10 -6
  16. package/dist/daemon-client-transport-types.d.ts +1 -1
  17. package/dist/daemon-client-transport-utils.d.ts +5 -1
  18. package/dist/daemon-client-transport-utils.js +8 -6
  19. package/dist/daemon-client-transport.d.ts +2 -2
  20. package/dist/daemon-client-transport.js +2 -2
  21. package/dist/daemon-client-websocket-transport.d.ts +4 -0
  22. package/dist/daemon-client-websocket-transport.js +23 -3
  23. package/dist/daemon-client.d.ts +1372 -925
  24. package/dist/daemon-client.js +4073 -2691
  25. package/dist/index.d.ts +197 -66
  26. package/dist/index.js +435 -72
  27. package/dist/legacy-usage-icons.d.ts +2 -0
  28. package/dist/legacy-usage-icons.js +16 -0
  29. package/dist/provider-snapshots/index.d.ts +21 -0
  30. package/dist/provider-snapshots/index.js +56 -0
  31. package/dist/terminal-stream-router.d.ts +7 -10
  32. package/dist/terminal-stream-router.js +41 -87
  33. package/dist/terminals/index.d.ts +69 -0
  34. package/dist/terminals/index.js +103 -0
  35. package/dist/timeline-subscription/index.d.ts +25 -0
  36. package/dist/timeline-subscription/index.js +56 -0
  37. package/package.json +10 -6
@@ -0,0 +1,56 @@
1
+ /** Resolve announcements only while observed, coalescing changes during a fetch. */
2
+ export class ProviderSnapshotUpdates {
3
+ constructor(host) {
4
+ this.host = host;
5
+ this.pending = new Map();
6
+ }
7
+ clear() {
8
+ this.pending.clear();
9
+ }
10
+ receive(update) {
11
+ if (!this.host.active(update))
12
+ return;
13
+ const key = JSON.stringify([update.payload.subscriptionId, update.payload.cwd]);
14
+ const pending = this.pending.get(key) ?? { latest: update, running: false };
15
+ pending.latest = update;
16
+ this.pending.set(key, pending);
17
+ if (pending.running)
18
+ return;
19
+ pending.running = true;
20
+ void this.resolve(key, pending);
21
+ }
22
+ async resolve(key, pending) {
23
+ try {
24
+ while (this.pending.get(key) === pending) {
25
+ const announced = pending.latest;
26
+ if (!this.host.active(announced))
27
+ return;
28
+ const snapshot = await this.host.fetch(announced.payload.cwd);
29
+ if (!this.host.active(announced))
30
+ return;
31
+ if (this.pending.get(key) !== pending)
32
+ return;
33
+ // A change arriving while the body was in flight needs the current body.
34
+ // If that response already covers it, no second fetch is necessary.
35
+ if (pending.latest !== announced &&
36
+ pending.latest.payload.snapshotHash !== snapshot.snapshotHash)
37
+ continue;
38
+ this.pending.delete(key);
39
+ this.host.emit({
40
+ type: "providers_snapshot_update",
41
+ payload: { ...snapshot, subscriptionId: announced.payload.subscriptionId },
42
+ });
43
+ return;
44
+ }
45
+ }
46
+ catch (error) {
47
+ if (this.pending.get(key) === pending)
48
+ this.host.failed(error);
49
+ }
50
+ finally {
51
+ if (this.pending.get(key) === pending)
52
+ this.pending.delete(key);
53
+ }
54
+ }
55
+ }
56
+ //# sourceMappingURL=index.js.map
@@ -1,28 +1,25 @@
1
1
  import { type TerminalStreamFrame } from "@hyperdrive.bot/fleet-protocol/binary-frames/index";
2
- import type { TerminalInput, TerminalState } from "@hyperdrive.bot/fleet-protocol/messages";
2
+ import type { TerminalState } from "@hyperdrive.bot/fleet-protocol/messages";
3
3
  export type TerminalStreamEvent = {
4
4
  terminalId: string;
5
+ subscriptionId: string;
6
+ } & ({
5
7
  type: "output";
6
8
  data: Uint8Array;
7
9
  } | {
8
- terminalId: string;
9
10
  type: "snapshot";
10
11
  state: TerminalState;
11
12
  } | {
12
- terminalId: string;
13
13
  type: "restore";
14
14
  data: Uint8Array;
15
- };
15
+ });
16
16
  export declare class TerminalStreamRouter {
17
- private readonly terminalSlots;
18
- private readonly slotTerminals;
17
+ private readonly slots;
19
18
  private readonly listeners;
19
+ /** Passive wire diagnostics. App state consumes the registration's callback. */
20
20
  onEvent(handler: (event: TerminalStreamEvent) => void): () => void;
21
- setSlot(terminalId: string, slot: number): void;
22
- removeTerminal(terminalId: string): void;
21
+ attach(subscriptionId: string, terminalId: string, slot: number, receive: (event: TerminalStreamEvent) => void): () => void;
23
22
  clearSlots(): void;
24
- encodeInput(terminalId: string, message: TerminalInput["message"]): Uint8Array | null;
25
23
  handleFrame(frame: TerminalStreamFrame): void;
26
- private emit;
27
24
  }
28
25
  //# sourceMappingURL=terminal-stream-router.d.ts.map
@@ -1,106 +1,60 @@
1
- import { decodeTerminalSnapshotPayload, encodeTerminalResizePayload, encodeTerminalStreamFrame, TerminalStreamOpcode, } from "@hyperdrive.bot/fleet-protocol/binary-frames/index";
1
+ import { decodeTerminalSnapshotPayload, TerminalStreamOpcode, } from "@hyperdrive.bot/fleet-protocol/binary-frames/index";
2
2
  export class TerminalStreamRouter {
3
3
  constructor() {
4
- this.terminalSlots = new Map();
5
- this.slotTerminals = new Map();
4
+ this.slots = new Map();
6
5
  this.listeners = new Set();
7
6
  }
7
+ /** Passive wire diagnostics. App state consumes the registration's callback. */
8
8
  onEvent(handler) {
9
9
  this.listeners.add(handler);
10
10
  return () => {
11
11
  this.listeners.delete(handler);
12
12
  };
13
13
  }
14
- setSlot(terminalId, slot) {
15
- const existingTerminalId = this.slotTerminals.get(slot);
16
- if (existingTerminalId && existingTerminalId !== terminalId) {
17
- this.terminalSlots.delete(existingTerminalId);
18
- }
19
- const existingSlot = this.terminalSlots.get(terminalId);
20
- if (typeof existingSlot === "number" && existingSlot !== slot) {
21
- this.slotTerminals.delete(existingSlot);
22
- }
23
- this.terminalSlots.set(terminalId, slot);
24
- this.slotTerminals.set(slot, terminalId);
25
- }
26
- removeTerminal(terminalId) {
27
- const slot = this.terminalSlots.get(terminalId);
28
- if (typeof slot !== "number") {
29
- return;
30
- }
31
- this.terminalSlots.delete(terminalId);
32
- if (this.slotTerminals.get(slot) === terminalId) {
33
- this.slotTerminals.delete(slot);
34
- }
14
+ attach(subscriptionId, terminalId, slot, receive) {
15
+ const registration = { subscriptionId, terminalId, receive };
16
+ const registrations = this.slots.get(slot) ?? new Set();
17
+ registrations.add(registration);
18
+ this.slots.set(slot, registrations);
19
+ return () => {
20
+ registrations.delete(registration);
21
+ if (registrations.size === 0)
22
+ this.slots.delete(slot);
23
+ };
35
24
  }
36
25
  clearSlots() {
37
- this.terminalSlots.clear();
38
- this.slotTerminals.clear();
39
- }
40
- encodeInput(terminalId, message) {
41
- const slot = this.terminalSlots.get(terminalId);
42
- if (typeof slot !== "number") {
43
- return null;
44
- }
45
- if (message.type === "input") {
46
- return encodeTerminalStreamFrame({
47
- opcode: TerminalStreamOpcode.Input,
48
- slot,
49
- payload: message.data,
50
- });
51
- }
52
- if (message.type === "resize") {
53
- return encodeTerminalStreamFrame({
54
- opcode: TerminalStreamOpcode.Resize,
55
- slot,
56
- payload: encodeTerminalResizePayload({
57
- rows: message.rows,
58
- cols: message.cols,
59
- }),
60
- });
61
- }
62
- return null;
26
+ this.slots.clear();
63
27
  }
64
28
  handleFrame(frame) {
65
- const terminalId = this.slotTerminals.get(frame.slot);
66
- if (!terminalId) {
67
- return;
68
- }
69
- if (frame.opcode === TerminalStreamOpcode.Output) {
70
- this.emit({
71
- terminalId,
72
- type: "output",
73
- data: frame.payload,
74
- });
29
+ const registrations = this.slots.get(frame.slot);
30
+ if (!registrations)
75
31
  return;
76
- }
77
- if (frame.opcode === TerminalStreamOpcode.Restore) {
78
- this.emit({
79
- terminalId,
80
- type: "restore",
81
- data: frame.payload,
82
- });
83
- return;
84
- }
85
- if (frame.opcode === TerminalStreamOpcode.Snapshot) {
86
- const state = decodeTerminalSnapshotPayload(frame.payload);
87
- if (!state) {
88
- return;
32
+ for (const registration of registrations) {
33
+ const identity = {
34
+ subscriptionId: registration.subscriptionId,
35
+ terminalId: registration.terminalId,
36
+ };
37
+ let event;
38
+ if (frame.opcode === TerminalStreamOpcode.Output)
39
+ event = { ...identity, type: "output", data: frame.payload };
40
+ else if (frame.opcode === TerminalStreamOpcode.Restore)
41
+ event = { ...identity, type: "restore", data: frame.payload };
42
+ else if (frame.opcode === TerminalStreamOpcode.Snapshot) {
43
+ const state = decodeTerminalSnapshotPayload(frame.payload);
44
+ if (!state)
45
+ return;
46
+ event = { ...identity, type: "snapshot", state };
89
47
  }
90
- this.emit({
91
- terminalId,
92
- type: "snapshot",
93
- state,
94
- });
95
- }
96
- }
97
- emit(event) {
98
- for (const listener of this.listeners) {
99
- try {
100
- listener(event);
101
- }
102
- catch {
103
- // no-op
48
+ else
49
+ return;
50
+ registration.receive(event);
51
+ for (const listener of this.listeners) {
52
+ try {
53
+ listener(event);
54
+ }
55
+ catch {
56
+ /* Passive diagnostics cannot interrupt delivery. */
57
+ }
104
58
  }
105
59
  }
106
60
  }
@@ -0,0 +1,69 @@
1
+ import { z } from "zod";
2
+ import type { DaemonClient } from "../daemon-client.js";
3
+ declare const TerminalSchema: z.ZodObject<{
4
+ id: z.ZodString;
5
+ workspaceId: z.ZodString;
6
+ cwd: z.ZodString;
7
+ name: z.ZodString;
8
+ }, z.core.$strip>;
9
+ export type PaseoTerminal = z.infer<typeof TerminalSchema>;
10
+ export interface PaseoTerminalCreateOptions {
11
+ workspaceId: string;
12
+ /** Process working directory; defaults to the workspace directory. */
13
+ cwd?: string;
14
+ name?: string;
15
+ command?: string;
16
+ args?: string[];
17
+ size?: {
18
+ rows: number;
19
+ cols: number;
20
+ };
21
+ requestId?: string;
22
+ }
23
+ export interface PaseoTerminalListOptions {
24
+ /** Ownership filter. When supplied, cwd does not restrict the results. */
25
+ workspaceId?: string;
26
+ /** Workspace root directory filter for unscoped listings. */
27
+ cwd?: string;
28
+ requestId?: string;
29
+ }
30
+ export interface PaseoTerminalListResult {
31
+ entries: PaseoTerminal[];
32
+ requestId: string;
33
+ }
34
+ export interface PaseoTerminalCaptureOptions {
35
+ start?: number;
36
+ end?: number;
37
+ stripAnsi?: boolean;
38
+ requestId?: string;
39
+ }
40
+ export type PaseoTerminalCaptureResult = Awaited<ReturnType<DaemonClient["captureTerminal"]>>;
41
+ export interface PaseoTerminalHandle {
42
+ readonly id: string;
43
+ current(): PaseoTerminal | null;
44
+ refresh(options?: {
45
+ requestId?: string;
46
+ }): Promise<PaseoTerminal | null>;
47
+ /** Sends literal input and returns its UTF-16 length. Does not await command execution. */
48
+ write(data: string): number;
49
+ /** Expands CLI key tokens; other strings are literal. Returns the input's UTF-16 length. */
50
+ sendKeys(keys: readonly string[]): number;
51
+ capture(options?: PaseoTerminalCaptureOptions): Promise<PaseoTerminalCaptureResult>;
52
+ kill(requestId?: string): Promise<void>;
53
+ }
54
+ export interface PaseoTerminalActions {
55
+ create(options: PaseoTerminalCreateOptions): Promise<PaseoTerminalHandle>;
56
+ list(options?: PaseoTerminalListOptions): Promise<PaseoTerminalListResult>;
57
+ ref(terminal: string | PaseoTerminal): PaseoTerminalHandle;
58
+ }
59
+ export interface PaseoWorkspaceTerminalActions {
60
+ create(options?: Omit<PaseoTerminalCreateOptions, "workspaceId">): Promise<PaseoTerminalHandle>;
61
+ list(options?: {
62
+ requestId?: string;
63
+ }): Promise<PaseoTerminalListResult>;
64
+ }
65
+ type TerminalClient = Pick<DaemonClient, "ensureConnected" | "getLastServerInfoMessage" | "createTerminal" | "listTerminals" | "sendTerminalInput" | "captureTerminal" | "killTerminal">;
66
+ /** @package */
67
+ export declare function createTerminalActions(daemonClient: TerminalClient, resolveWorkspaceDirectory: (workspaceId: string) => Promise<string>): PaseoTerminalActions;
68
+ export {};
69
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,103 @@
1
+ import { z } from "zod";
2
+ const TerminalSchema = z.object({
3
+ id: z.string(),
4
+ workspaceId: z.string(),
5
+ cwd: z.string(),
6
+ name: z.string(),
7
+ });
8
+ /** @package */
9
+ export function createTerminalActions(daemonClient, resolveWorkspaceDirectory) {
10
+ function client() {
11
+ daemonClient.ensureConnected();
12
+ // COMPAT(workspaceTerminals): added in v0.7.3, remove gate after 2027-09-05.
13
+ if (daemonClient.getLastServerInfoMessage()?.features?.workspaceTerminals !== true) {
14
+ throw new Error("Update the host to use workspace terminals through the SDK.");
15
+ }
16
+ return daemonClient;
17
+ }
18
+ const list = async (options = {}) => {
19
+ const result = await client().listTerminals(options.cwd, options.requestId, {
20
+ workspaceId: options.workspaceId,
21
+ });
22
+ return {
23
+ entries: result.terminals.map((terminal) => TerminalSchema.parse(terminal)),
24
+ requestId: result.requestId,
25
+ };
26
+ };
27
+ const ref = (terminal) => {
28
+ const id = typeof terminal === "string" ? terminal : terminal.id;
29
+ let current = typeof terminal === "string" ? null : terminal;
30
+ const write = (data) => {
31
+ client().sendTerminalInput(id, { type: "input", data });
32
+ return data.length;
33
+ };
34
+ return {
35
+ id,
36
+ current: () => current,
37
+ refresh: async (options) => {
38
+ const result = await list(options);
39
+ current = result.entries.find((entry) => entry.id === id) ?? null;
40
+ return current;
41
+ },
42
+ write,
43
+ sendKeys: (keys) => write(keys.map(resolveKeyToken).join("")),
44
+ capture: (options = {}) => {
45
+ const { requestId, ...captureOptions } = options;
46
+ return client().captureTerminal(id, captureOptions, requestId);
47
+ },
48
+ kill: async (requestId) => {
49
+ const result = await client().killTerminal(id, requestId);
50
+ if (!result.success)
51
+ throw new Error(`Failed to kill terminal ${id}`);
52
+ current = null;
53
+ },
54
+ };
55
+ };
56
+ return {
57
+ create: async ({ workspaceId, cwd, name, requestId, ...options }) => {
58
+ const driver = client();
59
+ if (!workspaceId)
60
+ throw new Error("workspaceId is required");
61
+ const directory = cwd ?? (await resolveWorkspaceDirectory(workspaceId));
62
+ const result = await driver.createTerminal(directory, name, requestId, {
63
+ ...options,
64
+ workspaceId,
65
+ });
66
+ if (result.error || !result.terminal) {
67
+ throw new Error(result.error ?? "The daemon did not create a terminal");
68
+ }
69
+ return ref(TerminalSchema.parse(result.terminal));
70
+ },
71
+ list,
72
+ ref,
73
+ };
74
+ }
75
+ function resolveKeyToken(key) {
76
+ switch (key) {
77
+ case "Enter":
78
+ return "\r";
79
+ case "Tab":
80
+ return "\t";
81
+ case "Escape":
82
+ return "\u001b";
83
+ case "Space":
84
+ return " ";
85
+ case "BSpace":
86
+ return "\u007f";
87
+ case "C-c":
88
+ return "\u0003";
89
+ case "C-d":
90
+ return "\u0004";
91
+ case "C-z":
92
+ return "\u001a";
93
+ case "C-l":
94
+ return "\u000c";
95
+ case "C-a":
96
+ return "\u0001";
97
+ case "C-e":
98
+ return "\u0005";
99
+ default:
100
+ return key;
101
+ }
102
+ }
103
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,25 @@
1
+ import type { SessionOutboundMessage } from "@hyperdrive.bot/fleet-protocol/messages";
2
+ import type { OwnedSubscription, TimelineSubscription } from "../connection/index.js";
3
+ type TimelineUpdate = Extract<SessionOutboundMessage, {
4
+ type: "agent_stream" | "agent.timeline.replacement";
5
+ }>;
6
+ /** Local SDK lifecycle messages; these are not additional wire RPCs. */
7
+ export type TimelineMessage = TimelineUpdate | {
8
+ type: "agent.timeline.subscription_restored";
9
+ payload: {
10
+ agentId: string;
11
+ subscriptionId: string;
12
+ };
13
+ } | {
14
+ type: "agent.timeline.error";
15
+ payload: {
16
+ agentId: string;
17
+ error: string;
18
+ };
19
+ };
20
+ /** Restores live delivery; the consumer owns history reads and gap recovery. */
21
+ export declare function subscribeTimeline(agentId: string, observation: OwnedSubscription<{
22
+ agentIds: string[];
23
+ }>, handler: (message: TimelineMessage) => void, reportError: (error: unknown) => void): TimelineSubscription;
24
+ export {};
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,56 @@
1
+ /** Restores live delivery; the consumer owns history reads and gap recovery. */
2
+ export function subscribeTimeline(agentId, observation, handler, reportError) {
3
+ let established = false;
4
+ let released = false;
5
+ const notify = (message) => {
6
+ if (released)
7
+ return;
8
+ try {
9
+ handler(message);
10
+ }
11
+ catch (error) {
12
+ reportError(error);
13
+ }
14
+ };
15
+ const release = () => {
16
+ released = true;
17
+ return observation.release();
18
+ };
19
+ const stop = () => {
20
+ // Use the public release entry point so the owning API also forgets this handle.
21
+ void subscription.release().catch(reportError);
22
+ };
23
+ const fail = (error) => {
24
+ notify({
25
+ type: "agent.timeline.error",
26
+ payload: { agentId, error: error instanceof Error ? error.message : String(error) },
27
+ });
28
+ stop();
29
+ };
30
+ observation.subscribe({
31
+ snapshot: ({ subscriptionId }) => {
32
+ if (!established) {
33
+ established = true;
34
+ return;
35
+ }
36
+ notify({
37
+ type: "agent.timeline.subscription_restored",
38
+ payload: { agentId, subscriptionId },
39
+ });
40
+ },
41
+ update: (message) => {
42
+ if (message.type === "agent_stream" || message.type === "agent.timeline.replacement")
43
+ notify(message);
44
+ },
45
+ error: fail,
46
+ });
47
+ const subscription = Object.assign(stop, {
48
+ ready: observation.ready.then(() => undefined),
49
+ release,
50
+ subscriptionId: null,
51
+ });
52
+ Object.defineProperty(subscription, "subscriptionId", { get: () => observation.subscriptionId });
53
+ void subscription.ready.catch(fail);
54
+ return subscription;
55
+ }
56
+ //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperdrive.bot/fleet-client",
3
- "version": "0.3.218",
3
+ "version": "0.3.221",
4
4
  "description": "Paseo client SDK package",
5
5
  "license": "Apache-2.0",
6
6
  "files": [
@@ -21,21 +21,25 @@
21
21
  "./internal/daemon-client-transport-types": {
22
22
  "types": "./dist/daemon-client-transport-types.d.ts",
23
23
  "default": "./dist/daemon-client-transport-types.js"
24
+ },
25
+ "./internal/daemon-client-websocket-transport": {
26
+ "types": "./dist/daemon-client-websocket-transport.d.ts",
27
+ "default": "./dist/daemon-client-websocket-transport.js"
24
28
  }
25
29
  },
26
30
  "scripts": {
27
31
  "clean": "node ../../scripts/clean-package-dist.mjs",
28
- "build": "npm run build --workspace=@hyperdrive.bot/fleet-protocol && tsc -p tsconfig.json --incremental false",
32
+ "build": "npm run build --workspace=@hyperdrive.bot/fleet-protocol && npm run build --workspace=@hyperdrive.bot/fleet-relay && tsc -p tsconfig.json --incremental false",
29
33
  "build:clean": "npm run clean && npm run build",
30
34
  "prepack": "npm run build:clean",
31
- "postpack": "node ../../scripts/personal-data-scan.mjs --npm-package .",
32
35
  "typecheck": "tsgo --noEmit",
33
36
  "typecheck:examples": "tsgo -p tsconfig.examples.json --noEmit",
34
- "test": "vitest run"
37
+ "test": "vitest run",
38
+ "postpack": "node ../../scripts/personal-data-scan.mjs --npm-package ."
35
39
  },
36
40
  "dependencies": {
37
- "@hyperdrive.bot/fleet-protocol": "0.3.218",
38
- "@hyperdrive.bot/fleet-relay": "0.3.218",
41
+ "@hyperdrive.bot/fleet-protocol": "0.3.221",
42
+ "@hyperdrive.bot/fleet-relay": "0.3.221",
39
43
  "zod": "^4.4.3"
40
44
  },
41
45
  "devDependencies": {