@onklave/agent-cli 0.1.9 → 0.1.11

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 (2) hide show
  1. package/main.js +192 -15
  2. package/package.json +1 -1
package/main.js CHANGED
@@ -510,7 +510,7 @@ var PlatformClient = class {
510
510
  async respondToGate(sessionId, decision, value) {
511
511
  await this.request(
512
512
  "POST",
513
- `/api/v1/agent/sessions/${sessionId}/gate`,
513
+ `/api/v1/agent/sessions/${sessionId}/respond`,
514
514
  {
515
515
  decision,
516
516
  message: value
@@ -569,6 +569,17 @@ var PlatformClient = class {
569
569
  async unregisterMachine() {
570
570
  await this.request("POST", "/api/v1/runner/unregister");
571
571
  }
572
+ /*
573
+ * Update an already-registered machine's configuration. Used by
574
+ * `onklave register --refresh` to push freshly-detected capabilities.
575
+ */
576
+ async updateMachine(machineId, patch) {
577
+ await this.request(
578
+ "PATCH",
579
+ `/api/v1/runner/machines/${machineId}`,
580
+ patch
581
+ );
582
+ }
572
583
  /*
573
584
  * List all registered machines.
574
585
  */
@@ -642,12 +653,19 @@ var CommsClient = class {
642
653
  }
643
654
  /*
644
655
  * Connect to the Onklave comms service via Socket.IO.
656
+ * Pass `machineId` so the server can route inbound runner events
657
+ * (e.g. `assignment:claim-available`) to this specific runner.
645
658
  */
646
- async connect(platformUrl, token) {
659
+ async connect(platformUrl, token, extra) {
647
660
  return new Promise((resolve3, reject) => {
648
661
  const commsUrl = platformUrl.replace(/\/+$/, "");
649
- this.socket = io(`${commsUrl}/agent`, {
650
- auth: { token },
662
+ this.socket = io(`${commsUrl}/agent-cli`, {
663
+ auth: {
664
+ token,
665
+ machineId: extra?.machineId,
666
+ deviceToken: extra?.deviceToken,
667
+ orgId: extra?.orgId
668
+ },
651
669
  transports: ["websocket", "polling"],
652
670
  reconnection: true,
653
671
  reconnectionAttempts: this.maxReconnectAttempts,
@@ -713,10 +731,13 @@ var CommsClient = class {
713
731
  this.socket?.emit("agent:audit", event);
714
732
  }
715
733
  /*
716
- * Emit a heartbeat to the platform.
734
+ * Emit a heartbeat to the platform. The comms namespace expects the
735
+ * `runner:heartbeat` event and the RunnerHeartbeatPayload shape; the
736
+ * primary heartbeat path is HTTP via HeartbeatService, this socket path
737
+ * is reserved for the future daemon-with-open-socket flow.
717
738
  */
718
739
  emitHeartbeat(data) {
719
- this.socket?.emit("agent:heartbeat", data);
740
+ this.socket?.emit("runner:heartbeat", data);
720
741
  }
721
742
  /*
722
743
  * Listen for approval responses from the platform.
@@ -738,6 +759,15 @@ var CommsClient = class {
738
759
  onSpawnRequest(handler) {
739
760
  this.socket?.on("agent:spawn", handler);
740
761
  }
762
+ /*
763
+ * Listen for assignment:claim-available events from the platform.
764
+ * The daemon mode (V6-CLI-002, deferred) will consume these to claim
765
+ * work items. For now this stays as a registerable listener so
766
+ * non-daemon use is unaffected.
767
+ */
768
+ onAssignmentAvailable(handler) {
769
+ this.socket?.on("assignment:claim-available", handler);
770
+ }
741
771
  };
742
772
 
743
773
  // _apps/@onklave/agent-cli/src/services/session-manager.ts
@@ -762,6 +792,12 @@ var SessionManager = class {
762
792
  if (config.allowedTools && config.allowedTools.length > 0) {
763
793
  args.push("--allowedTools", config.allowedTools.join(","));
764
794
  }
795
+ if (config.deniedTools && config.deniedTools.length > 0) {
796
+ args.push("--disallowedTools", config.deniedTools.join(","));
797
+ }
798
+ if (config.addDirs && config.addDirs.length > 0) {
799
+ args.push("--add-dir", ...config.addDirs);
800
+ }
765
801
  if (config.systemPromptAppend) {
766
802
  args.push("--append-system-prompt", config.systemPromptAppend);
767
803
  }
@@ -1178,6 +1214,69 @@ var GuardrailEnforcer = class {
1178
1214
  }
1179
1215
  };
1180
1216
 
1217
+ // _apps/@onklave/agent-cli/src/services/heartbeat.service.ts
1218
+ var DEFAULT_INTERVAL_MS = 3e4;
1219
+ var HeartbeatService = class {
1220
+ constructor(opts) {
1221
+ this.opts = opts;
1222
+ this.timer = null;
1223
+ this.intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS;
1224
+ this.onError = opts.onError ?? ((err) => console.error(`[heartbeat] ${err.message}`));
1225
+ }
1226
+ start() {
1227
+ if (this.timer) return;
1228
+ void this.emit();
1229
+ this.timer = setInterval(() => {
1230
+ void this.emit();
1231
+ }, this.intervalMs);
1232
+ if (typeof this.timer.unref === "function") {
1233
+ this.timer.unref();
1234
+ }
1235
+ }
1236
+ stop() {
1237
+ if (this.timer) {
1238
+ clearInterval(this.timer);
1239
+ this.timer = null;
1240
+ }
1241
+ }
1242
+ /**
1243
+ * Single heartbeat emission. Exposed for tests and one-shot diagnostics
1244
+ * (`onklave doctor`).
1245
+ */
1246
+ async emit() {
1247
+ const body = {
1248
+ machineId: this.opts.machineId,
1249
+ status: "online",
1250
+ activeSessionCount: this.opts.getActiveSessionCount(),
1251
+ resourceUsage: this.opts.getResourceUsage?.()
1252
+ };
1253
+ try {
1254
+ const response = await fetch(
1255
+ `${this.opts.platformUrl}/api/v1/runner/heartbeat`,
1256
+ {
1257
+ method: "POST",
1258
+ headers: {
1259
+ "Content-Type": "application/json",
1260
+ Accept: "application/json",
1261
+ "x-device-token": this.opts.deviceToken
1262
+ },
1263
+ body: JSON.stringify(body),
1264
+ signal: AbortSignal.timeout(1e4)
1265
+ }
1266
+ );
1267
+ if (!response.ok) {
1268
+ this.onError(
1269
+ new Error(
1270
+ `heartbeat failed: ${response.status} ${response.statusText}`
1271
+ )
1272
+ );
1273
+ }
1274
+ } catch (err) {
1275
+ this.onError(err instanceof Error ? err : new Error(String(err)));
1276
+ }
1277
+ }
1278
+ };
1279
+
1181
1280
  // _apps/@onklave/agent-cli/src/tui/render.tsx
1182
1281
  import { render } from "ink";
1183
1282
 
@@ -1764,6 +1863,21 @@ async function runCommand(args) {
1764
1863
  resolvedConfig.platformUrl,
1765
1864
  creds.token
1766
1865
  );
1866
+ const sessionManager = new SessionManager();
1867
+ let heartbeat = null;
1868
+ if (creds.machineId && creds.deviceToken) {
1869
+ heartbeat = new HeartbeatService({
1870
+ platformUrl: resolvedConfig.platformUrl,
1871
+ deviceToken: creds.deviceToken,
1872
+ machineId: creds.machineId,
1873
+ getActiveSessionCount: () => sessionManager.getActiveSessionIds().length
1874
+ });
1875
+ heartbeat.start();
1876
+ const stopHeartbeat = () => heartbeat?.stop();
1877
+ process.once("SIGTERM", stopHeartbeat);
1878
+ process.once("SIGINT", stopHeartbeat);
1879
+ process.once("beforeExit", stopHeartbeat);
1880
+ }
1767
1881
  let sessionId;
1768
1882
  try {
1769
1883
  console.log("Creating agent session on Onklave platform...");
@@ -1788,7 +1902,11 @@ async function runCommand(args) {
1788
1902
  const auditStreamer = new AuditStreamer(commsClient);
1789
1903
  try {
1790
1904
  console.log("Connecting to Onklave comms service...");
1791
- await commsClient.connect(resolvedConfig.platformUrl, creds.token);
1905
+ await commsClient.connect(resolvedConfig.platformUrl, creds.token, {
1906
+ machineId: creds.machineId,
1907
+ deviceToken: creds.deviceToken,
1908
+ orgId: creds.orgId
1909
+ });
1792
1910
  console.log("Connected.");
1793
1911
  } catch (err) {
1794
1912
  console.warn(
@@ -1817,13 +1935,27 @@ async function runCommand(args) {
1817
1935
  );
1818
1936
  }
1819
1937
  }
1820
- const _guardrailEnforcer = new GuardrailEnforcer({
1938
+ const guardrailEnforcer = new GuardrailEnforcer({
1821
1939
  rules: resolvedConfig.guardrails,
1822
1940
  allowedTools: resolvedConfig.allowedTools,
1823
1941
  deniedTools: resolvedConfig.deniedTools,
1824
1942
  readablePaths: resolvedConfig.readablePaths,
1825
1943
  writablePaths: resolvedConfig.writablePaths
1826
1944
  });
1945
+ for (const tool of resolvedConfig.allowedTools) {
1946
+ const verdict = guardrailEnforcer.checkToolCall(tool, {});
1947
+ if (!verdict.allowed) {
1948
+ console.warn(
1949
+ `Warning: allowed tool "${tool}" is also denied by guardrails \u2014 Claude will reject this tool.`
1950
+ );
1951
+ }
1952
+ }
1953
+ const addDirs = [
1954
+ .../* @__PURE__ */ new Set([
1955
+ ...resolvedConfig.writablePaths,
1956
+ ...resolvedConfig.readablePaths
1957
+ ])
1958
+ ];
1827
1959
  const sessionConfig = {
1828
1960
  task: resolvedConfig.task,
1829
1961
  context: resolvedConfig.context,
@@ -1834,12 +1966,25 @@ async function runCommand(args) {
1834
1966
  guardrails: resolvedConfig.guardrails,
1835
1967
  allowedTools: resolvedConfig.allowedTools,
1836
1968
  deniedTools: resolvedConfig.deniedTools,
1969
+ addDirs,
1837
1970
  apiKey: resolvedConfig.apiKey,
1838
1971
  headless: resolvedConfig.headless,
1839
1972
  platformUrl: resolvedConfig.platformUrl,
1840
1973
  orgId: resolvedConfig.orgId,
1841
1974
  systemPromptAppend: personaSystemPrompt ?? resolvedConfig.systemPromptAppend
1842
1975
  };
1976
+ auditStreamer.record({
1977
+ sessionId,
1978
+ type: "state_change" /* STATE_CHANGE */,
1979
+ action: "guardrails_applied",
1980
+ details: {
1981
+ ruleCount: resolvedConfig.guardrails.length,
1982
+ allowedTools: resolvedConfig.allowedTools,
1983
+ deniedTools: resolvedConfig.deniedTools,
1984
+ addDirs
1985
+ },
1986
+ outcome: "success"
1987
+ });
1843
1988
  statePublisher.publishSessionStarted(sessionId, sessionConfig);
1844
1989
  auditStreamer.record({
1845
1990
  sessionId,
@@ -1849,7 +1994,6 @@ async function runCommand(args) {
1849
1994
  outcome: "success"
1850
1995
  });
1851
1996
  const useTui = flags["tui"] === true || process.stdout.isTTY === true && !resolvedConfig.headless && flags["tui"] !== false;
1852
- const sessionManager = new SessionManager();
1853
1997
  if (useTui) {
1854
1998
  const tuiInstance = renderTui({
1855
1999
  sessionId,
@@ -2568,11 +2712,14 @@ async function configCommand(args) {
2568
2712
  import * as os3 from "os";
2569
2713
  async function registerCommand(args) {
2570
2714
  const { flags } = parseArgs(args);
2715
+ const refresh = flags["refresh"] === true;
2571
2716
  const linkingToken = flags["token"];
2572
- if (typeof linkingToken !== "string" || !linkingToken) {
2573
- console.error("Error: --token is required.\n");
2574
- console.log("Usage: onklave register --token <linking-token>\n");
2575
- console.log("To obtain a linking token:");
2717
+ if (!refresh && (typeof linkingToken !== "string" || !linkingToken)) {
2718
+ console.error("Error: --token is required (or pass --refresh).\n");
2719
+ console.log("Usage:");
2720
+ console.log(" onklave register --token <linking-token>");
2721
+ console.log(" onklave register --refresh # re-detect capabilities");
2722
+ console.log("\nTo obtain a linking token:");
2576
2723
  console.log(" 1. Log in to the Onklave Portal");
2577
2724
  console.log(" 2. Navigate to Settings > Machines");
2578
2725
  console.log(' 3. Click "Register Machine" to generate a linking token');
@@ -2586,6 +2733,28 @@ async function registerCommand(args) {
2586
2733
  process.exitCode = 1;
2587
2734
  return;
2588
2735
  }
2736
+ const platformClient = new PlatformClient(creds.platformUrl, creds.token);
2737
+ if (refresh) {
2738
+ if (!creds.machineId) {
2739
+ console.error(
2740
+ "Error: --refresh requires a previously-registered machine. Run `onklave register --token <linking-token>` first."
2741
+ );
2742
+ process.exitCode = 1;
2743
+ return;
2744
+ }
2745
+ const capabilities = detectCapabilities();
2746
+ console.log("Re-detecting capabilities for this machine...");
2747
+ console.log(` Capabilities: ${capabilities.join(", ") || "(none)"}`);
2748
+ try {
2749
+ await platformClient.updateMachine(creds.machineId, { capabilities });
2750
+ console.log(`
2751
+ Machine ${creds.machineId} updated successfully.`);
2752
+ } catch (err) {
2753
+ console.error(`Refresh failed: ${err.message}`);
2754
+ process.exitCode = 1;
2755
+ }
2756
+ return;
2757
+ }
2589
2758
  const metadata = {
2590
2759
  hostname: os3.hostname(),
2591
2760
  os: `${os3.platform()} ${os3.release()}`,
@@ -2593,14 +2762,19 @@ async function registerCommand(args) {
2593
2762
  nodeVersion: process.version,
2594
2763
  capabilities: detectCapabilities()
2595
2764
  };
2596
- const platformClient = new PlatformClient(creds.platformUrl, creds.token);
2597
2765
  try {
2598
2766
  console.log("Registering machine with Onklave platform...");
2599
2767
  console.log(` Hostname: ${metadata.hostname}`);
2600
2768
  console.log(` OS: ${metadata.os}`);
2601
2769
  console.log(` Arch: ${metadata.arch}`);
2602
2770
  console.log(` Node: ${metadata.nodeVersion}`);
2603
- const result = await platformClient.registerMachine(linkingToken, metadata);
2771
+ console.log(
2772
+ ` Capabilities: ${metadata.capabilities?.join(", ") || "(none)"}`
2773
+ );
2774
+ const result = await platformClient.registerMachine(
2775
+ linkingToken,
2776
+ metadata
2777
+ );
2604
2778
  await authService.saveDeviceToken(result.deviceToken, result.machineId);
2605
2779
  console.log(`
2606
2780
  Machine registered successfully.`);
@@ -2610,6 +2784,9 @@ Machine registered successfully.`);
2610
2784
  `
2611
2785
  This machine can now receive remote agent session requests.`
2612
2786
  );
2787
+ console.log(
2788
+ `Tip: after installing new tools (e.g. Docker), run \`onklave register --refresh\` to update capabilities.`
2789
+ );
2613
2790
  } catch (err) {
2614
2791
  console.error(`Registration failed: ${err.message}`);
2615
2792
  process.exitCode = 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onklave/agent-cli",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Onklave Agent CLI — local agent runner with cloud orchestration",
5
5
  "bin": {
6
6
  "onklave": "./main.js"