@onklave/agent-cli 0.1.10 → 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.
- package/main.js +192 -15
- 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}/
|
|
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: {
|
|
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("
|
|
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
|
|
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:
|
|
2575
|
-
console.log("
|
|
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
|
-
|
|
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;
|