@byok-sdk/server 0.3.0 → 0.4.0

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
@@ -4,7 +4,7 @@ The self-hosted SaaS-side reference coordinator: pairing, authenticated device
4
4
  HTTP/WebSocket/long-poll transport, task leasing, approvals, and in-memory
5
5
  stores over the frozen v1 protocol.
6
6
 
7
- Use `@byok-sdk/cloud` plus `@byok-sdk/cloud-postgres` for the durable hosted
7
+ Use `@byok-sdk/cloud` plus `@byok-sdk/cloud-dataplane` for the durable hosted
8
8
  composition.
9
9
 
10
10
  Toolset-aware dispatch names logical device-local MCP toolsets; it never sends
@@ -21,6 +21,8 @@ const task = await server.dispatch({
21
21
  ```
22
22
 
23
23
  The self-hosted coordinator rejects this call before task creation unless the
24
- live device advertises `toolset-selection`.
24
+ live device advertises `toolset-selection` and its `configuredToolsets`
25
+ inventory contains every required ID. `machines.list()` projects that same
26
+ logical-ID-only inventory; MCP commands and credentials remain device-local.
25
27
 
26
28
  MIT licensed. Node.js 22.19.0 or newer.
package/dist/hub.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { WebSocket } from 'ws';
2
- import { type Envelope, type RuntimeId, type RuntimeInfo, type TaskState } from '@byok-sdk/protocol';
2
+ import { type Envelope, type RuntimeId, type RuntimeInfo, type TaskState, type ToolsetId } from '@byok-sdk/protocol';
3
3
  import type { DeviceRegistry } from './auth';
4
4
  import { RateLimiter } from './rate-limiter';
5
5
  import type { TaskStore } from './task-store';
@@ -276,7 +276,7 @@ export declare class ConnectionHub {
276
276
  * connection this hub never learns capabilities for simply reads back
277
277
  * `undefined` from {@link getDeviceCapabilities}.
278
278
  */
279
- registerConnection(deviceId: string, ws: WebSocket, runtimes: RuntimeInfo[] | undefined, capabilities?: readonly string[]): void;
279
+ registerConnection(deviceId: string, ws: WebSocket, runtimes: RuntimeInfo[] | undefined, capabilities?: readonly string[], configuredToolsets?: readonly ToolsetId[]): void;
280
280
  sendConnAck(deviceId: string, capabilities: string[]): void;
281
281
  /**
282
282
  * Reconnection procedure step 3 (§9): redeliver, in `seq` order, every
package/dist/index.js CHANGED
@@ -844,9 +844,16 @@ var ConnectionHub = class {
844
844
  * connection this hub never learns capabilities for simply reads back
845
845
  * `undefined` from {@link getDeviceCapabilities}.
846
846
  */
847
- registerConnection(deviceId, ws, runtimes, capabilities) {
847
+ registerConnection(deviceId, ws, runtimes, capabilities, configuredToolsets) {
848
848
  const at = (/* @__PURE__ */ new Date()).toISOString();
849
- this.connections.set(deviceId, { ws, connected: true, lastSeen: at, runtimes, capabilities });
849
+ this.connections.set(deviceId, {
850
+ ws,
851
+ connected: true,
852
+ lastSeen: at,
853
+ runtimes,
854
+ capabilities,
855
+ configuredToolsets
856
+ });
850
857
  this.serverEvents.push({ kind: "device.connected", deviceId, at });
851
858
  this.settleLongPollWaiter(deviceId);
852
859
  }
@@ -941,7 +948,13 @@ var ConnectionHub = class {
941
948
  const wasFreshlyConnected = !conn || conn.ws !== void 0 || !conn.connected;
942
949
  if (conn?.ws) {
943
950
  const ws = conn.ws;
944
- this.connections.set(deviceId, { connected: true, lastSeen: at, runtimes: conn.runtimes, capabilities: conn.capabilities });
951
+ this.connections.set(deviceId, {
952
+ connected: true,
953
+ lastSeen: at,
954
+ runtimes: conn.runtimes,
955
+ capabilities: conn.capabilities,
956
+ configuredToolsets: conn.configuredToolsets
957
+ });
945
958
  ws.close(1e3, "superseded by long-poll connection");
946
959
  } else if (!conn) {
947
960
  this.connections.set(deviceId, { connected: true, lastSeen: at });
@@ -1671,7 +1684,7 @@ var ConnectionHub = class {
1671
1684
  async dispatch(input) {
1672
1685
  const dispatchSelection = input.dispatchSelection === void 0 ? void 0 : DispatchSelectionSchema.parse(input.dispatchSelection);
1673
1686
  const requiredToolsets = input.requiredToolsets === void 0 ? void 0 : RequiredToolsetsSchema.parse(input.requiredToolsets);
1674
- const deviceId = input.deviceId ?? this.pickFirstConnectedDevice();
1687
+ const deviceId = input.deviceId ?? this.pickFirstConnectedDevice(requiredToolsets);
1675
1688
  if (!deviceId || !this.connections.get(deviceId)?.connected) {
1676
1689
  throw new Error(
1677
1690
  deviceId ? `device ${deviceId} is not connected` : "no connected device to dispatch to (M0 does not queue tasks until a device connects)"
@@ -1687,6 +1700,21 @@ var ConnectionHub = class {
1687
1700
  `device ${deviceId} did not advertise toolset-selection capability; refusing a task whose semantics require local MCP tools`
1688
1701
  );
1689
1702
  }
1703
+ if (requiredToolsets !== void 0) {
1704
+ const configuredToolsets = this.connections.get(deviceId)?.configuredToolsets;
1705
+ if (configuredToolsets === void 0) {
1706
+ throw new Error(
1707
+ `device ${deviceId} did not advertise its configured toolset inventory; refusing to guess from runtime capability`
1708
+ );
1709
+ }
1710
+ const configured = new Set(configuredToolsets);
1711
+ const missing = requiredToolsets.filter((toolsetId) => !configured.has(toolsetId));
1712
+ if (missing.length > 0) {
1713
+ throw new Error(
1714
+ `device ${deviceId} is missing required MCP toolset(s): ${missing.join(", ")}`
1715
+ );
1716
+ }
1717
+ }
1690
1718
  if (dispatchSelection !== void 0 && input.runtime !== void 0 && input.runtime !== dispatchSelection.runtimeId) {
1691
1719
  throw new Error(
1692
1720
  `dispatch runtime ${input.runtime} does not match dispatchSelection.runtimeId ${dispatchSelection.runtimeId}`
@@ -1879,9 +1907,14 @@ var ConnectionHub = class {
1879
1907
  }
1880
1908
  this.sendToDevice(record.deviceId, "task.steer", { text }, { taskId });
1881
1909
  }
1882
- pickFirstConnectedDevice() {
1910
+ pickFirstConnectedDevice(requiredToolsets) {
1883
1911
  for (const [deviceId, conn] of this.connections) {
1884
- if (conn.connected) return deviceId;
1912
+ if (!conn.connected) continue;
1913
+ if (requiredToolsets === void 0) return deviceId;
1914
+ if (!conn.capabilities?.includes("toolset-selection")) continue;
1915
+ if (conn.configuredToolsets === void 0) continue;
1916
+ const configured = new Set(conn.configuredToolsets);
1917
+ if (requiredToolsets.every((toolsetId) => configured.has(toolsetId))) return deviceId;
1885
1918
  }
1886
1919
  return void 0;
1887
1920
  }
@@ -1964,7 +1997,8 @@ var ConnectionHub = class {
1964
1997
  deviceName,
1965
1998
  connected: conn?.connected ?? false,
1966
1999
  lastSeen: conn?.lastSeen,
1967
- runtimes: conn?.runtimes
2000
+ runtimes: conn?.runtimes,
2001
+ configuredToolsets: conn?.configuredToolsets ? [...conn.configuredToolsets] : void 0
1968
2002
  };
1969
2003
  });
1970
2004
  }
@@ -2212,7 +2246,13 @@ function handleConnection(ws, principal, deps) {
2212
2246
  return;
2213
2247
  }
2214
2248
  helloReceived = true;
2215
- deps.hub.registerConnection(deviceId, ws, payload.runtimes, payload.capabilities);
2249
+ deps.hub.registerConnection(
2250
+ deviceId,
2251
+ ws,
2252
+ payload.runtimes,
2253
+ payload.capabilities,
2254
+ payload.configuredToolsets
2255
+ );
2216
2256
  deps.hub.sendConnAck(deviceId, SUPPORTED_CAPABILITIES);
2217
2257
  if (payload.cursor !== void 0) {
2218
2258
  deps.hub.redeliverAfterReconnect(deviceId, payload.cursor);