@getworkle/cli 0.2.13 → 0.3.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/dist/cli.js +209 -1539
- package/dist/cli.js.map +1 -1
- package/package.json +7 -24
package/dist/cli.js
CHANGED
|
@@ -1209,8 +1209,8 @@ var require_command = __commonJS({
|
|
|
1209
1209
|
"use strict";
|
|
1210
1210
|
var EventEmitter = __require("events").EventEmitter;
|
|
1211
1211
|
var childProcess = __require("child_process");
|
|
1212
|
-
var
|
|
1213
|
-
var
|
|
1212
|
+
var path11 = __require("path");
|
|
1213
|
+
var fs8 = __require("fs");
|
|
1214
1214
|
var process2 = __require("process");
|
|
1215
1215
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
1216
1216
|
var { CommanderError: CommanderError2 } = require_error();
|
|
@@ -2204,7 +2204,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2204
2204
|
* @param {string} subcommandName
|
|
2205
2205
|
*/
|
|
2206
2206
|
_checkForMissingExecutable(executableFile, executableDir, subcommandName) {
|
|
2207
|
-
if (
|
|
2207
|
+
if (fs8.existsSync(executableFile)) return;
|
|
2208
2208
|
const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
|
|
2209
2209
|
const executableMissing = `'${executableFile}' does not exist
|
|
2210
2210
|
- if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
|
|
@@ -2222,11 +2222,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2222
2222
|
let launchWithNode = false;
|
|
2223
2223
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
2224
2224
|
function findFile(baseDir, baseName) {
|
|
2225
|
-
const localBin =
|
|
2226
|
-
if (
|
|
2227
|
-
if (sourceExt.includes(
|
|
2225
|
+
const localBin = path11.resolve(baseDir, baseName);
|
|
2226
|
+
if (fs8.existsSync(localBin)) return localBin;
|
|
2227
|
+
if (sourceExt.includes(path11.extname(baseName))) return void 0;
|
|
2228
2228
|
const foundExt = sourceExt.find(
|
|
2229
|
-
(ext) =>
|
|
2229
|
+
(ext) => fs8.existsSync(`${localBin}${ext}`)
|
|
2230
2230
|
);
|
|
2231
2231
|
if (foundExt) return `${localBin}${foundExt}`;
|
|
2232
2232
|
return void 0;
|
|
@@ -2238,21 +2238,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2238
2238
|
if (this._scriptPath) {
|
|
2239
2239
|
let resolvedScriptPath;
|
|
2240
2240
|
try {
|
|
2241
|
-
resolvedScriptPath =
|
|
2241
|
+
resolvedScriptPath = fs8.realpathSync(this._scriptPath);
|
|
2242
2242
|
} catch {
|
|
2243
2243
|
resolvedScriptPath = this._scriptPath;
|
|
2244
2244
|
}
|
|
2245
|
-
executableDir =
|
|
2246
|
-
|
|
2245
|
+
executableDir = path11.resolve(
|
|
2246
|
+
path11.dirname(resolvedScriptPath),
|
|
2247
2247
|
executableDir
|
|
2248
2248
|
);
|
|
2249
2249
|
}
|
|
2250
2250
|
if (executableDir) {
|
|
2251
2251
|
let localFile = findFile(executableDir, executableFile);
|
|
2252
2252
|
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
2253
|
-
const legacyName =
|
|
2253
|
+
const legacyName = path11.basename(
|
|
2254
2254
|
this._scriptPath,
|
|
2255
|
-
|
|
2255
|
+
path11.extname(this._scriptPath)
|
|
2256
2256
|
);
|
|
2257
2257
|
if (legacyName !== this._name) {
|
|
2258
2258
|
localFile = findFile(
|
|
@@ -2263,7 +2263,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2263
2263
|
}
|
|
2264
2264
|
executableFile = localFile || executableFile;
|
|
2265
2265
|
}
|
|
2266
|
-
launchWithNode = sourceExt.includes(
|
|
2266
|
+
launchWithNode = sourceExt.includes(path11.extname(executableFile));
|
|
2267
2267
|
let proc;
|
|
2268
2268
|
if (process2.platform !== "win32") {
|
|
2269
2269
|
if (launchWithNode) {
|
|
@@ -3178,7 +3178,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3178
3178
|
* @return {Command}
|
|
3179
3179
|
*/
|
|
3180
3180
|
nameFromFilename(filename) {
|
|
3181
|
-
this._name =
|
|
3181
|
+
this._name = path11.basename(filename, path11.extname(filename));
|
|
3182
3182
|
return this;
|
|
3183
3183
|
}
|
|
3184
3184
|
/**
|
|
@@ -3192,9 +3192,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3192
3192
|
* @param {string} [path]
|
|
3193
3193
|
* @return {(string|null|Command)}
|
|
3194
3194
|
*/
|
|
3195
|
-
executableDir(
|
|
3196
|
-
if (
|
|
3197
|
-
this._executableDir =
|
|
3195
|
+
executableDir(path12) {
|
|
3196
|
+
if (path12 === void 0) return this._executableDir;
|
|
3197
|
+
this._executableDir = path12;
|
|
3198
3198
|
return this;
|
|
3199
3199
|
}
|
|
3200
3200
|
/**
|
|
@@ -4285,7 +4285,7 @@ var require_background_scheduled_task = __commonJS({
|
|
|
4285
4285
|
"../../node_modules/node-cron/src/background-scheduled-task/index.js"(exports, module) {
|
|
4286
4286
|
"use strict";
|
|
4287
4287
|
var EventEmitter = __require("events");
|
|
4288
|
-
var
|
|
4288
|
+
var path11 = __require("path");
|
|
4289
4289
|
var { fork } = __require("child_process");
|
|
4290
4290
|
var uuid3 = (init_esm_node(), __toCommonJS(esm_node_exports));
|
|
4291
4291
|
var daemonPath = `${__dirname}/daemon.js`;
|
|
@@ -4320,7 +4320,7 @@ var require_background_scheduled_task = __commonJS({
|
|
|
4320
4320
|
options.scheduled = true;
|
|
4321
4321
|
this.forkProcess.send({
|
|
4322
4322
|
type: "register",
|
|
4323
|
-
path:
|
|
4323
|
+
path: path11.resolve(this.taskPath),
|
|
4324
4324
|
cron: this.cronExpression,
|
|
4325
4325
|
options
|
|
4326
4326
|
});
|
|
@@ -6600,7 +6600,7 @@ var require_websocket = __commonJS({
|
|
|
6600
6600
|
"use strict";
|
|
6601
6601
|
var EventEmitter = __require("events");
|
|
6602
6602
|
var https = __require("https");
|
|
6603
|
-
var
|
|
6603
|
+
var http2 = __require("http");
|
|
6604
6604
|
var net = __require("net");
|
|
6605
6605
|
var tls = __require("tls");
|
|
6606
6606
|
var { randomBytes: randomBytes2, createHash: createHash2 } = __require("crypto");
|
|
@@ -7131,7 +7131,7 @@ var require_websocket = __commonJS({
|
|
|
7131
7131
|
}
|
|
7132
7132
|
const defaultPort = isSecure ? 443 : 80;
|
|
7133
7133
|
const key = randomBytes2(16).toString("base64");
|
|
7134
|
-
const request = isSecure ? https.request :
|
|
7134
|
+
const request = isSecure ? https.request : http2.request;
|
|
7135
7135
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
7136
7136
|
let perMessageDeflate;
|
|
7137
7137
|
opts.createConnection = opts.createConnection || (isSecure ? tlsConnect : netConnect);
|
|
@@ -7625,7 +7625,7 @@ var require_websocket_server = __commonJS({
|
|
|
7625
7625
|
"../../node_modules/ws/lib/websocket-server.js"(exports, module) {
|
|
7626
7626
|
"use strict";
|
|
7627
7627
|
var EventEmitter = __require("events");
|
|
7628
|
-
var
|
|
7628
|
+
var http2 = __require("http");
|
|
7629
7629
|
var { Duplex } = __require("stream");
|
|
7630
7630
|
var { createHash: createHash2 } = __require("crypto");
|
|
7631
7631
|
var extension = require_extension();
|
|
@@ -7696,8 +7696,8 @@ var require_websocket_server = __commonJS({
|
|
|
7696
7696
|
);
|
|
7697
7697
|
}
|
|
7698
7698
|
if (options.port != null) {
|
|
7699
|
-
this._server =
|
|
7700
|
-
const body =
|
|
7699
|
+
this._server = http2.createServer((req, res) => {
|
|
7700
|
+
const body = http2.STATUS_CODES[426];
|
|
7701
7701
|
res.writeHead(426, {
|
|
7702
7702
|
"Content-Length": body.length,
|
|
7703
7703
|
"Content-Type": "text/plain"
|
|
@@ -7984,7 +7984,7 @@ var require_websocket_server = __commonJS({
|
|
|
7984
7984
|
this.destroy();
|
|
7985
7985
|
}
|
|
7986
7986
|
function abortHandshake(socket, code, message, headers) {
|
|
7987
|
-
message = message ||
|
|
7987
|
+
message = message || http2.STATUS_CODES[code];
|
|
7988
7988
|
headers = {
|
|
7989
7989
|
Connection: "close",
|
|
7990
7990
|
"Content-Type": "text/html",
|
|
@@ -7993,7 +7993,7 @@ var require_websocket_server = __commonJS({
|
|
|
7993
7993
|
};
|
|
7994
7994
|
socket.once("finish", socket.destroy);
|
|
7995
7995
|
socket.end(
|
|
7996
|
-
`HTTP/1.1 ${code} ${
|
|
7996
|
+
`HTTP/1.1 ${code} ${http2.STATUS_CODES[code]}\r
|
|
7997
7997
|
` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
|
|
7998
7998
|
);
|
|
7999
7999
|
}
|
|
@@ -8027,13 +8027,13 @@ var {
|
|
|
8027
8027
|
} = import_index.default;
|
|
8028
8028
|
|
|
8029
8029
|
// src/cli.ts
|
|
8030
|
-
import { execSync
|
|
8030
|
+
import { execSync, spawnSync } from "child_process";
|
|
8031
8031
|
import { createHash, randomBytes } from "crypto";
|
|
8032
|
-
import { readFileSync } from "fs";
|
|
8033
|
-
import
|
|
8034
|
-
import
|
|
8035
|
-
import
|
|
8036
|
-
import
|
|
8032
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
8033
|
+
import fs7 from "fs/promises";
|
|
8034
|
+
import http from "http";
|
|
8035
|
+
import os7 from "os";
|
|
8036
|
+
import path10 from "path";
|
|
8037
8037
|
|
|
8038
8038
|
// src/agents/config-materializer.ts
|
|
8039
8039
|
import fs2 from "fs/promises";
|
|
@@ -8151,7 +8151,7 @@ var AgentConfigMaterializer = class {
|
|
|
8151
8151
|
*/
|
|
8152
8152
|
async listAgents() {
|
|
8153
8153
|
const res = await fetch(
|
|
8154
|
-
`${this.apiUrl}/api/
|
|
8154
|
+
`${this.apiUrl}/api/local/instances/${this.instanceId}/runtime/agents`,
|
|
8155
8155
|
{ headers: { ...this.headers, "Content-Type": "application/json" } }
|
|
8156
8156
|
);
|
|
8157
8157
|
if (!res.ok) {
|
|
@@ -8165,7 +8165,7 @@ var AgentConfigMaterializer = class {
|
|
|
8165
8165
|
*/
|
|
8166
8166
|
async pullAgent(agentId) {
|
|
8167
8167
|
const res = await fetch(
|
|
8168
|
-
`${this.apiUrl}/api/
|
|
8168
|
+
`${this.apiUrl}/api/local/instances/${this.instanceId}/runtime/agents/${agentId}/pull`,
|
|
8169
8169
|
{ headers: { ...this.headers, "Content-Type": "application/json" } }
|
|
8170
8170
|
);
|
|
8171
8171
|
if (!res.ok) {
|
|
@@ -8291,19 +8291,27 @@ var AgentConfigMaterializer = class {
|
|
|
8291
8291
|
|
|
8292
8292
|
// src/agents/runner.ts
|
|
8293
8293
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
|
|
8294
|
+
import path4 from "path";
|
|
8295
|
+
import { fileURLToPath } from "url";
|
|
8296
|
+
async function resolveClaudeCodeExecutable() {
|
|
8297
|
+
let sdkEntryPath;
|
|
8298
|
+
try {
|
|
8299
|
+
sdkEntryPath = fileURLToPath(import.meta.resolve("@anthropic-ai/claude-agent-sdk"));
|
|
8300
|
+
} catch {
|
|
8301
|
+
const { createRequire } = await import("module");
|
|
8302
|
+
const localRequire = createRequire(import.meta.url);
|
|
8303
|
+
sdkEntryPath = localRequire.resolve("@anthropic-ai/claude-agent-sdk");
|
|
8304
|
+
}
|
|
8305
|
+
return path4.join(path4.dirname(sdkEntryPath), "cli.js");
|
|
8299
8306
|
}
|
|
8300
8307
|
async function runAgent(opts) {
|
|
8301
8308
|
const { agentDir, prompt, sessionId, maxTurns, permissionMode, model, allowedTools, mcpServers, onMessage, abortController } = opts;
|
|
8302
8309
|
try {
|
|
8310
|
+
const pathToClaudeCodeExecutable = await resolveClaudeCodeExecutable();
|
|
8303
8311
|
const stream = query({
|
|
8304
8312
|
prompt,
|
|
8305
8313
|
options: {
|
|
8306
|
-
pathToClaudeCodeExecutable
|
|
8314
|
+
pathToClaudeCodeExecutable,
|
|
8307
8315
|
executable: process.execPath,
|
|
8308
8316
|
// Full path to node — launchd doesn't have node in PATH
|
|
8309
8317
|
cwd: agentDir,
|
|
@@ -9112,10 +9120,10 @@ function mergeDefs(...defs) {
|
|
|
9112
9120
|
function cloneDef(schema) {
|
|
9113
9121
|
return mergeDefs(schema._zod.def);
|
|
9114
9122
|
}
|
|
9115
|
-
function getElementAtPath(obj,
|
|
9116
|
-
if (!
|
|
9123
|
+
function getElementAtPath(obj, path11) {
|
|
9124
|
+
if (!path11)
|
|
9117
9125
|
return obj;
|
|
9118
|
-
return
|
|
9126
|
+
return path11.reduce((acc, key) => acc?.[key], obj);
|
|
9119
9127
|
}
|
|
9120
9128
|
function promiseAllObject(promisesObj) {
|
|
9121
9129
|
const keys = Object.keys(promisesObj);
|
|
@@ -9481,11 +9489,11 @@ function aborted(x, startIndex = 0) {
|
|
|
9481
9489
|
}
|
|
9482
9490
|
return false;
|
|
9483
9491
|
}
|
|
9484
|
-
function prefixIssues(
|
|
9492
|
+
function prefixIssues(path11, issues) {
|
|
9485
9493
|
return issues.map((iss) => {
|
|
9486
9494
|
var _a2;
|
|
9487
9495
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
9488
|
-
iss.path.unshift(
|
|
9496
|
+
iss.path.unshift(path11);
|
|
9489
9497
|
return iss;
|
|
9490
9498
|
});
|
|
9491
9499
|
}
|
|
@@ -9647,7 +9655,7 @@ function formatError(error46, mapper = (issue2) => issue2.message) {
|
|
|
9647
9655
|
}
|
|
9648
9656
|
function treeifyError(error46, mapper = (issue2) => issue2.message) {
|
|
9649
9657
|
const result = { errors: [] };
|
|
9650
|
-
const processError = (error47,
|
|
9658
|
+
const processError = (error47, path11 = []) => {
|
|
9651
9659
|
var _a2, _b;
|
|
9652
9660
|
for (const issue2 of error47.issues) {
|
|
9653
9661
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -9657,7 +9665,7 @@ function treeifyError(error46, mapper = (issue2) => issue2.message) {
|
|
|
9657
9665
|
} else if (issue2.code === "invalid_element") {
|
|
9658
9666
|
processError({ issues: issue2.issues }, issue2.path);
|
|
9659
9667
|
} else {
|
|
9660
|
-
const fullpath = [...
|
|
9668
|
+
const fullpath = [...path11, ...issue2.path];
|
|
9661
9669
|
if (fullpath.length === 0) {
|
|
9662
9670
|
result.errors.push(mapper(issue2));
|
|
9663
9671
|
continue;
|
|
@@ -9689,8 +9697,8 @@ function treeifyError(error46, mapper = (issue2) => issue2.message) {
|
|
|
9689
9697
|
}
|
|
9690
9698
|
function toDotPath(_path) {
|
|
9691
9699
|
const segs = [];
|
|
9692
|
-
const
|
|
9693
|
-
for (const seg of
|
|
9700
|
+
const path11 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
9701
|
+
for (const seg of path11) {
|
|
9694
9702
|
if (typeof seg === "number")
|
|
9695
9703
|
segs.push(`[${seg}]`);
|
|
9696
9704
|
else if (typeof seg === "symbol")
|
|
@@ -21026,23 +21034,23 @@ function date4(params) {
|
|
|
21026
21034
|
config(en_default());
|
|
21027
21035
|
|
|
21028
21036
|
// src/agents/mcp-server.ts
|
|
21029
|
-
async function apiGet(apiUrl,
|
|
21030
|
-
const res = await fetch(`${apiUrl}${
|
|
21037
|
+
async function apiGet(apiUrl, path11, headers) {
|
|
21038
|
+
const res = await fetch(`${apiUrl}${path11}`, {
|
|
21031
21039
|
headers: { ...headers, "Content-Type": "application/json" }
|
|
21032
21040
|
});
|
|
21033
21041
|
if (!res.ok) {
|
|
21034
|
-
throw new Error(`API GET ${
|
|
21042
|
+
throw new Error(`API GET ${path11} failed: HTTP ${res.status}`);
|
|
21035
21043
|
}
|
|
21036
21044
|
return res.json();
|
|
21037
21045
|
}
|
|
21038
|
-
async function apiPost(apiUrl,
|
|
21039
|
-
const res = await fetch(`${apiUrl}${
|
|
21046
|
+
async function apiPost(apiUrl, path11, body, headers) {
|
|
21047
|
+
const res = await fetch(`${apiUrl}${path11}`, {
|
|
21040
21048
|
method: "POST",
|
|
21041
21049
|
headers: { ...headers, "Content-Type": "application/json" },
|
|
21042
21050
|
body: JSON.stringify(body)
|
|
21043
21051
|
});
|
|
21044
21052
|
if (!res.ok) {
|
|
21045
|
-
throw new Error(`API POST ${
|
|
21053
|
+
throw new Error(`API POST ${path11} failed: HTTP ${res.status}`);
|
|
21046
21054
|
}
|
|
21047
21055
|
return res.json();
|
|
21048
21056
|
}
|
|
@@ -21063,7 +21071,7 @@ function createWorkleMcpServer(opts) {
|
|
|
21063
21071
|
});
|
|
21064
21072
|
const data = await apiGet(
|
|
21065
21073
|
apiUrl,
|
|
21066
|
-
`/api/
|
|
21074
|
+
`/api/local/instances/${instanceId}/mcp/contacts?${params.toString()}`,
|
|
21067
21075
|
headers
|
|
21068
21076
|
);
|
|
21069
21077
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
@@ -21081,7 +21089,7 @@ function createWorkleMcpServer(opts) {
|
|
|
21081
21089
|
async (input) => {
|
|
21082
21090
|
const data = await apiPost(
|
|
21083
21091
|
apiUrl,
|
|
21084
|
-
`/api/
|
|
21092
|
+
`/api/local/instances/${instanceId}/mcp/activity`,
|
|
21085
21093
|
input,
|
|
21086
21094
|
headers
|
|
21087
21095
|
);
|
|
@@ -21102,7 +21110,7 @@ function createWorkleMcpServer(opts) {
|
|
|
21102
21110
|
});
|
|
21103
21111
|
const data = await apiGet(
|
|
21104
21112
|
apiUrl,
|
|
21105
|
-
`/api/
|
|
21113
|
+
`/api/local/instances/${instanceId}/mcp/documents?${params.toString()}`,
|
|
21106
21114
|
headers
|
|
21107
21115
|
);
|
|
21108
21116
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
@@ -21168,7 +21176,12 @@ var AgentScheduler = class {
|
|
|
21168
21176
|
// src/agents/service.ts
|
|
21169
21177
|
import { randomUUID } from "crypto";
|
|
21170
21178
|
import fs4 from "fs/promises";
|
|
21179
|
+
import path7 from "path";
|
|
21180
|
+
|
|
21181
|
+
// src/sync/relay.ts
|
|
21182
|
+
import { readFileSync } from "fs";
|
|
21171
21183
|
import path5 from "path";
|
|
21184
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
21172
21185
|
|
|
21173
21186
|
// ../../node_modules/ws/wrapper.mjs
|
|
21174
21187
|
var import_stream = __toESM(require_stream(), 1);
|
|
@@ -21182,6 +21195,39 @@ var wrapper_default = import_websocket.default;
|
|
|
21182
21195
|
var MAX_BACKOFF = 3e4;
|
|
21183
21196
|
var INITIAL_BACKOFF = 1e3;
|
|
21184
21197
|
var MAX_QUEUE_SIZE = 1e3;
|
|
21198
|
+
function readVersionFromJson(pathOrUrl) {
|
|
21199
|
+
try {
|
|
21200
|
+
const raw = readFileSync(pathOrUrl, "utf-8");
|
|
21201
|
+
const parsed = JSON.parse(raw);
|
|
21202
|
+
return typeof parsed.version === "string" ? parsed.version : void 0;
|
|
21203
|
+
} catch {
|
|
21204
|
+
return void 0;
|
|
21205
|
+
}
|
|
21206
|
+
}
|
|
21207
|
+
function readVersionFromNearestPackageJson(entryPath) {
|
|
21208
|
+
let currentDir = path5.dirname(entryPath);
|
|
21209
|
+
const rootDir = path5.parse(currentDir).root;
|
|
21210
|
+
while (true) {
|
|
21211
|
+
const version3 = readVersionFromJson(path5.join(currentDir, "package.json"));
|
|
21212
|
+
if (version3) {
|
|
21213
|
+
return version3;
|
|
21214
|
+
}
|
|
21215
|
+
if (currentDir === rootDir) {
|
|
21216
|
+
return void 0;
|
|
21217
|
+
}
|
|
21218
|
+
currentDir = path5.dirname(currentDir);
|
|
21219
|
+
}
|
|
21220
|
+
}
|
|
21221
|
+
var SYNC_VERSION = readVersionFromJson(new URL("../../package.json", import.meta.url));
|
|
21222
|
+
var AGENT_SDK_VERSION = (() => {
|
|
21223
|
+
try {
|
|
21224
|
+
return readVersionFromNearestPackageJson(
|
|
21225
|
+
fileURLToPath2(import.meta.resolve("@anthropic-ai/claude-agent-sdk"))
|
|
21226
|
+
);
|
|
21227
|
+
} catch {
|
|
21228
|
+
return void 0;
|
|
21229
|
+
}
|
|
21230
|
+
})();
|
|
21185
21231
|
var RelayClient = class {
|
|
21186
21232
|
constructor(relayUrl, instanceId, token, options) {
|
|
21187
21233
|
this.relayUrl = relayUrl;
|
|
@@ -21250,7 +21296,7 @@ var RelayClient = class {
|
|
|
21250
21296
|
doConnect() {
|
|
21251
21297
|
return new Promise((resolve, reject) => {
|
|
21252
21298
|
const url2 = `${this.relayUrl}/${this.instanceId}`;
|
|
21253
|
-
console.log(`[
|
|
21299
|
+
console.log(`[local] Connecting to relay: ${url2}`);
|
|
21254
21300
|
this.ws = new wrapper_default(url2, {
|
|
21255
21301
|
headers: {
|
|
21256
21302
|
Authorization: `Bearer ${this.token}`
|
|
@@ -21262,7 +21308,9 @@ var RelayClient = class {
|
|
|
21262
21308
|
this.backoff = INITIAL_BACKOFF;
|
|
21263
21309
|
const hello = {
|
|
21264
21310
|
type: "hello",
|
|
21265
|
-
instanceId: this.instanceId
|
|
21311
|
+
instanceId: this.instanceId,
|
|
21312
|
+
...SYNC_VERSION ? { syncVersion: SYNC_VERSION } : {},
|
|
21313
|
+
...AGENT_SDK_VERSION ? { agentSdkVersion: AGENT_SDK_VERSION } : {}
|
|
21266
21314
|
};
|
|
21267
21315
|
this.ws.send(JSON.stringify(hello));
|
|
21268
21316
|
this.flushQueue();
|
|
@@ -21270,7 +21318,7 @@ var RelayClient = class {
|
|
|
21270
21318
|
try {
|
|
21271
21319
|
this.onConnectCallback();
|
|
21272
21320
|
} catch (err) {
|
|
21273
|
-
console.error("[
|
|
21321
|
+
console.error("[local] onConnect callback error:", err);
|
|
21274
21322
|
}
|
|
21275
21323
|
}
|
|
21276
21324
|
if (!resolved) {
|
|
@@ -21286,7 +21334,7 @@ var RelayClient = class {
|
|
|
21286
21334
|
try {
|
|
21287
21335
|
handler(msg);
|
|
21288
21336
|
} catch (err) {
|
|
21289
|
-
console.error("[
|
|
21337
|
+
console.error("[local] Relay message handler error:", err);
|
|
21290
21338
|
}
|
|
21291
21339
|
}
|
|
21292
21340
|
} catch {
|
|
@@ -21296,20 +21344,20 @@ var RelayClient = class {
|
|
|
21296
21344
|
this.ws.on("close", (code, reason) => {
|
|
21297
21345
|
if (closeHandled) return;
|
|
21298
21346
|
closeHandled = true;
|
|
21299
|
-
const reasonStr = reason ? Buffer
|
|
21347
|
+
const reasonStr = typeof reason === "string" ? reason : reason instanceof Buffer ? reason.toString("utf-8") : "";
|
|
21300
21348
|
console.log(
|
|
21301
|
-
`[
|
|
21349
|
+
`[local] Relay WebSocket closed: code=${code} reason="${reasonStr}"`
|
|
21302
21350
|
);
|
|
21303
21351
|
this._connected = false;
|
|
21304
21352
|
this.cleanupWs();
|
|
21305
21353
|
if (code === 4e3) {
|
|
21306
|
-
console.log("[
|
|
21354
|
+
console.log("[local] Connection replaced \u2014 not reconnecting");
|
|
21307
21355
|
return;
|
|
21308
21356
|
}
|
|
21309
21357
|
this.scheduleReconnect();
|
|
21310
21358
|
});
|
|
21311
21359
|
this.ws.on("error", (err) => {
|
|
21312
|
-
console.error("[
|
|
21360
|
+
console.error("[local] Relay WebSocket error:", err.message);
|
|
21313
21361
|
this._connected = false;
|
|
21314
21362
|
if (!resolved) {
|
|
21315
21363
|
resolved = true;
|
|
@@ -21332,12 +21380,12 @@ var RelayClient = class {
|
|
|
21332
21380
|
clearTimeout(this.reconnectTimer);
|
|
21333
21381
|
}
|
|
21334
21382
|
console.log(
|
|
21335
|
-
`[
|
|
21383
|
+
`[local] Relay disconnected. Reconnecting in ${this.backoff}ms...`
|
|
21336
21384
|
);
|
|
21337
21385
|
this.reconnectTimer = setTimeout(() => {
|
|
21338
21386
|
this.reconnectTimer = null;
|
|
21339
21387
|
this.doConnect().catch((err) => {
|
|
21340
|
-
console.error("[
|
|
21388
|
+
console.error("[local] Relay reconnect failed:", err);
|
|
21341
21389
|
});
|
|
21342
21390
|
}, this.backoff);
|
|
21343
21391
|
const jitter = Math.random() * 0.3 * this.backoff;
|
|
@@ -21351,7 +21399,7 @@ var RelayClient = class {
|
|
|
21351
21399
|
this.ws.send(JSON.stringify(msg));
|
|
21352
21400
|
}
|
|
21353
21401
|
if (queued.length > 0) {
|
|
21354
|
-
console.log(`[
|
|
21402
|
+
console.log(`[local] Flushed ${queued.length} queued messages to relay`);
|
|
21355
21403
|
}
|
|
21356
21404
|
}
|
|
21357
21405
|
};
|
|
@@ -21359,8 +21407,8 @@ var RelayClient = class {
|
|
|
21359
21407
|
// src/agents/sessions.ts
|
|
21360
21408
|
import fs3 from "fs/promises";
|
|
21361
21409
|
import os4 from "os";
|
|
21362
|
-
import
|
|
21363
|
-
var SESSIONS_FILE =
|
|
21410
|
+
import path6 from "path";
|
|
21411
|
+
var SESSIONS_FILE = path6.join(
|
|
21364
21412
|
os4.homedir(),
|
|
21365
21413
|
".workle",
|
|
21366
21414
|
"agent-sessions.json"
|
|
@@ -21382,7 +21430,7 @@ async function saveSessionId(agentId, sessionId) {
|
|
|
21382
21430
|
} catch {
|
|
21383
21431
|
}
|
|
21384
21432
|
map2[agentId] = sessionId;
|
|
21385
|
-
await fs3.mkdir(
|
|
21433
|
+
await fs3.mkdir(path6.dirname(SESSIONS_FILE), { recursive: true });
|
|
21386
21434
|
await fs3.writeFile(SESSIONS_FILE, JSON.stringify(map2, null, 2), "utf-8");
|
|
21387
21435
|
}
|
|
21388
21436
|
|
|
@@ -21407,7 +21455,7 @@ function stringifyProgressValue(value) {
|
|
|
21407
21455
|
function deriveRelayUrl(apiUrl) {
|
|
21408
21456
|
const url2 = new URL(apiUrl);
|
|
21409
21457
|
url2.protocol = url2.protocol === "https:" ? "wss:" : "ws:";
|
|
21410
|
-
url2.pathname = "/api/
|
|
21458
|
+
url2.pathname = "/api/local/relay";
|
|
21411
21459
|
if (url2.hostname !== "localhost") {
|
|
21412
21460
|
url2.hostname = `relay.${url2.hostname}`;
|
|
21413
21461
|
}
|
|
@@ -21905,12 +21953,12 @@ var AgentService = class _AgentService {
|
|
|
21905
21953
|
if (mdFiles.length === 0) return;
|
|
21906
21954
|
const files = [];
|
|
21907
21955
|
for (const filename of mdFiles) {
|
|
21908
|
-
const content = await fs4.readFile(
|
|
21956
|
+
const content = await fs4.readFile(path7.join(memoryDir, filename), "utf-8");
|
|
21909
21957
|
files.push({ filename, content });
|
|
21910
21958
|
}
|
|
21911
21959
|
const headers = createAuthHeaders(this.token);
|
|
21912
21960
|
const res = await fetch(
|
|
21913
|
-
`${this.apiUrl}/api/
|
|
21961
|
+
`${this.apiUrl}/api/local/instances/${this.instanceId}/runtime/agents/${agentId}/push-memory`,
|
|
21914
21962
|
{
|
|
21915
21963
|
method: "POST",
|
|
21916
21964
|
headers: { ...headers, "Content-Type": "application/json" },
|
|
@@ -22013,171 +22061,18 @@ async function createAgentService() {
|
|
|
22013
22061
|
});
|
|
22014
22062
|
}
|
|
22015
22063
|
|
|
22016
|
-
// src/
|
|
22017
|
-
import { spawn } from "child_process";
|
|
22064
|
+
// src/lifecycle/doctor.ts
|
|
22018
22065
|
import fs5 from "fs/promises";
|
|
22019
|
-
import http from "http";
|
|
22020
22066
|
import os5 from "os";
|
|
22021
|
-
import
|
|
22022
|
-
var
|
|
22023
|
-
var LOG_FILE = path6.join(CLAW_HOME, "openclaw.log");
|
|
22024
|
-
var PID_FILE = path6.join(CLAW_HOME, "openclaw.pid");
|
|
22025
|
-
var OPENCLAW_PORT = 18789;
|
|
22026
|
-
var HEALTH_CHECK_TIMEOUT = 5e3;
|
|
22027
|
-
var ProcessManager = class {
|
|
22028
|
-
process = null;
|
|
22029
|
-
logHandle = null;
|
|
22030
|
-
/**
|
|
22031
|
-
* Spawn the OpenClaw gateway process.
|
|
22032
|
-
* Pipes stdout/stderr to a log file and writes PID to disk.
|
|
22033
|
-
*
|
|
22034
|
-
* @param openclawBinary - Path to the openclaw binary (defaults to "openclaw" in PATH)
|
|
22035
|
-
* @param configPath - Path to openclaw.json config (defaults to ~/.workle/openclaw.json)
|
|
22036
|
-
*/
|
|
22037
|
-
async spawn(openclawBinary = "openclaw", configPath = path6.join(CLAW_HOME, "openclaw.json")) {
|
|
22038
|
-
if (this.process) {
|
|
22039
|
-
console.log("[claw] OpenClaw process already running");
|
|
22040
|
-
return;
|
|
22041
|
-
}
|
|
22042
|
-
await fs5.mkdir(CLAW_HOME, { recursive: true });
|
|
22043
|
-
this.logHandle = await fs5.open(LOG_FILE, "a");
|
|
22044
|
-
const logFd = this.logHandle.fd;
|
|
22045
|
-
this.process = spawn(openclawBinary, ["--config", configPath], {
|
|
22046
|
-
stdio: ["ignore", logFd, logFd],
|
|
22047
|
-
detached: false,
|
|
22048
|
-
// eslint-disable-next-line no-restricted-properties -- CLI tool needs access to process.env
|
|
22049
|
-
env: { ...process.env }
|
|
22050
|
-
});
|
|
22051
|
-
if (this.process.pid) {
|
|
22052
|
-
await fs5.writeFile(PID_FILE, String(this.process.pid), "utf-8");
|
|
22053
|
-
}
|
|
22054
|
-
this.process.on("exit", (code, signal) => {
|
|
22055
|
-
console.log(
|
|
22056
|
-
`[claw] OpenClaw process exited (code=${code}, signal=${signal})`
|
|
22057
|
-
);
|
|
22058
|
-
this.process = null;
|
|
22059
|
-
if (this.logHandle) {
|
|
22060
|
-
void this.logHandle.close().catch(() => {
|
|
22061
|
-
});
|
|
22062
|
-
this.logHandle = null;
|
|
22063
|
-
}
|
|
22064
|
-
fs5.unlink(PID_FILE).catch(() => {
|
|
22065
|
-
});
|
|
22066
|
-
});
|
|
22067
|
-
this.process.on("error", (err) => {
|
|
22068
|
-
console.error("[claw] Failed to spawn OpenClaw:", err);
|
|
22069
|
-
this.process = null;
|
|
22070
|
-
});
|
|
22071
|
-
console.log(`[claw] OpenClaw spawned (PID: ${this.process.pid})`);
|
|
22072
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
22073
|
-
if (!this.process) {
|
|
22074
|
-
throw new Error(
|
|
22075
|
-
"OpenClaw process exited immediately. Check logs at " + LOG_FILE
|
|
22076
|
-
);
|
|
22077
|
-
}
|
|
22078
|
-
}
|
|
22079
|
-
/**
|
|
22080
|
-
* Stop the OpenClaw gateway process.
|
|
22081
|
-
*/
|
|
22082
|
-
async stop() {
|
|
22083
|
-
if (!this.process) {
|
|
22084
|
-
try {
|
|
22085
|
-
const pidStr = await fs5.readFile(PID_FILE, "utf-8");
|
|
22086
|
-
const pid = parseInt(pidStr.trim(), 10);
|
|
22087
|
-
if (!isNaN(pid)) {
|
|
22088
|
-
process.kill(pid, "SIGTERM");
|
|
22089
|
-
await fs5.unlink(PID_FILE).catch(() => {
|
|
22090
|
-
});
|
|
22091
|
-
console.log(`[claw] Sent SIGTERM to OpenClaw (PID: ${pid})`);
|
|
22092
|
-
return;
|
|
22093
|
-
}
|
|
22094
|
-
} catch {
|
|
22095
|
-
}
|
|
22096
|
-
console.log("[claw] No OpenClaw process to stop");
|
|
22097
|
-
return;
|
|
22098
|
-
}
|
|
22099
|
-
this.process.kill("SIGTERM");
|
|
22100
|
-
console.log("[claw] Sent SIGTERM to OpenClaw");
|
|
22101
|
-
await new Promise((resolve) => {
|
|
22102
|
-
const timer = setTimeout(() => {
|
|
22103
|
-
if (this.process && !this.process.killed) {
|
|
22104
|
-
this.process.kill("SIGKILL");
|
|
22105
|
-
console.log("[claw] Force-killed OpenClaw (SIGKILL)");
|
|
22106
|
-
}
|
|
22107
|
-
resolve();
|
|
22108
|
-
}, 5e3);
|
|
22109
|
-
if (this.process) {
|
|
22110
|
-
this.process.once("exit", () => {
|
|
22111
|
-
clearTimeout(timer);
|
|
22112
|
-
resolve();
|
|
22113
|
-
});
|
|
22114
|
-
} else {
|
|
22115
|
-
clearTimeout(timer);
|
|
22116
|
-
resolve();
|
|
22117
|
-
}
|
|
22118
|
-
});
|
|
22119
|
-
this.process = null;
|
|
22120
|
-
if (this.logHandle) {
|
|
22121
|
-
await this.logHandle.close().catch(() => {
|
|
22122
|
-
});
|
|
22123
|
-
this.logHandle = null;
|
|
22124
|
-
}
|
|
22125
|
-
await fs5.unlink(PID_FILE).catch(() => {
|
|
22126
|
-
});
|
|
22127
|
-
}
|
|
22128
|
-
/**
|
|
22129
|
-
* Restart the OpenClaw gateway process.
|
|
22130
|
-
*/
|
|
22131
|
-
async restart(openclawBinary, configPath) {
|
|
22132
|
-
await this.stop();
|
|
22133
|
-
await this.spawn(openclawBinary, configPath);
|
|
22134
|
-
}
|
|
22135
|
-
/**
|
|
22136
|
-
* Check if the OpenClaw gateway is healthy by pinging the loopback port.
|
|
22137
|
-
*/
|
|
22138
|
-
async isHealthy() {
|
|
22139
|
-
return new Promise((resolve) => {
|
|
22140
|
-
const req = http.request(
|
|
22141
|
-
{
|
|
22142
|
-
hostname: "127.0.0.1",
|
|
22143
|
-
port: OPENCLAW_PORT,
|
|
22144
|
-
path: "/",
|
|
22145
|
-
method: "GET",
|
|
22146
|
-
timeout: HEALTH_CHECK_TIMEOUT
|
|
22147
|
-
},
|
|
22148
|
-
(res) => {
|
|
22149
|
-
res.resume();
|
|
22150
|
-
resolve(true);
|
|
22151
|
-
}
|
|
22152
|
-
);
|
|
22153
|
-
req.on("error", () => {
|
|
22154
|
-
resolve(false);
|
|
22155
|
-
});
|
|
22156
|
-
req.on("timeout", () => {
|
|
22157
|
-
req.destroy();
|
|
22158
|
-
resolve(false);
|
|
22159
|
-
});
|
|
22160
|
-
req.end();
|
|
22161
|
-
});
|
|
22162
|
-
}
|
|
22163
|
-
};
|
|
22164
|
-
|
|
22165
|
-
// src/lifecycle/doctor.ts
|
|
22166
|
-
import { execSync } from "child_process";
|
|
22167
|
-
import fs6 from "fs/promises";
|
|
22168
|
-
import os6 from "os";
|
|
22169
|
-
import path7 from "path";
|
|
22170
|
-
var WORKLE_HOME2 = path7.join(os6.homedir(), ".workle");
|
|
22067
|
+
import path8 from "path";
|
|
22068
|
+
var WORKLE_HOME2 = path8.join(os5.homedir(), ".workle");
|
|
22171
22069
|
var MIN_NODE_MAJOR = 22;
|
|
22172
22070
|
async function runDoctor() {
|
|
22173
22071
|
const checks = [];
|
|
22174
22072
|
checks.push(checkNodeVersion());
|
|
22175
|
-
checks.push(
|
|
22176
|
-
checks.push(await checkClawHome());
|
|
22073
|
+
checks.push(await checkLocalHome());
|
|
22177
22074
|
checks.push(await checkAuthFile());
|
|
22178
|
-
|
|
22179
|
-
checks.push(await checkGatewayReachable());
|
|
22180
|
-
console.log("\n Workle Claw Doctor\n");
|
|
22075
|
+
console.log("\n Workle Local Doctor\n");
|
|
22181
22076
|
for (const check2 of checks) {
|
|
22182
22077
|
const icon = check2.status === "pass" ? "[OK]" : check2.status === "warn" ? "[WARN]" : "[FAIL]";
|
|
22183
22078
|
console.log(` ${icon} ${check2.name}: ${check2.message}`);
|
|
@@ -22207,52 +22102,33 @@ function checkNodeVersion() {
|
|
|
22207
22102
|
message: `${process.version} \u2014 Node >= ${MIN_NODE_MAJOR} is required`
|
|
22208
22103
|
};
|
|
22209
22104
|
}
|
|
22210
|
-
function
|
|
22211
|
-
try {
|
|
22212
|
-
const version3 = execSync("openclaw --version", {
|
|
22213
|
-
encoding: "utf-8",
|
|
22214
|
-
timeout: 5e3
|
|
22215
|
-
}).trim();
|
|
22216
|
-
return {
|
|
22217
|
-
name: "OpenClaw binary",
|
|
22218
|
-
status: "pass",
|
|
22219
|
-
message: `Found: ${version3}`
|
|
22220
|
-
};
|
|
22221
|
-
} catch {
|
|
22222
|
-
return {
|
|
22223
|
-
name: "OpenClaw binary",
|
|
22224
|
-
status: "fail",
|
|
22225
|
-
message: "openclaw not found in PATH. Install from https://openclaw.dev"
|
|
22226
|
-
};
|
|
22227
|
-
}
|
|
22228
|
-
}
|
|
22229
|
-
async function checkClawHome() {
|
|
22105
|
+
async function checkLocalHome() {
|
|
22230
22106
|
try {
|
|
22231
|
-
const stat = await
|
|
22107
|
+
const stat = await fs5.stat(WORKLE_HOME2);
|
|
22232
22108
|
if (stat.isDirectory()) {
|
|
22233
22109
|
return {
|
|
22234
|
-
name: "
|
|
22110
|
+
name: "Home directory",
|
|
22235
22111
|
status: "pass",
|
|
22236
22112
|
message: WORKLE_HOME2
|
|
22237
22113
|
};
|
|
22238
22114
|
}
|
|
22239
22115
|
return {
|
|
22240
|
-
name: "
|
|
22116
|
+
name: "Home directory",
|
|
22241
22117
|
status: "fail",
|
|
22242
22118
|
message: `${WORKLE_HOME2} exists but is not a directory`
|
|
22243
22119
|
};
|
|
22244
22120
|
} catch {
|
|
22245
22121
|
return {
|
|
22246
|
-
name: "
|
|
22122
|
+
name: "Home directory",
|
|
22247
22123
|
status: "fail",
|
|
22248
22124
|
message: `${WORKLE_HOME2} does not exist. Run 'workle setup'`
|
|
22249
22125
|
};
|
|
22250
22126
|
}
|
|
22251
22127
|
}
|
|
22252
22128
|
async function checkAuthFile() {
|
|
22253
|
-
const authPath =
|
|
22129
|
+
const authPath = path8.join(WORKLE_HOME2, "auth.json");
|
|
22254
22130
|
try {
|
|
22255
|
-
const raw = await
|
|
22131
|
+
const raw = await fs5.readFile(authPath, "utf-8");
|
|
22256
22132
|
const parsed = JSON.parse(raw);
|
|
22257
22133
|
if (parsed.instanceId && parsed.token && parsed.apiUrl) {
|
|
22258
22134
|
return {
|
|
@@ -22274,99 +22150,53 @@ async function checkAuthFile() {
|
|
|
22274
22150
|
};
|
|
22275
22151
|
}
|
|
22276
22152
|
}
|
|
22277
|
-
async function checkConfigFile() {
|
|
22278
|
-
const configPath = path7.join(WORKLE_HOME2, "openclaw.json");
|
|
22279
|
-
try {
|
|
22280
|
-
const raw = await fs6.readFile(configPath, "utf-8");
|
|
22281
|
-
const parsed = JSON.parse(raw);
|
|
22282
|
-
if (parsed.agents) {
|
|
22283
|
-
return {
|
|
22284
|
-
name: "Gateway config",
|
|
22285
|
-
status: "pass",
|
|
22286
|
-
message: configPath
|
|
22287
|
-
};
|
|
22288
|
-
}
|
|
22289
|
-
return {
|
|
22290
|
-
name: "Gateway config",
|
|
22291
|
-
status: "warn",
|
|
22292
|
-
message: "openclaw.json exists but has no agents section"
|
|
22293
|
-
};
|
|
22294
|
-
} catch {
|
|
22295
|
-
return {
|
|
22296
|
-
name: "Gateway config",
|
|
22297
|
-
status: "warn",
|
|
22298
|
-
message: `${configPath} not found. Will be created on first sync`
|
|
22299
|
-
};
|
|
22300
|
-
}
|
|
22301
|
-
}
|
|
22302
|
-
async function checkGatewayReachable() {
|
|
22303
|
-
try {
|
|
22304
|
-
const controller = new AbortController();
|
|
22305
|
-
const timer = setTimeout(() => controller.abort(), 3e3);
|
|
22306
|
-
const res = await fetch("http://127.0.0.1:18789/", {
|
|
22307
|
-
signal: controller.signal
|
|
22308
|
-
});
|
|
22309
|
-
clearTimeout(timer);
|
|
22310
|
-
return {
|
|
22311
|
-
name: "OpenClaw gateway",
|
|
22312
|
-
status: "pass",
|
|
22313
|
-
message: `Reachable on :18789 (HTTP ${res.status})`
|
|
22314
|
-
};
|
|
22315
|
-
} catch {
|
|
22316
|
-
return {
|
|
22317
|
-
name: "OpenClaw gateway",
|
|
22318
|
-
status: "warn",
|
|
22319
|
-
message: "Not reachable on :18789. Start with 'workle start'"
|
|
22320
|
-
};
|
|
22321
|
-
}
|
|
22322
|
-
}
|
|
22323
22153
|
|
|
22324
22154
|
// src/lifecycle/install.ts
|
|
22325
|
-
import
|
|
22326
|
-
import
|
|
22327
|
-
import
|
|
22328
|
-
var WORKLE_HOME3 =
|
|
22329
|
-
var LAUNCH_AGENTS_DIR =
|
|
22330
|
-
var STAGED_CLI_PATH =
|
|
22155
|
+
import fs6 from "fs/promises";
|
|
22156
|
+
import os6 from "os";
|
|
22157
|
+
import path9 from "path";
|
|
22158
|
+
var WORKLE_HOME3 = path9.join(os6.homedir(), ".workle");
|
|
22159
|
+
var LAUNCH_AGENTS_DIR = path9.join(os6.homedir(), "Library", "LaunchAgents");
|
|
22160
|
+
var STAGED_CLI_PATH = path9.join(WORKLE_HOME3, "bin", "cli.js");
|
|
22331
22161
|
function escapeXml(value) {
|
|
22332
22162
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
22333
22163
|
}
|
|
22334
|
-
async function
|
|
22164
|
+
async function setupLocalDirectory() {
|
|
22335
22165
|
const dirs = [
|
|
22336
22166
|
WORKLE_HOME3,
|
|
22337
|
-
|
|
22338
|
-
|
|
22167
|
+
path9.join(WORKLE_HOME3, "logs"),
|
|
22168
|
+
path9.join(WORKLE_HOME3, "bin")
|
|
22339
22169
|
];
|
|
22340
22170
|
for (const dir of dirs) {
|
|
22341
|
-
await
|
|
22342
|
-
await
|
|
22171
|
+
await fs6.mkdir(dir, { recursive: true, mode: 448 });
|
|
22172
|
+
await fs6.chmod(dir, 448).catch(() => {
|
|
22343
22173
|
});
|
|
22344
22174
|
}
|
|
22345
|
-
const gitignorePath =
|
|
22175
|
+
const gitignorePath = path9.join(WORKLE_HOME3, ".gitignore");
|
|
22346
22176
|
try {
|
|
22347
|
-
await
|
|
22177
|
+
await fs6.access(gitignorePath);
|
|
22348
22178
|
} catch {
|
|
22349
|
-
await
|
|
22179
|
+
await fs6.writeFile(gitignorePath, "*\n", "utf-8");
|
|
22350
22180
|
}
|
|
22351
|
-
console.log(`[
|
|
22181
|
+
console.log(`[local] Directory structure created at ${WORKLE_HOME3}`);
|
|
22352
22182
|
}
|
|
22353
22183
|
async function stageCliForDaemon(currentCliPath) {
|
|
22354
22184
|
if (!currentCliPath) {
|
|
22355
22185
|
throw new Error("Cannot determine the current CLI path for daemon install.");
|
|
22356
22186
|
}
|
|
22357
|
-
await
|
|
22187
|
+
await setupLocalDirectory();
|
|
22358
22188
|
const isNpxCache = currentCliPath.includes("/_npx/") || currentCliPath.includes("\\_npx\\");
|
|
22359
22189
|
if (!isNpxCache) {
|
|
22360
22190
|
return currentCliPath;
|
|
22361
22191
|
}
|
|
22362
|
-
await
|
|
22363
|
-
await
|
|
22192
|
+
await fs6.copyFile(currentCliPath, STAGED_CLI_PATH);
|
|
22193
|
+
await fs6.chmod(STAGED_CLI_PATH, 448).catch(() => {
|
|
22364
22194
|
});
|
|
22365
22195
|
return STAGED_CLI_PATH;
|
|
22366
22196
|
}
|
|
22367
|
-
async function writeLaunchdPlist(serviceName = "com.workle.
|
|
22368
|
-
await
|
|
22369
|
-
const plistPath =
|
|
22197
|
+
async function writeLaunchdPlist(serviceName = "com.workle.local", executable = process.execPath, args = [STAGED_CLI_PATH, "start"]) {
|
|
22198
|
+
await fs6.mkdir(LAUNCH_AGENTS_DIR, { recursive: true });
|
|
22199
|
+
const plistPath = path9.join(LAUNCH_AGENTS_DIR, `${serviceName}.plist`);
|
|
22370
22200
|
const programArguments = [executable, ...args].map((arg) => ` <string>${escapeXml(arg)}</string>`).join("\n");
|
|
22371
22201
|
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
22372
22202
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -22390,10 +22220,10 @@ ${programArguments}
|
|
|
22390
22220
|
</dict>
|
|
22391
22221
|
|
|
22392
22222
|
<key>StandardOutPath</key>
|
|
22393
|
-
<string>${
|
|
22223
|
+
<string>${path9.join(WORKLE_HOME3, "logs", "stdout.log")}</string>
|
|
22394
22224
|
|
|
22395
22225
|
<key>StandardErrorPath</key>
|
|
22396
|
-
<string>${
|
|
22226
|
+
<string>${path9.join(WORKLE_HOME3, "logs", "stderr.log")}</string>
|
|
22397
22227
|
|
|
22398
22228
|
<key>WorkingDirectory</key>
|
|
22399
22229
|
<string>${WORKLE_HOME3}</string>
|
|
@@ -22403,867 +22233,19 @@ ${programArguments}
|
|
|
22403
22233
|
</dict>
|
|
22404
22234
|
</plist>
|
|
22405
22235
|
`;
|
|
22406
|
-
await
|
|
22407
|
-
console.log(`[
|
|
22236
|
+
await fs6.writeFile(plistPath, plist, "utf-8");
|
|
22237
|
+
console.log(`[local] Launchd plist written to ${plistPath}`);
|
|
22408
22238
|
console.log(
|
|
22409
|
-
`[
|
|
22239
|
+
`[local] To enable: launchctl load ${plistPath}`
|
|
22410
22240
|
);
|
|
22411
22241
|
return plistPath;
|
|
22412
22242
|
}
|
|
22413
22243
|
|
|
22414
22244
|
// src/sync/service.ts
|
|
22415
|
-
import fs15 from "fs/promises";
|
|
22416
|
-
import os17 from "os";
|
|
22417
|
-
import path18 from "path";
|
|
22418
|
-
|
|
22419
|
-
// src/legacy/openclaw/gateway.ts
|
|
22420
|
-
import os8 from "os";
|
|
22421
|
-
import path9 from "path";
|
|
22422
|
-
var CLAW_HOME2 = path9.join(os8.homedir(), ".workle");
|
|
22423
|
-
function generateGatewayConfig(_instance, agents) {
|
|
22424
|
-
const sorted = [...agents].sort((a, b) => a.sortOrder - b.sortOrder);
|
|
22425
|
-
const list = sorted.map((agent) => ({
|
|
22426
|
-
id: agent.id,
|
|
22427
|
-
default: !agent.isSubAgent,
|
|
22428
|
-
workspace: path9.join(CLAW_HOME2, `workspace-${agent.agentKey}`),
|
|
22429
|
-
model: agent.model,
|
|
22430
|
-
toolProfile: agent.toolProfile,
|
|
22431
|
-
promptMode: agent.promptMode
|
|
22432
|
-
}));
|
|
22433
|
-
return {
|
|
22434
|
-
agents: { list }
|
|
22435
|
-
};
|
|
22436
|
-
}
|
|
22437
|
-
|
|
22438
|
-
// src/legacy/openclaw/soul.ts
|
|
22439
|
-
import fs8 from "fs/promises";
|
|
22440
|
-
import os9 from "os";
|
|
22441
|
-
import path10 from "path";
|
|
22442
|
-
var CLAW_HOME3 = path10.join(os9.homedir(), ".workle");
|
|
22443
|
-
var DEFAULT_SOUL_MD = `# Soul
|
|
22444
|
-
|
|
22445
|
-
You are a Workle agent. Follow your assigned skills and operational instructions.
|
|
22446
|
-
Be helpful, accurate, and concise. Always use available tools before guessing.
|
|
22447
|
-
`;
|
|
22448
|
-
async function writeSoulMd(agentKey, content, rpcCall) {
|
|
22449
|
-
const body = content?.trim() ? content : DEFAULT_SOUL_MD;
|
|
22450
|
-
if (rpcCall) {
|
|
22451
|
-
await rpcCall(agentKey, "SOUL.md", body);
|
|
22452
|
-
return;
|
|
22453
|
-
}
|
|
22454
|
-
const workspaceDir = path10.join(CLAW_HOME3, `workspace-${agentKey}`);
|
|
22455
|
-
await fs8.mkdir(workspaceDir, { recursive: true });
|
|
22456
|
-
const filePath = path10.join(workspaceDir, "SOUL.md");
|
|
22457
|
-
await fs8.writeFile(filePath, body, "utf-8");
|
|
22458
|
-
}
|
|
22459
|
-
|
|
22460
|
-
// src/legacy/openclaw/agents.ts
|
|
22461
|
-
import fs9 from "fs/promises";
|
|
22462
|
-
import os10 from "os";
|
|
22463
|
-
import path11 from "path";
|
|
22464
|
-
var CLAW_HOME4 = path11.join(os10.homedir(), ".workle");
|
|
22465
|
-
var DEFAULT_AGENTS_MD = `# Agents
|
|
22466
|
-
|
|
22467
|
-
No additional agent configuration provided.
|
|
22468
|
-
`;
|
|
22469
|
-
async function writeAgentsMd(agentKey, content, rpcCall) {
|
|
22470
|
-
const body = content?.trim() ? content : DEFAULT_AGENTS_MD;
|
|
22471
|
-
if (rpcCall) {
|
|
22472
|
-
await rpcCall(agentKey, "AGENTS.md", body);
|
|
22473
|
-
return;
|
|
22474
|
-
}
|
|
22475
|
-
const workspaceDir = path11.join(CLAW_HOME4, `workspace-${agentKey}`);
|
|
22476
|
-
await fs9.mkdir(workspaceDir, { recursive: true });
|
|
22477
|
-
const filePath = path11.join(workspaceDir, "AGENTS.md");
|
|
22478
|
-
await fs9.writeFile(filePath, body, "utf-8");
|
|
22479
|
-
}
|
|
22480
|
-
|
|
22481
|
-
// src/config/identity.ts
|
|
22482
|
-
import fs10 from "fs/promises";
|
|
22483
|
-
import os11 from "os";
|
|
22484
|
-
import path12 from "path";
|
|
22485
|
-
var WORKLE_HOME4 = path12.join(os11.homedir(), ".workle");
|
|
22486
|
-
var DEFAULT_IDENTITY_MD = `# Identity
|
|
22487
|
-
|
|
22488
|
-
No identity configuration provided.
|
|
22489
|
-
`;
|
|
22490
|
-
async function writeIdentityMd(agentKey, content, rpcCall) {
|
|
22491
|
-
const body = content?.trim() ? content : DEFAULT_IDENTITY_MD;
|
|
22492
|
-
if (rpcCall) {
|
|
22493
|
-
await rpcCall(agentKey, "IDENTITY.md", body);
|
|
22494
|
-
return;
|
|
22495
|
-
}
|
|
22496
|
-
const workspaceDir = path12.join(WORKLE_HOME4, `workspace-${agentKey}`);
|
|
22497
|
-
await fs10.mkdir(workspaceDir, { recursive: true });
|
|
22498
|
-
const filePath = path12.join(workspaceDir, "IDENTITY.md");
|
|
22499
|
-
await fs10.writeFile(filePath, body, "utf-8");
|
|
22500
|
-
}
|
|
22501
|
-
|
|
22502
|
-
// src/config/heartbeat.ts
|
|
22503
|
-
import fs11 from "fs/promises";
|
|
22504
|
-
import os12 from "os";
|
|
22505
|
-
import path13 from "path";
|
|
22506
|
-
var WORKLE_HOME5 = path13.join(os12.homedir(), ".workle");
|
|
22507
|
-
var DEFAULT_HEARTBEAT_MD = `# Heartbeat
|
|
22508
|
-
|
|
22509
|
-
No heartbeat configuration provided.
|
|
22510
|
-
`;
|
|
22511
|
-
async function writeHeartbeatMd(agentKey, content, rpcCall) {
|
|
22512
|
-
const body = content?.trim() ? content : DEFAULT_HEARTBEAT_MD;
|
|
22513
|
-
if (rpcCall) {
|
|
22514
|
-
await rpcCall(agentKey, "HEARTBEAT.md", body);
|
|
22515
|
-
return;
|
|
22516
|
-
}
|
|
22517
|
-
const workspaceDir = path13.join(WORKLE_HOME5, `workspace-${agentKey}`);
|
|
22518
|
-
await fs11.mkdir(workspaceDir, { recursive: true });
|
|
22519
|
-
const filePath = path13.join(workspaceDir, "HEARTBEAT.md");
|
|
22520
|
-
await fs11.writeFile(filePath, body, "utf-8");
|
|
22521
|
-
}
|
|
22522
|
-
|
|
22523
|
-
// src/config/skills.ts
|
|
22524
|
-
import fs12 from "fs/promises";
|
|
22525
|
-
import os13 from "os";
|
|
22526
|
-
import path14 from "path";
|
|
22527
|
-
var WORKLE_HOME6 = path14.join(os13.homedir(), ".workle");
|
|
22528
|
-
|
|
22529
|
-
// src/legacy/openclaw/rpc.ts
|
|
22530
|
-
import crypto4 from "crypto";
|
|
22531
|
-
import fs13 from "fs";
|
|
22532
|
-
import os14 from "os";
|
|
22533
|
-
import path15 from "path";
|
|
22534
|
-
var OPENCLAW_PORT2 = 18789;
|
|
22535
|
-
var OPENCLAW_URL = `ws://127.0.0.1:${OPENCLAW_PORT2}`;
|
|
22536
|
-
var RPC_TIMEOUT = 3e4;
|
|
22537
|
-
var MAX_RECONNECT_BACKOFF = 1e4;
|
|
22538
|
-
var CLAW_VERSION = "0.1.16";
|
|
22539
|
-
var OPENCLAW_CONFIG_PATHS = [
|
|
22540
|
-
() => path15.join(os14.homedir(), ".openclaw", "openclaw.json"),
|
|
22541
|
-
() => path15.join(os14.homedir(), ".config", "openclaw", "openclaw.json")
|
|
22542
|
-
];
|
|
22543
|
-
var CLAW_IDENTITY_DIR = path15.join(os14.homedir(), ".workle");
|
|
22544
|
-
var CLAW_IDENTITY_FILE = path15.join(CLAW_IDENTITY_DIR, "device-identity.json");
|
|
22545
|
-
var ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
|
|
22546
|
-
function base64UrlEncode(buf) {
|
|
22547
|
-
return buf.toString("base64").replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/g, "");
|
|
22548
|
-
}
|
|
22549
|
-
function derivePublicKeyRaw(publicKeyPem) {
|
|
22550
|
-
const key = crypto4.createPublicKey(publicKeyPem);
|
|
22551
|
-
const spki = key.export({ type: "spki", format: "der" });
|
|
22552
|
-
if (spki.length === ED25519_SPKI_PREFIX.length + 32 && spki.subarray(0, ED25519_SPKI_PREFIX.length).equals(ED25519_SPKI_PREFIX)) {
|
|
22553
|
-
return spki.subarray(ED25519_SPKI_PREFIX.length);
|
|
22554
|
-
}
|
|
22555
|
-
return spki;
|
|
22556
|
-
}
|
|
22557
|
-
function fingerprintPublicKey(publicKeyPem) {
|
|
22558
|
-
const raw = derivePublicKeyRaw(publicKeyPem);
|
|
22559
|
-
return crypto4.createHash("sha256").update(raw).digest("hex");
|
|
22560
|
-
}
|
|
22561
|
-
function loadOrCreateDeviceIdentity() {
|
|
22562
|
-
try {
|
|
22563
|
-
if (fs13.existsSync(CLAW_IDENTITY_FILE)) {
|
|
22564
|
-
const raw = fs13.readFileSync(CLAW_IDENTITY_FILE, "utf8");
|
|
22565
|
-
const parsed = JSON.parse(raw);
|
|
22566
|
-
if (parsed?.version === 1 && typeof parsed.deviceId === "string" && typeof parsed.publicKeyPem === "string" && typeof parsed.privateKeyPem === "string") {
|
|
22567
|
-
const derivedId = fingerprintPublicKey(parsed.publicKeyPem);
|
|
22568
|
-
const deviceId2 = derivedId === parsed.deviceId ? parsed.deviceId : derivedId;
|
|
22569
|
-
console.log(`[claw] Loaded device identity: ${deviceId2.slice(0, 12)}...`);
|
|
22570
|
-
return {
|
|
22571
|
-
deviceId: deviceId2,
|
|
22572
|
-
publicKeyPem: parsed.publicKeyPem,
|
|
22573
|
-
privateKeyPem: parsed.privateKeyPem
|
|
22574
|
-
};
|
|
22575
|
-
}
|
|
22576
|
-
}
|
|
22577
|
-
} catch {
|
|
22578
|
-
}
|
|
22579
|
-
const { publicKey, privateKey } = crypto4.generateKeyPairSync("ed25519");
|
|
22580
|
-
const publicKeyPem = publicKey.export({ type: "spki", format: "pem" }).toString();
|
|
22581
|
-
const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" }).toString();
|
|
22582
|
-
const deviceId = fingerprintPublicKey(publicKeyPem);
|
|
22583
|
-
fs13.mkdirSync(CLAW_IDENTITY_DIR, { recursive: true });
|
|
22584
|
-
const stored = {
|
|
22585
|
-
version: 1,
|
|
22586
|
-
deviceId,
|
|
22587
|
-
publicKeyPem,
|
|
22588
|
-
privateKeyPem,
|
|
22589
|
-
createdAtMs: Date.now()
|
|
22590
|
-
};
|
|
22591
|
-
fs13.writeFileSync(CLAW_IDENTITY_FILE, `${JSON.stringify(stored, null, 2)}
|
|
22592
|
-
`, { mode: 384 });
|
|
22593
|
-
try {
|
|
22594
|
-
fs13.chmodSync(CLAW_IDENTITY_FILE, 384);
|
|
22595
|
-
} catch {
|
|
22596
|
-
}
|
|
22597
|
-
console.log(`[claw] Generated new device identity: ${deviceId.slice(0, 12)}...`);
|
|
22598
|
-
return { deviceId, publicKeyPem, privateKeyPem };
|
|
22599
|
-
}
|
|
22600
|
-
function buildDeviceAuthPayload(params) {
|
|
22601
|
-
return [
|
|
22602
|
-
"v2",
|
|
22603
|
-
params.deviceId,
|
|
22604
|
-
params.clientId,
|
|
22605
|
-
params.clientMode,
|
|
22606
|
-
params.role,
|
|
22607
|
-
params.scopes.join(","),
|
|
22608
|
-
String(params.signedAtMs),
|
|
22609
|
-
params.token,
|
|
22610
|
-
params.nonce
|
|
22611
|
-
].join("|");
|
|
22612
|
-
}
|
|
22613
|
-
function signPayload(privateKeyPem, payload) {
|
|
22614
|
-
const key = crypto4.createPrivateKey(privateKeyPem);
|
|
22615
|
-
const sig = crypto4.sign(null, Buffer.from(payload, "utf8"), key);
|
|
22616
|
-
return base64UrlEncode(sig);
|
|
22617
|
-
}
|
|
22618
|
-
function readGatewayAuth() {
|
|
22619
|
-
const envToken = process.env.OPENCLAW_GATEWAY_TOKEN?.trim() || process.env.CLAWDBOT_GATEWAY_TOKEN?.trim();
|
|
22620
|
-
if (envToken) {
|
|
22621
|
-
console.log("[claw] Using gateway token from environment variable");
|
|
22622
|
-
return { token: envToken };
|
|
22623
|
-
}
|
|
22624
|
-
const envPassword = process.env.OPENCLAW_GATEWAY_PASSWORD?.trim() || process.env.CLAWDBOT_GATEWAY_PASSWORD?.trim();
|
|
22625
|
-
if (envPassword) {
|
|
22626
|
-
console.log("[claw] Using gateway password from environment variable");
|
|
22627
|
-
return { password: envPassword };
|
|
22628
|
-
}
|
|
22629
|
-
for (const getPath of OPENCLAW_CONFIG_PATHS) {
|
|
22630
|
-
try {
|
|
22631
|
-
const configPath = getPath();
|
|
22632
|
-
const raw = fs13.readFileSync(configPath, "utf-8");
|
|
22633
|
-
const config2 = JSON.parse(raw);
|
|
22634
|
-
const token = config2.gateway?.auth?.token?.trim();
|
|
22635
|
-
const password = config2.gateway?.auth?.password?.trim();
|
|
22636
|
-
if (token || password) {
|
|
22637
|
-
console.log(
|
|
22638
|
-
`[claw] Using gateway ${token ? "token" : "password"} from ${configPath}`
|
|
22639
|
-
);
|
|
22640
|
-
return { token: token || void 0, password: password || void 0 };
|
|
22641
|
-
}
|
|
22642
|
-
} catch {
|
|
22643
|
-
}
|
|
22644
|
-
}
|
|
22645
|
-
console.warn(
|
|
22646
|
-
"[claw] \u26A0 No gateway auth found! Set OPENCLAW_GATEWAY_TOKEN or configure gateway.auth.token in ~/.openclaw/openclaw.json"
|
|
22647
|
-
);
|
|
22648
|
-
return void 0;
|
|
22649
|
-
}
|
|
22650
|
-
var OpenClawRpcClient = class {
|
|
22651
|
-
ws = null;
|
|
22652
|
-
pendingCalls = /* @__PURE__ */ new Map();
|
|
22653
|
-
eventHandlers = [];
|
|
22654
|
-
callCounter = 0;
|
|
22655
|
-
_connected = false;
|
|
22656
|
-
/** True after the connect handshake completes (hello-ok received) */
|
|
22657
|
-
_handshakeComplete = false;
|
|
22658
|
-
shouldReconnect = false;
|
|
22659
|
-
reconnectTimer = null;
|
|
22660
|
-
reconnectBackoff = 1e3;
|
|
22661
|
-
get connected() {
|
|
22662
|
-
return this._connected && this._handshakeComplete;
|
|
22663
|
-
}
|
|
22664
|
-
/**
|
|
22665
|
-
* Connect to the OpenClaw gateway WebSocket and complete the handshake.
|
|
22666
|
-
*/
|
|
22667
|
-
async connect() {
|
|
22668
|
-
if (this.ws && this._connected && this._handshakeComplete) return;
|
|
22669
|
-
this.shouldReconnect = true;
|
|
22670
|
-
return this.doConnect();
|
|
22671
|
-
}
|
|
22672
|
-
doConnect() {
|
|
22673
|
-
return new Promise((resolve, reject) => {
|
|
22674
|
-
let resolved = false;
|
|
22675
|
-
this._handshakeComplete = false;
|
|
22676
|
-
let challengeNonce;
|
|
22677
|
-
this.ws = new wrapper_default(OPENCLAW_URL);
|
|
22678
|
-
this.ws.on("open", () => {
|
|
22679
|
-
console.log("[claw] OpenClaw RPC WebSocket opened");
|
|
22680
|
-
this._connected = true;
|
|
22681
|
-
this.reconnectBackoff = 1e3;
|
|
22682
|
-
});
|
|
22683
|
-
this.ws.on("message", (data) => {
|
|
22684
|
-
try {
|
|
22685
|
-
const text = typeof data === "string" ? data : Buffer.from(data).toString("utf-8");
|
|
22686
|
-
const frame = JSON.parse(text);
|
|
22687
|
-
if (frame.type === "res") {
|
|
22688
|
-
if (!this._handshakeComplete && frame.ok) {
|
|
22689
|
-
this._handshakeComplete = true;
|
|
22690
|
-
console.log("[claw] OpenClaw handshake complete");
|
|
22691
|
-
if (!resolved) {
|
|
22692
|
-
resolved = true;
|
|
22693
|
-
resolve();
|
|
22694
|
-
}
|
|
22695
|
-
return;
|
|
22696
|
-
}
|
|
22697
|
-
if (!this._handshakeComplete && !frame.ok) {
|
|
22698
|
-
const errMsg = frame.error?.message ?? "handshake rejected";
|
|
22699
|
-
console.error(`[claw] OpenClaw handshake failed: ${errMsg}`);
|
|
22700
|
-
if (!resolved) {
|
|
22701
|
-
resolved = true;
|
|
22702
|
-
reject(new Error(`Gateway handshake failed: ${errMsg}`));
|
|
22703
|
-
}
|
|
22704
|
-
return;
|
|
22705
|
-
}
|
|
22706
|
-
this.handleResponse(frame);
|
|
22707
|
-
} else if (frame.type === "event") {
|
|
22708
|
-
if (frame.event === "connect.challenge" && !this._handshakeComplete) {
|
|
22709
|
-
const payload = frame.payload;
|
|
22710
|
-
challengeNonce = payload?.nonce;
|
|
22711
|
-
console.log("[claw] Received connect.challenge, sending handshake...");
|
|
22712
|
-
this.sendConnectHandshake(challengeNonce);
|
|
22713
|
-
return;
|
|
22714
|
-
}
|
|
22715
|
-
this.handleEvent(frame);
|
|
22716
|
-
}
|
|
22717
|
-
} catch {
|
|
22718
|
-
}
|
|
22719
|
-
});
|
|
22720
|
-
this.ws.on("close", (code, reason) => {
|
|
22721
|
-
const reasonStr = reason ? Buffer.from(reason).toString("utf-8") : "";
|
|
22722
|
-
console.log(
|
|
22723
|
-
`[claw] OpenClaw RPC WebSocket closed: code=${code} reason="${reasonStr}"`
|
|
22724
|
-
);
|
|
22725
|
-
this._connected = false;
|
|
22726
|
-
this._handshakeComplete = false;
|
|
22727
|
-
this.rejectAllPending(new Error("WebSocket connection closed"));
|
|
22728
|
-
if (!resolved) {
|
|
22729
|
-
resolved = true;
|
|
22730
|
-
reject(new Error(`Gateway closed during handshake: ${reasonStr}`));
|
|
22731
|
-
}
|
|
22732
|
-
this.scheduleReconnect();
|
|
22733
|
-
});
|
|
22734
|
-
this.ws.on("error", (err) => {
|
|
22735
|
-
this._connected = false;
|
|
22736
|
-
this._handshakeComplete = false;
|
|
22737
|
-
if (!resolved) {
|
|
22738
|
-
resolved = true;
|
|
22739
|
-
reject(err);
|
|
22740
|
-
}
|
|
22741
|
-
});
|
|
22742
|
-
});
|
|
22743
|
-
}
|
|
22744
|
-
/**
|
|
22745
|
-
* Send the connect handshake required by the OpenClaw gateway protocol.
|
|
22746
|
-
*
|
|
22747
|
-
* Flow:
|
|
22748
|
-
* 1. Gateway sends connect.challenge with { nonce, ts }
|
|
22749
|
-
* 2. Client builds device identity + signs the nonce
|
|
22750
|
-
* 3. Client sends connect request with device, auth, role, and scopes
|
|
22751
|
-
* 4. Gateway auto-approves pairing for loopback connections (silent)
|
|
22752
|
-
* 5. Gateway responds with hello-ok including granted scopes
|
|
22753
|
-
*
|
|
22754
|
-
* CRITICAL: The gateway strips ALL self-declared scopes from connections
|
|
22755
|
-
* that lack a signed device identity, even if the auth token is valid.
|
|
22756
|
-
* The device identity is what binds scopes to a connection.
|
|
22757
|
-
*/
|
|
22758
|
-
sendConnectHandshake(challengeNonce) {
|
|
22759
|
-
const gatewayAuth = readGatewayAuth();
|
|
22760
|
-
const identity = loadOrCreateDeviceIdentity();
|
|
22761
|
-
const role = "operator";
|
|
22762
|
-
const scopes = ["operator.admin"];
|
|
22763
|
-
const signedAtMs = Date.now();
|
|
22764
|
-
const authToken = gatewayAuth?.token ?? "";
|
|
22765
|
-
const payload = buildDeviceAuthPayload({
|
|
22766
|
-
deviceId: identity.deviceId,
|
|
22767
|
-
clientId: "gateway-client",
|
|
22768
|
-
clientMode: "backend",
|
|
22769
|
-
role,
|
|
22770
|
-
scopes,
|
|
22771
|
-
signedAtMs,
|
|
22772
|
-
token: authToken,
|
|
22773
|
-
nonce: challengeNonce ?? ""
|
|
22774
|
-
});
|
|
22775
|
-
const signature = signPayload(identity.privateKeyPem, payload);
|
|
22776
|
-
const publicKeyBase64Url = base64UrlEncode(derivePublicKeyRaw(identity.publicKeyPem));
|
|
22777
|
-
const connectFrame = {
|
|
22778
|
-
type: "req",
|
|
22779
|
-
id: `rpc_connect_${Date.now()}`,
|
|
22780
|
-
method: "connect",
|
|
22781
|
-
params: {
|
|
22782
|
-
minProtocol: 3,
|
|
22783
|
-
maxProtocol: 3,
|
|
22784
|
-
client: {
|
|
22785
|
-
id: "gateway-client",
|
|
22786
|
-
displayName: "Workle Claw",
|
|
22787
|
-
version: CLAW_VERSION,
|
|
22788
|
-
platform: "node",
|
|
22789
|
-
mode: "backend"
|
|
22790
|
-
},
|
|
22791
|
-
role,
|
|
22792
|
-
scopes,
|
|
22793
|
-
device: {
|
|
22794
|
-
id: identity.deviceId,
|
|
22795
|
-
publicKey: publicKeyBase64Url,
|
|
22796
|
-
signature,
|
|
22797
|
-
signedAt: signedAtMs,
|
|
22798
|
-
nonce: challengeNonce ?? void 0
|
|
22799
|
-
},
|
|
22800
|
-
...gatewayAuth ? { auth: gatewayAuth } : {}
|
|
22801
|
-
}
|
|
22802
|
-
};
|
|
22803
|
-
const authKind = gatewayAuth?.token ? "token" : gatewayAuth?.password ? "password" : "none";
|
|
22804
|
-
console.log(
|
|
22805
|
-
`[claw] Sending connect handshake (auth=${authKind}, device=${identity.deviceId.slice(0, 12)}..., scopes=operator.admin)`
|
|
22806
|
-
);
|
|
22807
|
-
this.ws.send(JSON.stringify(connectFrame));
|
|
22808
|
-
}
|
|
22809
|
-
scheduleReconnect() {
|
|
22810
|
-
if (!this.shouldReconnect) return;
|
|
22811
|
-
if (this.reconnectTimer) return;
|
|
22812
|
-
this.reconnectTimer = setTimeout(() => {
|
|
22813
|
-
this.reconnectTimer = null;
|
|
22814
|
-
this.doConnect().catch(() => {
|
|
22815
|
-
});
|
|
22816
|
-
}, this.reconnectBackoff);
|
|
22817
|
-
this.reconnectBackoff = Math.min(
|
|
22818
|
-
this.reconnectBackoff * 2,
|
|
22819
|
-
MAX_RECONNECT_BACKOFF
|
|
22820
|
-
);
|
|
22821
|
-
}
|
|
22822
|
-
/**
|
|
22823
|
-
* Disconnect from the OpenClaw gateway.
|
|
22824
|
-
*/
|
|
22825
|
-
disconnect() {
|
|
22826
|
-
this.shouldReconnect = false;
|
|
22827
|
-
if (this.reconnectTimer) {
|
|
22828
|
-
clearTimeout(this.reconnectTimer);
|
|
22829
|
-
this.reconnectTimer = null;
|
|
22830
|
-
}
|
|
22831
|
-
if (this.ws) {
|
|
22832
|
-
this.ws.close();
|
|
22833
|
-
this.ws = null;
|
|
22834
|
-
this._connected = false;
|
|
22835
|
-
this._handshakeComplete = false;
|
|
22836
|
-
this.rejectAllPending(new Error("Client disconnected"));
|
|
22837
|
-
}
|
|
22838
|
-
}
|
|
22839
|
-
/**
|
|
22840
|
-
* Get the current gateway configuration.
|
|
22841
|
-
*/
|
|
22842
|
-
async configGet() {
|
|
22843
|
-
return this.call("config.get", {});
|
|
22844
|
-
}
|
|
22845
|
-
/**
|
|
22846
|
-
* Patch the gateway configuration.
|
|
22847
|
-
*/
|
|
22848
|
-
async configPatch(patch, baseHash) {
|
|
22849
|
-
return this.call("config.patch", { patch, baseHash });
|
|
22850
|
-
}
|
|
22851
|
-
/**
|
|
22852
|
-
* List all agents known to the gateway.
|
|
22853
|
-
*/
|
|
22854
|
-
async agentsList() {
|
|
22855
|
-
return this.call("agents.list", {});
|
|
22856
|
-
}
|
|
22857
|
-
/**
|
|
22858
|
-
* Send a chat message to an agent.
|
|
22859
|
-
*/
|
|
22860
|
-
async chatSend(message, agentId) {
|
|
22861
|
-
return this.call("chat.send", { message, agentId });
|
|
22862
|
-
}
|
|
22863
|
-
/**
|
|
22864
|
-
* Get chat history for an agent.
|
|
22865
|
-
*/
|
|
22866
|
-
async chatHistory(agentId) {
|
|
22867
|
-
return this.call("chat.history", { agentId });
|
|
22868
|
-
}
|
|
22869
|
-
/**
|
|
22870
|
-
* Get a file from an agent's workspace.
|
|
22871
|
-
*/
|
|
22872
|
-
async agentFilesGet(agentId, name) {
|
|
22873
|
-
return this.call("agents.files.get", { agentId, name });
|
|
22874
|
-
}
|
|
22875
|
-
/**
|
|
22876
|
-
* Set (write) a file in an agent's workspace.
|
|
22877
|
-
*/
|
|
22878
|
-
async agentFilesSet(agentId, name, content) {
|
|
22879
|
-
return this.call("agents.files.set", { agentId, name, content });
|
|
22880
|
-
}
|
|
22881
|
-
/**
|
|
22882
|
-
* Create a new agent.
|
|
22883
|
-
*/
|
|
22884
|
-
async agentsCreate(name, workspace) {
|
|
22885
|
-
return this.call("agents.create", { name, workspace });
|
|
22886
|
-
}
|
|
22887
|
-
/**
|
|
22888
|
-
* Update an existing agent.
|
|
22889
|
-
*/
|
|
22890
|
-
async agentsUpdate(agentId, patch) {
|
|
22891
|
-
return this.call("agents.update", { agentId, ...patch });
|
|
22892
|
-
}
|
|
22893
|
-
/**
|
|
22894
|
-
* Delete an agent.
|
|
22895
|
-
*/
|
|
22896
|
-
async agentsDelete(agentId) {
|
|
22897
|
-
return this.call("agents.delete", { agentId });
|
|
22898
|
-
}
|
|
22899
|
-
/**
|
|
22900
|
-
* List cron jobs.
|
|
22901
|
-
*/
|
|
22902
|
-
async cronList(params) {
|
|
22903
|
-
return this.call("cron.list", params ?? {});
|
|
22904
|
-
}
|
|
22905
|
-
/**
|
|
22906
|
-
* Add a cron job.
|
|
22907
|
-
*/
|
|
22908
|
-
async cronAdd(params) {
|
|
22909
|
-
return this.call("cron.add", params);
|
|
22910
|
-
}
|
|
22911
|
-
/**
|
|
22912
|
-
* Update a cron job.
|
|
22913
|
-
*/
|
|
22914
|
-
async cronUpdate(id, patch) {
|
|
22915
|
-
return this.call("cron.update", { id, ...patch });
|
|
22916
|
-
}
|
|
22917
|
-
/**
|
|
22918
|
-
* Remove a cron job.
|
|
22919
|
-
*/
|
|
22920
|
-
async cronRemove(id) {
|
|
22921
|
-
return this.call("cron.remove", { id });
|
|
22922
|
-
}
|
|
22923
|
-
/**
|
|
22924
|
-
* Run a cron job immediately.
|
|
22925
|
-
*/
|
|
22926
|
-
async cronRun(id, mode) {
|
|
22927
|
-
return this.call("cron.run", { id, ...mode ? { mode } : {} });
|
|
22928
|
-
}
|
|
22929
|
-
/**
|
|
22930
|
-
* Get skills status for an agent (or all agents).
|
|
22931
|
-
*/
|
|
22932
|
-
async skillsStatus(agentId) {
|
|
22933
|
-
return this.call("skills.status", agentId ? { agentId } : {});
|
|
22934
|
-
}
|
|
22935
|
-
/**
|
|
22936
|
-
* Update skills configuration.
|
|
22937
|
-
*/
|
|
22938
|
-
async skillsUpdate(params) {
|
|
22939
|
-
return this.call("skills.update", params);
|
|
22940
|
-
}
|
|
22941
|
-
/**
|
|
22942
|
-
* Get the tool catalog for an agent (or all agents).
|
|
22943
|
-
*/
|
|
22944
|
-
async toolsCatalog(agentId) {
|
|
22945
|
-
return this.call("tools.catalog", agentId ? { agentId } : {});
|
|
22946
|
-
}
|
|
22947
|
-
/**
|
|
22948
|
-
* List available models.
|
|
22949
|
-
*/
|
|
22950
|
-
async modelsList() {
|
|
22951
|
-
return this.call("models.list", {});
|
|
22952
|
-
}
|
|
22953
|
-
/**
|
|
22954
|
-
* Subscribe to OpenClaw events. The handler receives all events.
|
|
22955
|
-
*/
|
|
22956
|
-
subscribeEvents(handler) {
|
|
22957
|
-
this.eventHandlers.push(handler);
|
|
22958
|
-
return () => {
|
|
22959
|
-
this.eventHandlers = this.eventHandlers.filter((h) => h !== handler);
|
|
22960
|
-
};
|
|
22961
|
-
}
|
|
22962
|
-
// --- Internal ---
|
|
22963
|
-
async call(method, params) {
|
|
22964
|
-
if (!this.ws || !this._connected || !this._handshakeComplete) {
|
|
22965
|
-
throw new Error("Not connected to OpenClaw gateway");
|
|
22966
|
-
}
|
|
22967
|
-
const id = `rpc_${++this.callCounter}_${Date.now()}`;
|
|
22968
|
-
return new Promise((resolve, reject) => {
|
|
22969
|
-
const timer = setTimeout(() => {
|
|
22970
|
-
this.pendingCalls.delete(id);
|
|
22971
|
-
reject(new Error(`RPC call ${method} timed out after ${RPC_TIMEOUT}ms`));
|
|
22972
|
-
}, RPC_TIMEOUT);
|
|
22973
|
-
this.pendingCalls.set(id, { resolve, reject, timer });
|
|
22974
|
-
const frame = { type: "req", id, method, params };
|
|
22975
|
-
this.ws.send(JSON.stringify(frame));
|
|
22976
|
-
});
|
|
22977
|
-
}
|
|
22978
|
-
handleResponse(frame) {
|
|
22979
|
-
const pending = this.pendingCalls.get(frame.id);
|
|
22980
|
-
if (!pending) return;
|
|
22981
|
-
clearTimeout(pending.timer);
|
|
22982
|
-
this.pendingCalls.delete(frame.id);
|
|
22983
|
-
if (!frame.ok || frame.error) {
|
|
22984
|
-
pending.reject(
|
|
22985
|
-
new Error(
|
|
22986
|
-
`RPC error ${frame.error?.code ?? "unknown"}: ${frame.error?.message ?? "Unknown error"}`
|
|
22987
|
-
)
|
|
22988
|
-
);
|
|
22989
|
-
} else {
|
|
22990
|
-
pending.resolve(frame.payload);
|
|
22991
|
-
}
|
|
22992
|
-
}
|
|
22993
|
-
handleEvent(frame) {
|
|
22994
|
-
const event = {
|
|
22995
|
-
type: "event",
|
|
22996
|
-
name: frame.event,
|
|
22997
|
-
data: frame.payload ?? {}
|
|
22998
|
-
};
|
|
22999
|
-
for (const handler of this.eventHandlers) {
|
|
23000
|
-
try {
|
|
23001
|
-
handler(event);
|
|
23002
|
-
} catch (err) {
|
|
23003
|
-
console.error("[claw] Event handler error:", err);
|
|
23004
|
-
}
|
|
23005
|
-
}
|
|
23006
|
-
}
|
|
23007
|
-
rejectAllPending(error46) {
|
|
23008
|
-
for (const [id, pending] of this.pendingCalls) {
|
|
23009
|
-
clearTimeout(pending.timer);
|
|
23010
|
-
pending.reject(error46);
|
|
23011
|
-
this.pendingCalls.delete(id);
|
|
23012
|
-
}
|
|
23013
|
-
}
|
|
23014
|
-
};
|
|
23015
|
-
|
|
23016
|
-
// src/sync/discovery.ts
|
|
23017
|
-
async function agentDiscovery(rpc, apiUrl, instanceId, token) {
|
|
23018
|
-
const result = await rpc.agentsList();
|
|
23019
|
-
console.log(
|
|
23020
|
-
`[claw] agents.list returned ${result.agents?.length ?? 0} agents (default=${result.defaultId})`
|
|
23021
|
-
);
|
|
23022
|
-
const agents = (result.agents ?? []).map((agent) => ({
|
|
23023
|
-
name: agent.id,
|
|
23024
|
-
// OpenClaw agent id IS the agent key/name
|
|
23025
|
-
isDefault: agent.id === result.defaultId
|
|
23026
|
-
}));
|
|
23027
|
-
const res = await fetch(
|
|
23028
|
-
`${apiUrl}/api/claw/instances/${instanceId}/sync-agents`,
|
|
23029
|
-
{
|
|
23030
|
-
method: "POST",
|
|
23031
|
-
headers: {
|
|
23032
|
-
"Content-Type": "application/json",
|
|
23033
|
-
Authorization: `Bearer ${token}`
|
|
23034
|
-
},
|
|
23035
|
-
body: JSON.stringify({ agents })
|
|
23036
|
-
}
|
|
23037
|
-
);
|
|
23038
|
-
if (!res.ok) {
|
|
23039
|
-
const body = await res.text().catch(() => "");
|
|
23040
|
-
throw new Error(
|
|
23041
|
-
`Agent discovery sync failed: ${res.status} ${res.statusText} \u2014 ${body}`
|
|
23042
|
-
);
|
|
23043
|
-
}
|
|
23044
|
-
const syncResult = await res.json();
|
|
23045
|
-
console.log(
|
|
23046
|
-
`[claw] Agent discovery: ${syncResult.created} created, ${syncResult.deleted} deleted`
|
|
23047
|
-
);
|
|
23048
|
-
return syncResult;
|
|
23049
|
-
}
|
|
23050
|
-
|
|
23051
|
-
// src/sync/events.ts
|
|
23052
|
-
import os16 from "os";
|
|
23053
|
-
import path17 from "path";
|
|
23054
|
-
|
|
23055
|
-
// src/sync/queue.ts
|
|
23056
|
-
import fs14 from "fs/promises";
|
|
23057
|
-
import os15 from "os";
|
|
23058
|
-
import path16 from "path";
|
|
23059
|
-
var MAX_FILE_SIZE = 5e8;
|
|
23060
|
-
var PersistentEventQueue = class {
|
|
23061
|
-
filePath;
|
|
23062
|
-
constructor(customPath) {
|
|
23063
|
-
this.filePath = customPath ?? path16.join(os15.homedir(), ".workle", "pending-events.jsonl");
|
|
23064
|
-
}
|
|
23065
|
-
/**
|
|
23066
|
-
* Append a single entry to the queue file.
|
|
23067
|
-
*/
|
|
23068
|
-
async append(entry) {
|
|
23069
|
-
await this.ensureDir();
|
|
23070
|
-
const currentSize = await this.size();
|
|
23071
|
-
if (currentSize > MAX_FILE_SIZE) {
|
|
23072
|
-
await this.evictOldestHalf();
|
|
23073
|
-
}
|
|
23074
|
-
const line = JSON.stringify(entry) + "\n";
|
|
23075
|
-
await fs14.appendFile(this.filePath, line, "utf-8");
|
|
23076
|
-
}
|
|
23077
|
-
/**
|
|
23078
|
-
* Read and return all entries in chronological order.
|
|
23079
|
-
*/
|
|
23080
|
-
async replay() {
|
|
23081
|
-
let raw;
|
|
23082
|
-
try {
|
|
23083
|
-
raw = await fs14.readFile(this.filePath, "utf-8");
|
|
23084
|
-
} catch {
|
|
23085
|
-
return [];
|
|
23086
|
-
}
|
|
23087
|
-
const lines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
23088
|
-
const entries = [];
|
|
23089
|
-
for (const line of lines) {
|
|
23090
|
-
try {
|
|
23091
|
-
entries.push(JSON.parse(line));
|
|
23092
|
-
} catch {
|
|
23093
|
-
}
|
|
23094
|
-
}
|
|
23095
|
-
return entries;
|
|
23096
|
-
}
|
|
23097
|
-
/**
|
|
23098
|
-
* Remove the first `upToIndex` entries from the file (0-based exclusive).
|
|
23099
|
-
* E.g., truncate(3) removes entries at index 0, 1, 2.
|
|
23100
|
-
*/
|
|
23101
|
-
async truncate(upToIndex) {
|
|
23102
|
-
if (upToIndex <= 0) return;
|
|
23103
|
-
let raw;
|
|
23104
|
-
try {
|
|
23105
|
-
raw = await fs14.readFile(this.filePath, "utf-8");
|
|
23106
|
-
} catch {
|
|
23107
|
-
return;
|
|
23108
|
-
}
|
|
23109
|
-
const lines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
23110
|
-
const remaining = lines.slice(upToIndex);
|
|
23111
|
-
if (remaining.length === 0) {
|
|
23112
|
-
await fs14.writeFile(this.filePath, "", "utf-8");
|
|
23113
|
-
} else {
|
|
23114
|
-
await fs14.writeFile(this.filePath, remaining.join("\n") + "\n", "utf-8");
|
|
23115
|
-
}
|
|
23116
|
-
}
|
|
23117
|
-
/**
|
|
23118
|
-
* Return the current file size in bytes, or 0 if the file does not exist.
|
|
23119
|
-
*/
|
|
23120
|
-
async size() {
|
|
23121
|
-
try {
|
|
23122
|
-
const stats = await fs14.stat(this.filePath);
|
|
23123
|
-
return stats.size;
|
|
23124
|
-
} catch {
|
|
23125
|
-
return 0;
|
|
23126
|
-
}
|
|
23127
|
-
}
|
|
23128
|
-
// --- Internal ---
|
|
23129
|
-
async ensureDir() {
|
|
23130
|
-
const dir = path16.dirname(this.filePath);
|
|
23131
|
-
await fs14.mkdir(dir, { recursive: true });
|
|
23132
|
-
}
|
|
23133
|
-
/**
|
|
23134
|
-
* Evict the oldest half of entries when the file exceeds the size cap.
|
|
23135
|
-
*/
|
|
23136
|
-
async evictOldestHalf() {
|
|
23137
|
-
let raw;
|
|
23138
|
-
try {
|
|
23139
|
-
raw = await fs14.readFile(this.filePath, "utf-8");
|
|
23140
|
-
} catch {
|
|
23141
|
-
return;
|
|
23142
|
-
}
|
|
23143
|
-
const lines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
23144
|
-
const halfIndex = Math.ceil(lines.length / 2);
|
|
23145
|
-
const remaining = lines.slice(halfIndex);
|
|
23146
|
-
if (remaining.length === 0) {
|
|
23147
|
-
await fs14.writeFile(this.filePath, "", "utf-8");
|
|
23148
|
-
} else {
|
|
23149
|
-
await fs14.writeFile(this.filePath, remaining.join("\n") + "\n", "utf-8");
|
|
23150
|
-
}
|
|
23151
|
-
console.log(
|
|
23152
|
-
`[claw] Queue size exceeded ${MAX_FILE_SIZE} bytes. Evicted ${halfIndex} oldest entries.`
|
|
23153
|
-
);
|
|
23154
|
-
}
|
|
23155
|
-
};
|
|
23156
|
-
|
|
23157
|
-
// src/sync/events.ts
|
|
23158
|
-
var WORKLE_HOME7 = path17.join(os16.homedir(), ".workle");
|
|
23159
|
-
var EVENT_TYPE_MAP = {
|
|
23160
|
-
"agent.run": "agent.started",
|
|
23161
|
-
"agent.run.complete": "agent.completed",
|
|
23162
|
-
tool: "tool.executed",
|
|
23163
|
-
"tool.error": "tool.errored",
|
|
23164
|
-
turn: "agent.turn",
|
|
23165
|
-
"turn.complete": "agent.turn.complete",
|
|
23166
|
-
"session.start": "session.lifecycle",
|
|
23167
|
-
"session.end": "session.lifecycle",
|
|
23168
|
-
"session.pause": "session.lifecycle",
|
|
23169
|
-
"session.resume": "session.lifecycle",
|
|
23170
|
-
"config.changed": "config.synced",
|
|
23171
|
-
"config.reload": "config.synced"
|
|
23172
|
-
};
|
|
23173
|
-
function mapOpenClawEvent(event, instanceId) {
|
|
23174
|
-
const eventType = EVENT_TYPE_MAP[event.name];
|
|
23175
|
-
if (!eventType) return null;
|
|
23176
|
-
const agentId = typeof event.data.agentId === "string" ? event.data.agentId : void 0;
|
|
23177
|
-
const summary = typeof event.data.summary === "string" ? event.data.summary : void 0;
|
|
23178
|
-
return {
|
|
23179
|
-
eventType,
|
|
23180
|
-
subjectType: agentId ? "claw.agent" : "claw.instance",
|
|
23181
|
-
subjectId: agentId ?? instanceId,
|
|
23182
|
-
summary,
|
|
23183
|
-
actorType: "device",
|
|
23184
|
-
actorId: `claw_${instanceId}`,
|
|
23185
|
-
metadata: {
|
|
23186
|
-
...event.data,
|
|
23187
|
-
clawInstanceId: instanceId,
|
|
23188
|
-
sourceEvent: event.name,
|
|
23189
|
-
occurredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
23190
|
-
}
|
|
23191
|
-
};
|
|
23192
|
-
}
|
|
23193
|
-
var sharedQueues = /* @__PURE__ */ new Map();
|
|
23194
|
-
function getQueue(instanceId) {
|
|
23195
|
-
const queueKey = instanceId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
23196
|
-
const existing = sharedQueues.get(queueKey);
|
|
23197
|
-
if (existing) return existing;
|
|
23198
|
-
const queue = new PersistentEventQueue(
|
|
23199
|
-
path17.join(WORKLE_HOME7, `pending-events-${queueKey}.jsonl`)
|
|
23200
|
-
);
|
|
23201
|
-
sharedQueues.set(queueKey, queue);
|
|
23202
|
-
return queue;
|
|
23203
|
-
}
|
|
23204
|
-
async function deliverPayload(endpoint, token, payload) {
|
|
23205
|
-
try {
|
|
23206
|
-
const res = await fetch(endpoint, {
|
|
23207
|
-
method: "POST",
|
|
23208
|
-
headers: {
|
|
23209
|
-
"Content-Type": "application/json",
|
|
23210
|
-
Authorization: `Bearer ${token}`
|
|
23211
|
-
},
|
|
23212
|
-
body: JSON.stringify(payload)
|
|
23213
|
-
});
|
|
23214
|
-
return res.ok || res.status === 400;
|
|
23215
|
-
} catch {
|
|
23216
|
-
return false;
|
|
23217
|
-
}
|
|
23218
|
-
}
|
|
23219
|
-
function createEventForwarder(apiUrl, token, instanceId) {
|
|
23220
|
-
const endpoint = `${apiUrl}/api/claw/instances/${instanceId}/activity`;
|
|
23221
|
-
const queue = getQueue(instanceId);
|
|
23222
|
-
return (event) => {
|
|
23223
|
-
const payload = mapOpenClawEvent(event, instanceId);
|
|
23224
|
-
if (!payload) return;
|
|
23225
|
-
const entry = {
|
|
23226
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
23227
|
-
event: event.name,
|
|
23228
|
-
payload,
|
|
23229
|
-
instanceId
|
|
23230
|
-
};
|
|
23231
|
-
void (async () => {
|
|
23232
|
-
const success2 = await deliverPayload(endpoint, token, payload);
|
|
23233
|
-
if (!success2) {
|
|
23234
|
-
await queue.append(entry);
|
|
23235
|
-
}
|
|
23236
|
-
})();
|
|
23237
|
-
};
|
|
23238
|
-
}
|
|
23239
|
-
async function replayPendingEvents(apiUrl, token, instanceId) {
|
|
23240
|
-
const endpoint = `${apiUrl}/api/claw/instances/${instanceId}/activity`;
|
|
23241
|
-
const queue = getQueue(instanceId);
|
|
23242
|
-
const entries = await queue.replay();
|
|
23243
|
-
if (entries.length === 0) return;
|
|
23244
|
-
console.log(`[claw] Replaying ${entries.length} pending events...`);
|
|
23245
|
-
let deliveredCount = 0;
|
|
23246
|
-
for (const entry of entries) {
|
|
23247
|
-
const payload = entry.payload;
|
|
23248
|
-
const success2 = await deliverPayload(endpoint, token, payload);
|
|
23249
|
-
if (success2) {
|
|
23250
|
-
deliveredCount++;
|
|
23251
|
-
} else {
|
|
23252
|
-
break;
|
|
23253
|
-
}
|
|
23254
|
-
}
|
|
23255
|
-
if (deliveredCount > 0) {
|
|
23256
|
-
await queue.truncate(deliveredCount);
|
|
23257
|
-
console.log(`[claw] Replayed ${deliveredCount}/${entries.length} events`);
|
|
23258
|
-
}
|
|
23259
|
-
}
|
|
23260
|
-
|
|
23261
|
-
// src/sync/service.ts
|
|
23262
|
-
var WORKLE_HOME8 = path18.join(os17.homedir(), ".workle");
|
|
23263
22245
|
function deriveRelayUrl2(apiUrl) {
|
|
23264
22246
|
const url2 = new URL(apiUrl);
|
|
23265
22247
|
url2.protocol = url2.protocol === "https:" ? "wss:" : "ws:";
|
|
23266
|
-
url2.pathname = "/api/
|
|
22248
|
+
url2.pathname = "/api/local/relay";
|
|
23267
22249
|
if (url2.hostname !== "localhost") {
|
|
23268
22250
|
url2.hostname = `relay.${url2.hostname}`;
|
|
23269
22251
|
}
|
|
@@ -23274,9 +22256,7 @@ var SyncService = class {
|
|
|
23274
22256
|
this.options = options;
|
|
23275
22257
|
}
|
|
23276
22258
|
auth = null;
|
|
23277
|
-
rpcClient = null;
|
|
23278
22259
|
relayClient = null;
|
|
23279
|
-
unsubscribeEvents = null;
|
|
23280
22260
|
unsubscribeRelayMessages = null;
|
|
23281
22261
|
running = false;
|
|
23282
22262
|
externalMessageHandler = null;
|
|
@@ -23312,30 +22292,14 @@ var SyncService = class {
|
|
|
23312
22292
|
*/
|
|
23313
22293
|
async start() {
|
|
23314
22294
|
if (this.running) {
|
|
23315
|
-
console.log("[
|
|
22295
|
+
console.log("[local] SyncService already running");
|
|
23316
22296
|
return;
|
|
23317
22297
|
}
|
|
23318
|
-
console.log("[
|
|
22298
|
+
console.log("[local] Starting SyncService...");
|
|
23319
22299
|
this.auth = await loadAuth();
|
|
23320
22300
|
console.log(
|
|
23321
|
-
`[
|
|
22301
|
+
`[local] Authenticated as instance ${this.auth.instanceId}`
|
|
23322
22302
|
);
|
|
23323
|
-
const { instance, agents } = await this.fetchCloudData();
|
|
23324
|
-
console.log(
|
|
23325
|
-
`[claw] Fetched ${agents.length} agents for instance "${instance.name}"`
|
|
23326
|
-
);
|
|
23327
|
-
await this.writeConfigFiles(instance, agents);
|
|
23328
|
-
console.log("[claw] Config files written to disk");
|
|
23329
|
-
this.rpcClient = new OpenClawRpcClient();
|
|
23330
|
-
try {
|
|
23331
|
-
await this.rpcClient.connect();
|
|
23332
|
-
console.log("[claw] Connected to OpenClaw gateway");
|
|
23333
|
-
} catch (err) {
|
|
23334
|
-
console.warn(
|
|
23335
|
-
"[claw] Could not connect to OpenClaw gateway (is it running?):",
|
|
23336
|
-
err
|
|
23337
|
-
);
|
|
23338
|
-
}
|
|
23339
22303
|
if (!this.options.localOnly) {
|
|
23340
22304
|
const relayUrl = this.options.relayUrl ?? deriveRelayUrl2(this.auth.apiUrl);
|
|
23341
22305
|
this.relayClient = new RelayClient(
|
|
@@ -23345,51 +22309,11 @@ var SyncService = class {
|
|
|
23345
22309
|
{
|
|
23346
22310
|
onConnect: () => {
|
|
23347
22311
|
void (async () => {
|
|
23348
|
-
const MAX_RETRIES = 3;
|
|
23349
|
-
const RETRY_DELAY_MS = 2e3;
|
|
23350
|
-
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
23351
|
-
try {
|
|
23352
|
-
if (!this.auth) break;
|
|
23353
|
-
if (!this.rpcClient?.connected) {
|
|
23354
|
-
console.log(
|
|
23355
|
-
`[claw] RPC client not connected, waiting ${RETRY_DELAY_MS}ms (attempt ${attempt}/${MAX_RETRIES})`
|
|
23356
|
-
);
|
|
23357
|
-
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
23358
|
-
if (!this.rpcClient?.connected) continue;
|
|
23359
|
-
}
|
|
23360
|
-
await agentDiscovery(
|
|
23361
|
-
this.rpcClient,
|
|
23362
|
-
this.auth.apiUrl,
|
|
23363
|
-
this.auth.instanceId,
|
|
23364
|
-
this.auth.token
|
|
23365
|
-
);
|
|
23366
|
-
break;
|
|
23367
|
-
} catch (err) {
|
|
23368
|
-
console.error(
|
|
23369
|
-
`[claw] Agent discovery failed (attempt ${attempt}/${MAX_RETRIES}):`,
|
|
23370
|
-
err instanceof Error ? err.message : err
|
|
23371
|
-
);
|
|
23372
|
-
if (attempt < MAX_RETRIES) {
|
|
23373
|
-
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
23374
|
-
}
|
|
23375
|
-
}
|
|
23376
|
-
}
|
|
23377
|
-
try {
|
|
23378
|
-
if (this.auth) {
|
|
23379
|
-
await replayPendingEvents(
|
|
23380
|
-
this.auth.apiUrl,
|
|
23381
|
-
this.auth.token,
|
|
23382
|
-
this.auth.instanceId
|
|
23383
|
-
);
|
|
23384
|
-
}
|
|
23385
|
-
} catch (err) {
|
|
23386
|
-
console.error("[claw] Event replay on connect failed:", err);
|
|
23387
|
-
}
|
|
23388
22312
|
if (this.reconnectHandler) {
|
|
23389
22313
|
try {
|
|
23390
22314
|
await this.reconnectHandler();
|
|
23391
22315
|
} catch (err) {
|
|
23392
|
-
console.error("[
|
|
22316
|
+
console.error("[local] Agent config re-sync on reconnect failed:", err);
|
|
23393
22317
|
}
|
|
23394
22318
|
}
|
|
23395
22319
|
})();
|
|
@@ -23398,200 +22322,29 @@ var SyncService = class {
|
|
|
23398
22322
|
);
|
|
23399
22323
|
try {
|
|
23400
22324
|
await this.relayClient.connect();
|
|
23401
|
-
console.log("[
|
|
22325
|
+
console.log("[local] Connected to relay server");
|
|
23402
22326
|
} catch (err) {
|
|
23403
22327
|
console.warn(
|
|
23404
|
-
"[
|
|
22328
|
+
"[local] Could not connect to relay (will retry):",
|
|
23405
22329
|
err
|
|
23406
22330
|
);
|
|
23407
22331
|
}
|
|
23408
22332
|
this.unsubscribeRelayMessages = this.relayClient.onMessage((msg) => {
|
|
23409
|
-
|
|
23410
|
-
|
|
23411
|
-
|
|
23412
|
-
this.externalMessageHandler(msg);
|
|
23413
|
-
}
|
|
23414
|
-
return;
|
|
22333
|
+
console.log(`[local] Relay message received: type=${msg.type}`, JSON.stringify(msg).slice(0, 200));
|
|
22334
|
+
if (this.externalMessageHandler) {
|
|
22335
|
+
this.externalMessageHandler(msg);
|
|
23415
22336
|
}
|
|
23416
|
-
const rpcId = typeof msg.id === "string" ? msg.id : null;
|
|
23417
|
-
const method = typeof msg.method === "string" ? msg.method : null;
|
|
23418
|
-
const params = msg.params && typeof msg.params === "object" ? msg.params : {};
|
|
23419
|
-
if (!rpcId || !method) return;
|
|
23420
|
-
void (async () => {
|
|
23421
|
-
try {
|
|
23422
|
-
if (!this.rpcClient?.connected) {
|
|
23423
|
-
throw new Error("OpenClaw gateway is not connected");
|
|
23424
|
-
}
|
|
23425
|
-
let result;
|
|
23426
|
-
switch (method) {
|
|
23427
|
-
case "config.get":
|
|
23428
|
-
result = await this.rpcClient.configGet();
|
|
23429
|
-
break;
|
|
23430
|
-
case "agents.list":
|
|
23431
|
-
result = await this.rpcClient.agentsList();
|
|
23432
|
-
break;
|
|
23433
|
-
case "chat.send": {
|
|
23434
|
-
const message = typeof params.message === "string" ? params.message : "";
|
|
23435
|
-
const agentId = typeof params.agentId === "string" ? params.agentId : typeof params.agentKey === "string" ? params.agentKey : "main";
|
|
23436
|
-
if (!message.trim()) {
|
|
23437
|
-
throw new Error("Missing chat message");
|
|
23438
|
-
}
|
|
23439
|
-
result = await this.rpcClient.chatSend(message, agentId);
|
|
23440
|
-
break;
|
|
23441
|
-
}
|
|
23442
|
-
case "chat.abort":
|
|
23443
|
-
result = { ok: true };
|
|
23444
|
-
break;
|
|
23445
|
-
case "config.patch": {
|
|
23446
|
-
const patch = params.patch && typeof params.patch === "object" ? params.patch : {};
|
|
23447
|
-
const baseHash = typeof params.baseHash === "string" ? params.baseHash : "";
|
|
23448
|
-
if (!baseHash) {
|
|
23449
|
-
throw new Error("Missing config baseHash");
|
|
23450
|
-
}
|
|
23451
|
-
result = await this.rpcClient.configPatch(patch, baseHash);
|
|
23452
|
-
break;
|
|
23453
|
-
}
|
|
23454
|
-
case "config.push":
|
|
23455
|
-
result = { accepted: true };
|
|
23456
|
-
break;
|
|
23457
|
-
case "agents.files.get": {
|
|
23458
|
-
const agentId = typeof params.agentId === "string" ? params.agentId : "";
|
|
23459
|
-
const name = typeof params.name === "string" ? params.name : "";
|
|
23460
|
-
if (!agentId || !name) {
|
|
23461
|
-
throw new Error("Missing agentId or name");
|
|
23462
|
-
}
|
|
23463
|
-
result = await this.rpcClient.agentFilesGet(agentId, name);
|
|
23464
|
-
break;
|
|
23465
|
-
}
|
|
23466
|
-
case "agents.files.set": {
|
|
23467
|
-
const agentId = typeof params.agentId === "string" ? params.agentId : "";
|
|
23468
|
-
const name = typeof params.name === "string" ? params.name : "";
|
|
23469
|
-
const content = typeof params.content === "string" ? params.content : "";
|
|
23470
|
-
if (!agentId || !name) {
|
|
23471
|
-
throw new Error("Missing agentId or name");
|
|
23472
|
-
}
|
|
23473
|
-
result = await this.rpcClient.agentFilesSet(
|
|
23474
|
-
agentId,
|
|
23475
|
-
name,
|
|
23476
|
-
content
|
|
23477
|
-
);
|
|
23478
|
-
break;
|
|
23479
|
-
}
|
|
23480
|
-
case "agents.create": {
|
|
23481
|
-
const name = typeof params.name === "string" ? params.name : "";
|
|
23482
|
-
const workspace = typeof params.workspace === "string" ? params.workspace : "";
|
|
23483
|
-
if (!name || !workspace) {
|
|
23484
|
-
throw new Error("Missing name or workspace");
|
|
23485
|
-
}
|
|
23486
|
-
result = await this.rpcClient.agentsCreate(name, workspace);
|
|
23487
|
-
break;
|
|
23488
|
-
}
|
|
23489
|
-
case "agents.update": {
|
|
23490
|
-
const agentId = typeof params.agentId === "string" ? params.agentId : "";
|
|
23491
|
-
if (!agentId) {
|
|
23492
|
-
throw new Error("Missing agentId");
|
|
23493
|
-
}
|
|
23494
|
-
const { agentId: _, ...patch } = params;
|
|
23495
|
-
result = await this.rpcClient.agentsUpdate(agentId, patch);
|
|
23496
|
-
break;
|
|
23497
|
-
}
|
|
23498
|
-
case "agents.delete": {
|
|
23499
|
-
const agentId = typeof params.agentId === "string" ? params.agentId : "";
|
|
23500
|
-
if (!agentId) {
|
|
23501
|
-
throw new Error("Missing agentId");
|
|
23502
|
-
}
|
|
23503
|
-
result = await this.rpcClient.agentsDelete(agentId);
|
|
23504
|
-
break;
|
|
23505
|
-
}
|
|
23506
|
-
case "cron.list":
|
|
23507
|
-
result = await this.rpcClient.cronList(params);
|
|
23508
|
-
break;
|
|
23509
|
-
case "cron.add":
|
|
23510
|
-
result = await this.rpcClient.cronAdd(params);
|
|
23511
|
-
break;
|
|
23512
|
-
case "cron.update": {
|
|
23513
|
-
const id = typeof params.id === "string" ? params.id : "";
|
|
23514
|
-
if (!id) {
|
|
23515
|
-
throw new Error("Missing cron id");
|
|
23516
|
-
}
|
|
23517
|
-
const { id: _cronId, ...cronPatch } = params;
|
|
23518
|
-
result = await this.rpcClient.cronUpdate(id, cronPatch);
|
|
23519
|
-
break;
|
|
23520
|
-
}
|
|
23521
|
-
case "cron.remove": {
|
|
23522
|
-
const id = typeof params.id === "string" ? params.id : "";
|
|
23523
|
-
if (!id) {
|
|
23524
|
-
throw new Error("Missing cron id");
|
|
23525
|
-
}
|
|
23526
|
-
result = await this.rpcClient.cronRemove(id);
|
|
23527
|
-
break;
|
|
23528
|
-
}
|
|
23529
|
-
case "cron.run": {
|
|
23530
|
-
const id = typeof params.id === "string" ? params.id : "";
|
|
23531
|
-
if (!id) {
|
|
23532
|
-
throw new Error("Missing cron id");
|
|
23533
|
-
}
|
|
23534
|
-
const mode = typeof params.mode === "string" ? params.mode : void 0;
|
|
23535
|
-
result = await this.rpcClient.cronRun(id, mode);
|
|
23536
|
-
break;
|
|
23537
|
-
}
|
|
23538
|
-
case "skills.status": {
|
|
23539
|
-
const agentId = typeof params.agentId === "string" ? params.agentId : void 0;
|
|
23540
|
-
result = await this.rpcClient.skillsStatus(agentId);
|
|
23541
|
-
break;
|
|
23542
|
-
}
|
|
23543
|
-
case "skills.update":
|
|
23544
|
-
result = await this.rpcClient.skillsUpdate(params);
|
|
23545
|
-
break;
|
|
23546
|
-
case "tools.catalog": {
|
|
23547
|
-
const agentId = typeof params.agentId === "string" ? params.agentId : void 0;
|
|
23548
|
-
result = await this.rpcClient.toolsCatalog(agentId);
|
|
23549
|
-
break;
|
|
23550
|
-
}
|
|
23551
|
-
case "models.list":
|
|
23552
|
-
result = await this.rpcClient.modelsList();
|
|
23553
|
-
break;
|
|
23554
|
-
default:
|
|
23555
|
-
throw new Error(`Unsupported RPC method: ${method}`);
|
|
23556
|
-
}
|
|
23557
|
-
this.relayClient?.send({
|
|
23558
|
-
type: "rpc.response",
|
|
23559
|
-
id: rpcId,
|
|
23560
|
-
result
|
|
23561
|
-
});
|
|
23562
|
-
} catch (err) {
|
|
23563
|
-
this.relayClient?.send({
|
|
23564
|
-
type: "rpc.response",
|
|
23565
|
-
id: rpcId,
|
|
23566
|
-
error: {
|
|
23567
|
-
code: "rpc_error",
|
|
23568
|
-
message: err instanceof Error ? err.message : String(err)
|
|
23569
|
-
}
|
|
23570
|
-
});
|
|
23571
|
-
}
|
|
23572
|
-
})();
|
|
23573
22337
|
});
|
|
23574
22338
|
}
|
|
23575
|
-
const forwarder = createEventForwarder(
|
|
23576
|
-
this.auth.apiUrl,
|
|
23577
|
-
this.auth.token,
|
|
23578
|
-
this.auth.instanceId
|
|
23579
|
-
);
|
|
23580
|
-
this.unsubscribeEvents = this.rpcClient.subscribeEvents(forwarder);
|
|
23581
|
-
console.log("[claw] Event forwarding registered");
|
|
23582
22339
|
this.running = true;
|
|
23583
|
-
console.log("[
|
|
22340
|
+
console.log("[local] SyncService started");
|
|
23584
22341
|
}
|
|
23585
22342
|
/**
|
|
23586
22343
|
* Gracefully stop the sync service.
|
|
23587
22344
|
*/
|
|
23588
22345
|
stop() {
|
|
23589
22346
|
if (!this.running) return;
|
|
23590
|
-
console.log("[
|
|
23591
|
-
if (this.unsubscribeEvents) {
|
|
23592
|
-
this.unsubscribeEvents();
|
|
23593
|
-
this.unsubscribeEvents = null;
|
|
23594
|
-
}
|
|
22347
|
+
console.log("[local] Stopping SyncService...");
|
|
23595
22348
|
if (this.unsubscribeRelayMessages) {
|
|
23596
22349
|
this.unsubscribeRelayMessages();
|
|
23597
22350
|
this.unsubscribeRelayMessages = null;
|
|
@@ -23600,54 +22353,16 @@ var SyncService = class {
|
|
|
23600
22353
|
this.relayClient.disconnect();
|
|
23601
22354
|
this.relayClient = null;
|
|
23602
22355
|
}
|
|
23603
|
-
if (this.rpcClient) {
|
|
23604
|
-
this.rpcClient.disconnect();
|
|
23605
|
-
this.rpcClient = null;
|
|
23606
|
-
}
|
|
23607
22356
|
this.running = false;
|
|
23608
|
-
console.log("[
|
|
23609
|
-
}
|
|
23610
|
-
// --- Internal ---
|
|
23611
|
-
async fetchCloudData() {
|
|
23612
|
-
if (!this.auth) throw new Error("Not authenticated");
|
|
23613
|
-
const res = await fetch(
|
|
23614
|
-
`${this.auth.apiUrl}/api/claw/instances/${this.auth.instanceId}/sync`,
|
|
23615
|
-
{
|
|
23616
|
-
headers: {
|
|
23617
|
-
Authorization: `Bearer ${this.auth.token}`,
|
|
23618
|
-
"Content-Type": "application/json"
|
|
23619
|
-
}
|
|
23620
|
-
}
|
|
23621
|
-
);
|
|
23622
|
-
if (!res.ok) {
|
|
23623
|
-
throw new Error(
|
|
23624
|
-
`Failed to fetch cloud data: ${res.status} ${res.statusText}`
|
|
23625
|
-
);
|
|
23626
|
-
}
|
|
23627
|
-
const data = await res.json();
|
|
23628
|
-
return data;
|
|
23629
|
-
}
|
|
23630
|
-
async writeConfigFiles(instance, agents) {
|
|
23631
|
-
const gatewayConfig = generateGatewayConfig(instance, agents);
|
|
23632
|
-
const configPath = path18.join(WORKLE_HOME8, "openclaw.json");
|
|
23633
|
-
await fs15.mkdir(WORKLE_HOME8, { recursive: true });
|
|
23634
|
-
await fs15.writeFile(configPath, JSON.stringify(gatewayConfig, null, 2), "utf-8");
|
|
23635
|
-
await Promise.all(
|
|
23636
|
-
agents.map(async (agent) => {
|
|
23637
|
-
await writeSoulMd(agent.agentKey, agent.soulMd);
|
|
23638
|
-
await writeAgentsMd(agent.agentKey, agent.agentsMd);
|
|
23639
|
-
await writeIdentityMd(agent.agentKey, agent.identityMd);
|
|
23640
|
-
await writeHeartbeatMd(agent.agentKey, agent.heartbeatMd);
|
|
23641
|
-
})
|
|
23642
|
-
);
|
|
22357
|
+
console.log("[local] SyncService stopped");
|
|
23643
22358
|
}
|
|
23644
22359
|
};
|
|
23645
22360
|
|
|
23646
22361
|
// src/cli.ts
|
|
23647
|
-
var
|
|
23648
|
-
var AUTH_FILE2 =
|
|
23649
|
-
var LAUNCH_AGENTS_DIR2 =
|
|
23650
|
-
var PLIST_LABEL = "com.workle.
|
|
22362
|
+
var WORKLE_HOME4 = path10.join(os7.homedir(), ".workle");
|
|
22363
|
+
var AUTH_FILE2 = path10.join(WORKLE_HOME4, "auth.json");
|
|
22364
|
+
var LAUNCH_AGENTS_DIR2 = path10.join(os7.homedir(), "Library", "LaunchAgents");
|
|
22365
|
+
var PLIST_LABEL = "com.workle.local";
|
|
23651
22366
|
var color = {
|
|
23652
22367
|
green: (s) => `\x1B[32m${s}\x1B[0m`,
|
|
23653
22368
|
red: (s) => `\x1B[31m${s}\x1B[0m`,
|
|
@@ -23659,7 +22374,7 @@ var color = {
|
|
|
23659
22374
|
function readPkgVersion() {
|
|
23660
22375
|
try {
|
|
23661
22376
|
const pkgPath = new URL("../package.json", import.meta.url);
|
|
23662
|
-
const raw =
|
|
22377
|
+
const raw = readFileSync2(pkgPath, "utf-8");
|
|
23663
22378
|
return JSON.parse(raw).version;
|
|
23664
22379
|
} catch {
|
|
23665
22380
|
return "0.0.0";
|
|
@@ -23684,16 +22399,16 @@ function pairingErrorMessage(status, fallback) {
|
|
|
23684
22399
|
return fallback;
|
|
23685
22400
|
}
|
|
23686
22401
|
async function saveAuth(data) {
|
|
23687
|
-
await
|
|
22402
|
+
await setupLocalDirectory();
|
|
23688
22403
|
const authData = {
|
|
23689
22404
|
instanceId: data.instanceId,
|
|
23690
22405
|
token: data.token,
|
|
23691
22406
|
apiUrl: data.apiUrl
|
|
23692
22407
|
};
|
|
23693
|
-
await
|
|
22408
|
+
await fs7.writeFile(AUTH_FILE2, JSON.stringify(authData, null, 2), {
|
|
23694
22409
|
mode: 384
|
|
23695
22410
|
});
|
|
23696
|
-
await
|
|
22411
|
+
await fs7.chmod(AUTH_FILE2, 384).catch(() => {
|
|
23697
22412
|
});
|
|
23698
22413
|
console.log("");
|
|
23699
22414
|
console.log(color.green("\u2713 Paired successfully!"));
|
|
@@ -23710,7 +22425,7 @@ async function saveAuth(data) {
|
|
|
23710
22425
|
console.log(` ${color.cyan("npx @getworkle/cli doctor")} Run health checks`);
|
|
23711
22426
|
}
|
|
23712
22427
|
async function exchangeBrowserCode(code, codeVerifier, apiUrl) {
|
|
23713
|
-
const res = await fetch(`${apiUrl}/api/
|
|
22428
|
+
const res = await fetch(`${apiUrl}/api/local/pair/exchange`, {
|
|
23714
22429
|
method: "POST",
|
|
23715
22430
|
headers: { "Content-Type": "application/json" },
|
|
23716
22431
|
body: JSON.stringify({ code, codeVerifier })
|
|
@@ -23728,7 +22443,7 @@ function browserAuth(apiUrl) {
|
|
|
23728
22443
|
const codeVerifier = createPairingCodeVerifier();
|
|
23729
22444
|
const codeChallenge = createPairingCodeChallenge(codeVerifier);
|
|
23730
22445
|
let settled = false;
|
|
23731
|
-
const server =
|
|
22446
|
+
const server = http.createServer((req, res) => {
|
|
23732
22447
|
void (async () => {
|
|
23733
22448
|
const url2 = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
23734
22449
|
if (url2.pathname !== "/callback") {
|
|
@@ -23758,7 +22473,7 @@ function browserAuth(apiUrl) {
|
|
|
23758
22473
|
res.end(
|
|
23759
22474
|
[
|
|
23760
22475
|
"<!DOCTYPE html>",
|
|
23761
|
-
"<html><head><title>Workle
|
|
22476
|
+
"<html><head><title>Workle Local</title>",
|
|
23762
22477
|
"<style>body{font-family:system-ui;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0;background:#fafafa;color:#111}",
|
|
23763
22478
|
".card{text-align:center;padding:3rem;border-radius:12px;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.1)}",
|
|
23764
22479
|
"h1{font-size:1.5rem;margin:0 0 .5rem}p{color:#666;margin:0}</style></head>",
|
|
@@ -23775,7 +22490,7 @@ function browserAuth(apiUrl) {
|
|
|
23775
22490
|
res.end(
|
|
23776
22491
|
[
|
|
23777
22492
|
"<!DOCTYPE html>",
|
|
23778
|
-
"<html><head><title>Workle
|
|
22493
|
+
"<html><head><title>Workle Local</title>",
|
|
23779
22494
|
"<style>body{font-family:system-ui;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0;background:#fafafa;color:#111}",
|
|
23780
22495
|
".card{text-align:center;padding:3rem;border-radius:12px;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.1)}",
|
|
23781
22496
|
"h1{font-size:1.5rem;margin:0 0 .5rem}p{color:#666;margin:0 0 1rem}</style></head>",
|
|
@@ -23807,12 +22522,12 @@ function browserAuth(apiUrl) {
|
|
|
23807
22522
|
return;
|
|
23808
22523
|
}
|
|
23809
22524
|
const port = addr.port;
|
|
23810
|
-
const pairUrl = `${apiUrl}/
|
|
22525
|
+
const pairUrl = `${apiUrl}/local/pair?port=${port}&state=${encodeURIComponent(callbackState)}&codeChallenge=${encodeURIComponent(codeChallenge)}`;
|
|
23811
22526
|
console.log(color.dim("Opening browser to authenticate..."));
|
|
23812
22527
|
console.log(color.dim(` ${pairUrl}
|
|
23813
22528
|
`));
|
|
23814
22529
|
try {
|
|
23815
|
-
|
|
22530
|
+
execSync(`open "${pairUrl}"`, { stdio: "ignore" });
|
|
23816
22531
|
} catch {
|
|
23817
22532
|
console.log(color.yellow("Could not open browser automatically."));
|
|
23818
22533
|
console.log("Open this URL in your browser:\n");
|
|
@@ -23841,7 +22556,7 @@ function browserAuth(apiUrl) {
|
|
|
23841
22556
|
}
|
|
23842
22557
|
async function tokenAuth(setupToken, apiUrl) {
|
|
23843
22558
|
console.log(color.dim("Activating with Workle..."));
|
|
23844
|
-
const res = await fetch(`${apiUrl}/api/
|
|
22559
|
+
const res = await fetch(`${apiUrl}/api/local/activate`, {
|
|
23845
22560
|
method: "POST",
|
|
23846
22561
|
headers: { "Content-Type": "application/json" },
|
|
23847
22562
|
body: JSON.stringify({ setupToken })
|
|
@@ -23882,7 +22597,7 @@ program2.command("setup").alias("pair").description("Pair this Mac with a Workle
|
|
|
23882
22597
|
process.exit(1);
|
|
23883
22598
|
}
|
|
23884
22599
|
});
|
|
23885
|
-
program2.command("start").description("Start the Workle
|
|
22600
|
+
program2.command("start").description("Start the Workle Local sync service").option("-d, --daemon", "Run as a background daemon via launchd").action(async (opts) => {
|
|
23886
22601
|
if (opts.daemon) {
|
|
23887
22602
|
console.log(color.dim("Installing launchd service..."));
|
|
23888
22603
|
const stagedCliPath = await stageCliForDaemon(process.argv[1] ?? "");
|
|
@@ -23891,12 +22606,12 @@ program2.command("start").description("Start the Workle Claw sync service").opti
|
|
|
23891
22606
|
"start"
|
|
23892
22607
|
]);
|
|
23893
22608
|
try {
|
|
23894
|
-
|
|
22609
|
+
execSync(`launchctl load -w "${plistPath}"`, { stdio: "inherit" });
|
|
23895
22610
|
console.log(color.green("\u2713 Daemon started"));
|
|
23896
22611
|
console.log("");
|
|
23897
22612
|
console.log(` Service: ${color.dim(PLIST_LABEL)}`);
|
|
23898
22613
|
console.log(` Plist: ${color.dim(plistPath)}`);
|
|
23899
|
-
console.log(` Logs: ${color.dim(
|
|
22614
|
+
console.log(` Logs: ${color.dim(path10.join(WORKLE_HOME4, "logs/"))}`);
|
|
23900
22615
|
console.log("");
|
|
23901
22616
|
console.log(
|
|
23902
22617
|
`To stop: ${color.cyan("npx @getworkle/cli stop")}`
|
|
@@ -23908,36 +22623,19 @@ program2.command("start").description("Start the Workle Claw sync service").opti
|
|
|
23908
22623
|
}
|
|
23909
22624
|
return;
|
|
23910
22625
|
}
|
|
23911
|
-
console.log(color.bold("Workle
|
|
22626
|
+
console.log(color.bold("Workle Local"));
|
|
23912
22627
|
console.log(color.dim("Starting sync service... (Ctrl+C to stop)\n"));
|
|
23913
|
-
const processManager = new ProcessManager();
|
|
23914
22628
|
const syncService = new SyncService();
|
|
23915
22629
|
let agentService = null;
|
|
23916
|
-
let weSpawnedGateway = false;
|
|
23917
22630
|
const shutdown = () => {
|
|
23918
22631
|
console.log("\n" + color.dim("Shutting down..."));
|
|
23919
22632
|
agentService?.stop();
|
|
23920
22633
|
syncService.stop();
|
|
23921
|
-
|
|
23922
|
-
void processManager.stop().then(() => {
|
|
23923
|
-
process.exit(0);
|
|
23924
|
-
});
|
|
23925
|
-
} else {
|
|
23926
|
-
process.exit(0);
|
|
23927
|
-
}
|
|
22634
|
+
process.exit(0);
|
|
23928
22635
|
};
|
|
23929
22636
|
process.on("SIGINT", shutdown);
|
|
23930
22637
|
process.on("SIGTERM", shutdown);
|
|
23931
22638
|
try {
|
|
23932
|
-
const alreadyRunning = await processManager.isHealthy();
|
|
23933
|
-
if (alreadyRunning) {
|
|
23934
|
-
console.log(
|
|
23935
|
-
color.dim("OpenClaw gateway already running on :18789 \u2014 skipping spawn")
|
|
23936
|
-
);
|
|
23937
|
-
} else {
|
|
23938
|
-
await processManager.spawn();
|
|
23939
|
-
weSpawnedGateway = true;
|
|
23940
|
-
}
|
|
23941
22639
|
await syncService.start();
|
|
23942
22640
|
try {
|
|
23943
22641
|
agentService = await createAgentService();
|
|
@@ -23962,41 +22660,26 @@ program2.command("start").description("Start the Workle Claw sync service").opti
|
|
|
23962
22660
|
)
|
|
23963
22661
|
);
|
|
23964
22662
|
syncService.stop();
|
|
23965
|
-
if (weSpawnedGateway) {
|
|
23966
|
-
await processManager.stop();
|
|
23967
|
-
}
|
|
23968
22663
|
process.exit(1);
|
|
23969
22664
|
}
|
|
23970
22665
|
});
|
|
23971
|
-
program2.command("stop").description("Stop the Workle
|
|
23972
|
-
const plistPath =
|
|
22666
|
+
program2.command("stop").description("Stop the Workle Local daemon").action(async () => {
|
|
22667
|
+
const plistPath = path10.join(LAUNCH_AGENTS_DIR2, `${PLIST_LABEL}.plist`);
|
|
23973
22668
|
try {
|
|
23974
|
-
|
|
22669
|
+
execSync(`launchctl unload "${plistPath}"`, { stdio: "inherit" });
|
|
23975
22670
|
console.log(color.green("\u2713 Daemon stopped"));
|
|
23976
22671
|
} catch {
|
|
23977
22672
|
console.log(color.dim("No launchd service loaded."));
|
|
23978
22673
|
}
|
|
23979
|
-
const pidFile = path19.join(WORKLE_HOME9, "openclaw.pid");
|
|
23980
|
-
try {
|
|
23981
|
-
const pidStr = await fs16.readFile(pidFile, "utf-8");
|
|
23982
|
-
const pid = parseInt(pidStr.trim(), 10);
|
|
23983
|
-
if (!isNaN(pid)) {
|
|
23984
|
-
process.kill(pid, "SIGTERM");
|
|
23985
|
-
await fs16.unlink(pidFile).catch(() => {
|
|
23986
|
-
});
|
|
23987
|
-
console.log(color.dim(`Sent SIGTERM to OpenClaw (PID: ${pid})`));
|
|
23988
|
-
}
|
|
23989
|
-
} catch {
|
|
23990
|
-
}
|
|
23991
22674
|
});
|
|
23992
|
-
program2.command("doctor").description("Run health checks for the Workle
|
|
22675
|
+
program2.command("doctor").description("Run health checks for the Workle Local environment").action(async () => {
|
|
23993
22676
|
const checks = await runDoctor();
|
|
23994
22677
|
const failures = checks.filter((c) => c.status === "fail");
|
|
23995
22678
|
process.exit(failures.length > 0 ? 1 : 0);
|
|
23996
22679
|
});
|
|
23997
22680
|
program2.command("status").description("Show connection state and instance info").action(async () => {
|
|
23998
22681
|
console.log("");
|
|
23999
|
-
console.log(color.bold(" Workle
|
|
22682
|
+
console.log(color.bold(" Workle Local Status"));
|
|
24000
22683
|
console.log("");
|
|
24001
22684
|
try {
|
|
24002
22685
|
const auth = await loadAuth();
|
|
@@ -24008,24 +22691,11 @@ program2.command("status").description("Show connection state and instance info"
|
|
|
24008
22691
|
console.log("");
|
|
24009
22692
|
return;
|
|
24010
22693
|
}
|
|
22694
|
+
const plistPath = path10.join(LAUNCH_AGENTS_DIR2, `${PLIST_LABEL}.plist`);
|
|
24011
22695
|
try {
|
|
24012
|
-
|
|
24013
|
-
const timer = setTimeout(() => controller.abort(), 3e3);
|
|
24014
|
-
const res = await fetch("http://127.0.0.1:18789/", {
|
|
24015
|
-
signal: controller.signal
|
|
24016
|
-
});
|
|
24017
|
-
clearTimeout(timer);
|
|
24018
|
-
console.log(
|
|
24019
|
-
` Gateway: ${color.green("reachable")} (HTTP ${res.status})`
|
|
24020
|
-
);
|
|
24021
|
-
} catch {
|
|
24022
|
-
console.log(` Gateway: ${color.yellow("unreachable")}`);
|
|
24023
|
-
}
|
|
24024
|
-
const plistPath = path19.join(LAUNCH_AGENTS_DIR2, `${PLIST_LABEL}.plist`);
|
|
24025
|
-
try {
|
|
24026
|
-
await fs16.access(plistPath);
|
|
22696
|
+
await fs7.access(plistPath);
|
|
24027
22697
|
try {
|
|
24028
|
-
const output =
|
|
22698
|
+
const output = execSync(`launchctl list ${PLIST_LABEL} 2>&1`, {
|
|
24029
22699
|
encoding: "utf-8"
|
|
24030
22700
|
});
|
|
24031
22701
|
if (output.includes(PLIST_LABEL)) {
|
|
@@ -24041,15 +22711,15 @@ program2.command("status").description("Show connection state and instance info"
|
|
|
24041
22711
|
}
|
|
24042
22712
|
console.log("");
|
|
24043
22713
|
});
|
|
24044
|
-
program2.command("logs").description("Tail the Workle
|
|
22714
|
+
program2.command("logs").description("Tail the Workle Local log files").option("-n, --lines <count>", "Number of lines to show", "50").option("-f, --follow", "Follow log output").action((opts) => {
|
|
24045
22715
|
const lineCount = parseInt(opts.lines, 10);
|
|
24046
22716
|
if (!Number.isFinite(lineCount) || lineCount < 1) {
|
|
24047
22717
|
console.error(color.red("Error: --lines must be a positive integer."));
|
|
24048
22718
|
process.exit(1);
|
|
24049
22719
|
}
|
|
24050
22720
|
const logFiles = [
|
|
24051
|
-
|
|
24052
|
-
|
|
22721
|
+
path10.join(WORKLE_HOME4, "logs", "stdout.log"),
|
|
22722
|
+
path10.join(WORKLE_HOME4, "logs", "stderr.log")
|
|
24053
22723
|
];
|
|
24054
22724
|
const args = ["-n", String(lineCount)];
|
|
24055
22725
|
if (opts.follow) args.push("-f");
|
|
@@ -24057,7 +22727,7 @@ program2.command("logs").description("Tail the Workle Claw log files").option("-
|
|
|
24057
22727
|
const result = spawnSync("tail", args, { stdio: "inherit" });
|
|
24058
22728
|
if (result.error && !opts.follow) {
|
|
24059
22729
|
console.error(color.red(`Failed to read logs: ${result.error.message}`));
|
|
24060
|
-
console.error(`Expected log files at: ${
|
|
22730
|
+
console.error(`Expected log files at: ${WORKLE_HOME4}/logs/`);
|
|
24061
22731
|
}
|
|
24062
22732
|
});
|
|
24063
22733
|
var agentCmd = program2.command("agent").description("Manage and run Workle AI agents locally");
|
|
@@ -24213,12 +22883,12 @@ agentCmd.command("logs [agentId]").description("Tail agent run logs").option("-n
|
|
|
24213
22883
|
console.error(color.red("Error: --lines must be a positive integer."));
|
|
24214
22884
|
process.exit(1);
|
|
24215
22885
|
}
|
|
24216
|
-
const logsDir =
|
|
22886
|
+
const logsDir = path10.join(WORKLE_HOME4, "agents");
|
|
24217
22887
|
let logFile;
|
|
24218
22888
|
if (agentId) {
|
|
24219
|
-
logFile =
|
|
22889
|
+
logFile = path10.join(logsDir, agentId, "run.log");
|
|
24220
22890
|
} else {
|
|
24221
|
-
logFile =
|
|
22891
|
+
logFile = path10.join(logsDir, "*", "run.log");
|
|
24222
22892
|
}
|
|
24223
22893
|
const args = ["-n", String(lineCount)];
|
|
24224
22894
|
if (opts.follow) args.push("-f");
|