@getworkle/cli 0.2.13 → 0.3.1
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 +216 -1543
- 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
|
-
const { agentDir, prompt, sessionId, maxTurns, permissionMode, model, allowedTools, mcpServers, onMessage, abortController } = opts;
|
|
8308
|
+
const { agentDir, prompt, sessionId, maxTurns, permissionMode, model, allowedTools, autoMemoryEnabled, 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,
|
|
@@ -8314,6 +8322,7 @@ async function runAgent(opts) {
|
|
|
8314
8322
|
...allowedTools && allowedTools.length > 0 ? { allowedTools } : {},
|
|
8315
8323
|
...sessionId ? { resume: sessionId } : {},
|
|
8316
8324
|
...typeof maxTurns === "number" ? { maxTurns } : {},
|
|
8325
|
+
...typeof autoMemoryEnabled === "boolean" ? { autoMemoryEnabled } : {},
|
|
8317
8326
|
...mcpServers ? { mcpServers } : {},
|
|
8318
8327
|
...abortController ? { abortController } : {}
|
|
8319
8328
|
}
|
|
@@ -9112,10 +9121,10 @@ function mergeDefs(...defs) {
|
|
|
9112
9121
|
function cloneDef(schema) {
|
|
9113
9122
|
return mergeDefs(schema._zod.def);
|
|
9114
9123
|
}
|
|
9115
|
-
function getElementAtPath(obj,
|
|
9116
|
-
if (!
|
|
9124
|
+
function getElementAtPath(obj, path11) {
|
|
9125
|
+
if (!path11)
|
|
9117
9126
|
return obj;
|
|
9118
|
-
return
|
|
9127
|
+
return path11.reduce((acc, key) => acc?.[key], obj);
|
|
9119
9128
|
}
|
|
9120
9129
|
function promiseAllObject(promisesObj) {
|
|
9121
9130
|
const keys = Object.keys(promisesObj);
|
|
@@ -9481,11 +9490,11 @@ function aborted(x, startIndex = 0) {
|
|
|
9481
9490
|
}
|
|
9482
9491
|
return false;
|
|
9483
9492
|
}
|
|
9484
|
-
function prefixIssues(
|
|
9493
|
+
function prefixIssues(path11, issues) {
|
|
9485
9494
|
return issues.map((iss) => {
|
|
9486
9495
|
var _a2;
|
|
9487
9496
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
9488
|
-
iss.path.unshift(
|
|
9497
|
+
iss.path.unshift(path11);
|
|
9489
9498
|
return iss;
|
|
9490
9499
|
});
|
|
9491
9500
|
}
|
|
@@ -9647,7 +9656,7 @@ function formatError(error46, mapper = (issue2) => issue2.message) {
|
|
|
9647
9656
|
}
|
|
9648
9657
|
function treeifyError(error46, mapper = (issue2) => issue2.message) {
|
|
9649
9658
|
const result = { errors: [] };
|
|
9650
|
-
const processError = (error47,
|
|
9659
|
+
const processError = (error47, path11 = []) => {
|
|
9651
9660
|
var _a2, _b;
|
|
9652
9661
|
for (const issue2 of error47.issues) {
|
|
9653
9662
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -9657,7 +9666,7 @@ function treeifyError(error46, mapper = (issue2) => issue2.message) {
|
|
|
9657
9666
|
} else if (issue2.code === "invalid_element") {
|
|
9658
9667
|
processError({ issues: issue2.issues }, issue2.path);
|
|
9659
9668
|
} else {
|
|
9660
|
-
const fullpath = [...
|
|
9669
|
+
const fullpath = [...path11, ...issue2.path];
|
|
9661
9670
|
if (fullpath.length === 0) {
|
|
9662
9671
|
result.errors.push(mapper(issue2));
|
|
9663
9672
|
continue;
|
|
@@ -9689,8 +9698,8 @@ function treeifyError(error46, mapper = (issue2) => issue2.message) {
|
|
|
9689
9698
|
}
|
|
9690
9699
|
function toDotPath(_path) {
|
|
9691
9700
|
const segs = [];
|
|
9692
|
-
const
|
|
9693
|
-
for (const seg of
|
|
9701
|
+
const path11 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
9702
|
+
for (const seg of path11) {
|
|
9694
9703
|
if (typeof seg === "number")
|
|
9695
9704
|
segs.push(`[${seg}]`);
|
|
9696
9705
|
else if (typeof seg === "symbol")
|
|
@@ -21026,23 +21035,23 @@ function date4(params) {
|
|
|
21026
21035
|
config(en_default());
|
|
21027
21036
|
|
|
21028
21037
|
// src/agents/mcp-server.ts
|
|
21029
|
-
async function apiGet(apiUrl,
|
|
21030
|
-
const res = await fetch(`${apiUrl}${
|
|
21038
|
+
async function apiGet(apiUrl, path11, headers) {
|
|
21039
|
+
const res = await fetch(`${apiUrl}${path11}`, {
|
|
21031
21040
|
headers: { ...headers, "Content-Type": "application/json" }
|
|
21032
21041
|
});
|
|
21033
21042
|
if (!res.ok) {
|
|
21034
|
-
throw new Error(`API GET ${
|
|
21043
|
+
throw new Error(`API GET ${path11} failed: HTTP ${res.status}`);
|
|
21035
21044
|
}
|
|
21036
21045
|
return res.json();
|
|
21037
21046
|
}
|
|
21038
|
-
async function apiPost(apiUrl,
|
|
21039
|
-
const res = await fetch(`${apiUrl}${
|
|
21047
|
+
async function apiPost(apiUrl, path11, body, headers) {
|
|
21048
|
+
const res = await fetch(`${apiUrl}${path11}`, {
|
|
21040
21049
|
method: "POST",
|
|
21041
21050
|
headers: { ...headers, "Content-Type": "application/json" },
|
|
21042
21051
|
body: JSON.stringify(body)
|
|
21043
21052
|
});
|
|
21044
21053
|
if (!res.ok) {
|
|
21045
|
-
throw new Error(`API POST ${
|
|
21054
|
+
throw new Error(`API POST ${path11} failed: HTTP ${res.status}`);
|
|
21046
21055
|
}
|
|
21047
21056
|
return res.json();
|
|
21048
21057
|
}
|
|
@@ -21063,7 +21072,7 @@ function createWorkleMcpServer(opts) {
|
|
|
21063
21072
|
});
|
|
21064
21073
|
const data = await apiGet(
|
|
21065
21074
|
apiUrl,
|
|
21066
|
-
`/api/
|
|
21075
|
+
`/api/local/instances/${instanceId}/mcp/contacts?${params.toString()}`,
|
|
21067
21076
|
headers
|
|
21068
21077
|
);
|
|
21069
21078
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
@@ -21081,7 +21090,7 @@ function createWorkleMcpServer(opts) {
|
|
|
21081
21090
|
async (input) => {
|
|
21082
21091
|
const data = await apiPost(
|
|
21083
21092
|
apiUrl,
|
|
21084
|
-
`/api/
|
|
21093
|
+
`/api/local/instances/${instanceId}/mcp/activity`,
|
|
21085
21094
|
input,
|
|
21086
21095
|
headers
|
|
21087
21096
|
);
|
|
@@ -21102,7 +21111,7 @@ function createWorkleMcpServer(opts) {
|
|
|
21102
21111
|
});
|
|
21103
21112
|
const data = await apiGet(
|
|
21104
21113
|
apiUrl,
|
|
21105
|
-
`/api/
|
|
21114
|
+
`/api/local/instances/${instanceId}/mcp/documents?${params.toString()}`,
|
|
21106
21115
|
headers
|
|
21107
21116
|
);
|
|
21108
21117
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
@@ -21168,7 +21177,12 @@ var AgentScheduler = class {
|
|
|
21168
21177
|
// src/agents/service.ts
|
|
21169
21178
|
import { randomUUID } from "crypto";
|
|
21170
21179
|
import fs4 from "fs/promises";
|
|
21180
|
+
import path7 from "path";
|
|
21181
|
+
|
|
21182
|
+
// src/sync/relay.ts
|
|
21183
|
+
import { readFileSync } from "fs";
|
|
21171
21184
|
import path5 from "path";
|
|
21185
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
21172
21186
|
|
|
21173
21187
|
// ../../node_modules/ws/wrapper.mjs
|
|
21174
21188
|
var import_stream = __toESM(require_stream(), 1);
|
|
@@ -21182,6 +21196,39 @@ var wrapper_default = import_websocket.default;
|
|
|
21182
21196
|
var MAX_BACKOFF = 3e4;
|
|
21183
21197
|
var INITIAL_BACKOFF = 1e3;
|
|
21184
21198
|
var MAX_QUEUE_SIZE = 1e3;
|
|
21199
|
+
function readVersionFromJson(pathOrUrl) {
|
|
21200
|
+
try {
|
|
21201
|
+
const raw = readFileSync(pathOrUrl, "utf-8");
|
|
21202
|
+
const parsed = JSON.parse(raw);
|
|
21203
|
+
return typeof parsed.version === "string" ? parsed.version : void 0;
|
|
21204
|
+
} catch {
|
|
21205
|
+
return void 0;
|
|
21206
|
+
}
|
|
21207
|
+
}
|
|
21208
|
+
function readVersionFromNearestPackageJson(entryPath) {
|
|
21209
|
+
let currentDir = path5.dirname(entryPath);
|
|
21210
|
+
const rootDir = path5.parse(currentDir).root;
|
|
21211
|
+
while (true) {
|
|
21212
|
+
const version3 = readVersionFromJson(path5.join(currentDir, "package.json"));
|
|
21213
|
+
if (version3) {
|
|
21214
|
+
return version3;
|
|
21215
|
+
}
|
|
21216
|
+
if (currentDir === rootDir) {
|
|
21217
|
+
return void 0;
|
|
21218
|
+
}
|
|
21219
|
+
currentDir = path5.dirname(currentDir);
|
|
21220
|
+
}
|
|
21221
|
+
}
|
|
21222
|
+
var SYNC_VERSION = readVersionFromJson(new URL("../../package.json", import.meta.url));
|
|
21223
|
+
var AGENT_SDK_VERSION = (() => {
|
|
21224
|
+
try {
|
|
21225
|
+
return readVersionFromNearestPackageJson(
|
|
21226
|
+
fileURLToPath2(import.meta.resolve("@anthropic-ai/claude-agent-sdk"))
|
|
21227
|
+
);
|
|
21228
|
+
} catch {
|
|
21229
|
+
return void 0;
|
|
21230
|
+
}
|
|
21231
|
+
})();
|
|
21185
21232
|
var RelayClient = class {
|
|
21186
21233
|
constructor(relayUrl, instanceId, token, options) {
|
|
21187
21234
|
this.relayUrl = relayUrl;
|
|
@@ -21250,7 +21297,7 @@ var RelayClient = class {
|
|
|
21250
21297
|
doConnect() {
|
|
21251
21298
|
return new Promise((resolve, reject) => {
|
|
21252
21299
|
const url2 = `${this.relayUrl}/${this.instanceId}`;
|
|
21253
|
-
console.log(`[
|
|
21300
|
+
console.log(`[local] Connecting to relay: ${url2}`);
|
|
21254
21301
|
this.ws = new wrapper_default(url2, {
|
|
21255
21302
|
headers: {
|
|
21256
21303
|
Authorization: `Bearer ${this.token}`
|
|
@@ -21262,7 +21309,9 @@ var RelayClient = class {
|
|
|
21262
21309
|
this.backoff = INITIAL_BACKOFF;
|
|
21263
21310
|
const hello = {
|
|
21264
21311
|
type: "hello",
|
|
21265
|
-
instanceId: this.instanceId
|
|
21312
|
+
instanceId: this.instanceId,
|
|
21313
|
+
...SYNC_VERSION ? { syncVersion: SYNC_VERSION } : {},
|
|
21314
|
+
...AGENT_SDK_VERSION ? { agentSdkVersion: AGENT_SDK_VERSION } : {}
|
|
21266
21315
|
};
|
|
21267
21316
|
this.ws.send(JSON.stringify(hello));
|
|
21268
21317
|
this.flushQueue();
|
|
@@ -21270,7 +21319,7 @@ var RelayClient = class {
|
|
|
21270
21319
|
try {
|
|
21271
21320
|
this.onConnectCallback();
|
|
21272
21321
|
} catch (err) {
|
|
21273
|
-
console.error("[
|
|
21322
|
+
console.error("[local] onConnect callback error:", err);
|
|
21274
21323
|
}
|
|
21275
21324
|
}
|
|
21276
21325
|
if (!resolved) {
|
|
@@ -21286,7 +21335,7 @@ var RelayClient = class {
|
|
|
21286
21335
|
try {
|
|
21287
21336
|
handler(msg);
|
|
21288
21337
|
} catch (err) {
|
|
21289
|
-
console.error("[
|
|
21338
|
+
console.error("[local] Relay message handler error:", err);
|
|
21290
21339
|
}
|
|
21291
21340
|
}
|
|
21292
21341
|
} catch {
|
|
@@ -21296,20 +21345,20 @@ var RelayClient = class {
|
|
|
21296
21345
|
this.ws.on("close", (code, reason) => {
|
|
21297
21346
|
if (closeHandled) return;
|
|
21298
21347
|
closeHandled = true;
|
|
21299
|
-
const reasonStr = reason ? Buffer
|
|
21348
|
+
const reasonStr = typeof reason === "string" ? reason : reason instanceof Buffer ? reason.toString("utf-8") : "";
|
|
21300
21349
|
console.log(
|
|
21301
|
-
`[
|
|
21350
|
+
`[local] Relay WebSocket closed: code=${code} reason="${reasonStr}"`
|
|
21302
21351
|
);
|
|
21303
21352
|
this._connected = false;
|
|
21304
21353
|
this.cleanupWs();
|
|
21305
21354
|
if (code === 4e3) {
|
|
21306
|
-
console.log("[
|
|
21355
|
+
console.log("[local] Connection replaced \u2014 not reconnecting");
|
|
21307
21356
|
return;
|
|
21308
21357
|
}
|
|
21309
21358
|
this.scheduleReconnect();
|
|
21310
21359
|
});
|
|
21311
21360
|
this.ws.on("error", (err) => {
|
|
21312
|
-
console.error("[
|
|
21361
|
+
console.error("[local] Relay WebSocket error:", err.message);
|
|
21313
21362
|
this._connected = false;
|
|
21314
21363
|
if (!resolved) {
|
|
21315
21364
|
resolved = true;
|
|
@@ -21332,12 +21381,12 @@ var RelayClient = class {
|
|
|
21332
21381
|
clearTimeout(this.reconnectTimer);
|
|
21333
21382
|
}
|
|
21334
21383
|
console.log(
|
|
21335
|
-
`[
|
|
21384
|
+
`[local] Relay disconnected. Reconnecting in ${this.backoff}ms...`
|
|
21336
21385
|
);
|
|
21337
21386
|
this.reconnectTimer = setTimeout(() => {
|
|
21338
21387
|
this.reconnectTimer = null;
|
|
21339
21388
|
this.doConnect().catch((err) => {
|
|
21340
|
-
console.error("[
|
|
21389
|
+
console.error("[local] Relay reconnect failed:", err);
|
|
21341
21390
|
});
|
|
21342
21391
|
}, this.backoff);
|
|
21343
21392
|
const jitter = Math.random() * 0.3 * this.backoff;
|
|
@@ -21351,7 +21400,7 @@ var RelayClient = class {
|
|
|
21351
21400
|
this.ws.send(JSON.stringify(msg));
|
|
21352
21401
|
}
|
|
21353
21402
|
if (queued.length > 0) {
|
|
21354
|
-
console.log(`[
|
|
21403
|
+
console.log(`[local] Flushed ${queued.length} queued messages to relay`);
|
|
21355
21404
|
}
|
|
21356
21405
|
}
|
|
21357
21406
|
};
|
|
@@ -21359,8 +21408,8 @@ var RelayClient = class {
|
|
|
21359
21408
|
// src/agents/sessions.ts
|
|
21360
21409
|
import fs3 from "fs/promises";
|
|
21361
21410
|
import os4 from "os";
|
|
21362
|
-
import
|
|
21363
|
-
var SESSIONS_FILE =
|
|
21411
|
+
import path6 from "path";
|
|
21412
|
+
var SESSIONS_FILE = path6.join(
|
|
21364
21413
|
os4.homedir(),
|
|
21365
21414
|
".workle",
|
|
21366
21415
|
"agent-sessions.json"
|
|
@@ -21382,7 +21431,7 @@ async function saveSessionId(agentId, sessionId) {
|
|
|
21382
21431
|
} catch {
|
|
21383
21432
|
}
|
|
21384
21433
|
map2[agentId] = sessionId;
|
|
21385
|
-
await fs3.mkdir(
|
|
21434
|
+
await fs3.mkdir(path6.dirname(SESSIONS_FILE), { recursive: true });
|
|
21386
21435
|
await fs3.writeFile(SESSIONS_FILE, JSON.stringify(map2, null, 2), "utf-8");
|
|
21387
21436
|
}
|
|
21388
21437
|
|
|
@@ -21407,7 +21456,7 @@ function stringifyProgressValue(value) {
|
|
|
21407
21456
|
function deriveRelayUrl(apiUrl) {
|
|
21408
21457
|
const url2 = new URL(apiUrl);
|
|
21409
21458
|
url2.protocol = url2.protocol === "https:" ? "wss:" : "ws:";
|
|
21410
|
-
url2.pathname = "/api/
|
|
21459
|
+
url2.pathname = "/api/local/relay";
|
|
21411
21460
|
if (url2.hostname !== "localhost") {
|
|
21412
21461
|
url2.hostname = `relay.${url2.hostname}`;
|
|
21413
21462
|
}
|
|
@@ -21609,7 +21658,7 @@ var AgentService = class _AgentService {
|
|
|
21609
21658
|
instanceId: this.instanceId,
|
|
21610
21659
|
token: this.token
|
|
21611
21660
|
});
|
|
21612
|
-
const { permissionMode, model, allowedTools } = extractSdkOptions(config2);
|
|
21661
|
+
const { permissionMode, model, allowedTools, autoMemoryEnabled } = extractSdkOptions(config2);
|
|
21613
21662
|
const result = await runAgent({
|
|
21614
21663
|
agentDir,
|
|
21615
21664
|
prompt,
|
|
@@ -21618,6 +21667,7 @@ var AgentService = class _AgentService {
|
|
|
21618
21667
|
permissionMode,
|
|
21619
21668
|
model,
|
|
21620
21669
|
allowedTools,
|
|
21670
|
+
autoMemoryEnabled,
|
|
21621
21671
|
mcpServers: { workle: mcpServer },
|
|
21622
21672
|
abortController,
|
|
21623
21673
|
onMessage: (message) => {
|
|
@@ -21905,12 +21955,12 @@ var AgentService = class _AgentService {
|
|
|
21905
21955
|
if (mdFiles.length === 0) return;
|
|
21906
21956
|
const files = [];
|
|
21907
21957
|
for (const filename of mdFiles) {
|
|
21908
|
-
const content = await fs4.readFile(
|
|
21958
|
+
const content = await fs4.readFile(path7.join(memoryDir, filename), "utf-8");
|
|
21909
21959
|
files.push({ filename, content });
|
|
21910
21960
|
}
|
|
21911
21961
|
const headers = createAuthHeaders(this.token);
|
|
21912
21962
|
const res = await fetch(
|
|
21913
|
-
`${this.apiUrl}/api/
|
|
21963
|
+
`${this.apiUrl}/api/local/instances/${this.instanceId}/runtime/agents/${agentId}/push-memory`,
|
|
21914
21964
|
{
|
|
21915
21965
|
method: "POST",
|
|
21916
21966
|
headers: { ...headers, "Content-Type": "application/json" },
|
|
@@ -21983,7 +22033,7 @@ var VALID_PERMISSION_MODES = /* @__PURE__ */ new Set([
|
|
|
21983
22033
|
function extractSdkOptions(config2) {
|
|
21984
22034
|
const settings = config2?.settings;
|
|
21985
22035
|
if (!settings || typeof settings !== "object") {
|
|
21986
|
-
return { permissionMode: void 0, model: void 0, allowedTools: void 0 };
|
|
22036
|
+
return { permissionMode: void 0, model: void 0, allowedTools: void 0, autoMemoryEnabled: void 0 };
|
|
21987
22037
|
}
|
|
21988
22038
|
const s = settings;
|
|
21989
22039
|
let permissionMode;
|
|
@@ -22002,7 +22052,8 @@ function extractSdkOptions(config2) {
|
|
|
22002
22052
|
allowedTools = allow.filter((t) => typeof t === "string");
|
|
22003
22053
|
}
|
|
22004
22054
|
}
|
|
22005
|
-
|
|
22055
|
+
const autoMemoryEnabled = typeof s.autoMemoryEnabled === "boolean" ? s.autoMemoryEnabled : void 0;
|
|
22056
|
+
return { permissionMode, model, allowedTools, autoMemoryEnabled };
|
|
22006
22057
|
}
|
|
22007
22058
|
async function createAgentService() {
|
|
22008
22059
|
const auth = await loadAuth();
|
|
@@ -22013,171 +22064,18 @@ async function createAgentService() {
|
|
|
22013
22064
|
});
|
|
22014
22065
|
}
|
|
22015
22066
|
|
|
22016
|
-
// src/
|
|
22017
|
-
import { spawn } from "child_process";
|
|
22067
|
+
// src/lifecycle/doctor.ts
|
|
22018
22068
|
import fs5 from "fs/promises";
|
|
22019
|
-
import http from "http";
|
|
22020
22069
|
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");
|
|
22070
|
+
import path8 from "path";
|
|
22071
|
+
var WORKLE_HOME2 = path8.join(os5.homedir(), ".workle");
|
|
22171
22072
|
var MIN_NODE_MAJOR = 22;
|
|
22172
22073
|
async function runDoctor() {
|
|
22173
22074
|
const checks = [];
|
|
22174
22075
|
checks.push(checkNodeVersion());
|
|
22175
|
-
checks.push(
|
|
22176
|
-
checks.push(await checkClawHome());
|
|
22076
|
+
checks.push(await checkLocalHome());
|
|
22177
22077
|
checks.push(await checkAuthFile());
|
|
22178
|
-
|
|
22179
|
-
checks.push(await checkGatewayReachable());
|
|
22180
|
-
console.log("\n Workle Claw Doctor\n");
|
|
22078
|
+
console.log("\n Workle Local Doctor\n");
|
|
22181
22079
|
for (const check2 of checks) {
|
|
22182
22080
|
const icon = check2.status === "pass" ? "[OK]" : check2.status === "warn" ? "[WARN]" : "[FAIL]";
|
|
22183
22081
|
console.log(` ${icon} ${check2.name}: ${check2.message}`);
|
|
@@ -22207,52 +22105,33 @@ function checkNodeVersion() {
|
|
|
22207
22105
|
message: `${process.version} \u2014 Node >= ${MIN_NODE_MAJOR} is required`
|
|
22208
22106
|
};
|
|
22209
22107
|
}
|
|
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() {
|
|
22108
|
+
async function checkLocalHome() {
|
|
22230
22109
|
try {
|
|
22231
|
-
const stat = await
|
|
22110
|
+
const stat = await fs5.stat(WORKLE_HOME2);
|
|
22232
22111
|
if (stat.isDirectory()) {
|
|
22233
22112
|
return {
|
|
22234
|
-
name: "
|
|
22113
|
+
name: "Home directory",
|
|
22235
22114
|
status: "pass",
|
|
22236
22115
|
message: WORKLE_HOME2
|
|
22237
22116
|
};
|
|
22238
22117
|
}
|
|
22239
22118
|
return {
|
|
22240
|
-
name: "
|
|
22119
|
+
name: "Home directory",
|
|
22241
22120
|
status: "fail",
|
|
22242
22121
|
message: `${WORKLE_HOME2} exists but is not a directory`
|
|
22243
22122
|
};
|
|
22244
22123
|
} catch {
|
|
22245
22124
|
return {
|
|
22246
|
-
name: "
|
|
22125
|
+
name: "Home directory",
|
|
22247
22126
|
status: "fail",
|
|
22248
22127
|
message: `${WORKLE_HOME2} does not exist. Run 'workle setup'`
|
|
22249
22128
|
};
|
|
22250
22129
|
}
|
|
22251
22130
|
}
|
|
22252
22131
|
async function checkAuthFile() {
|
|
22253
|
-
const authPath =
|
|
22132
|
+
const authPath = path8.join(WORKLE_HOME2, "auth.json");
|
|
22254
22133
|
try {
|
|
22255
|
-
const raw = await
|
|
22134
|
+
const raw = await fs5.readFile(authPath, "utf-8");
|
|
22256
22135
|
const parsed = JSON.parse(raw);
|
|
22257
22136
|
if (parsed.instanceId && parsed.token && parsed.apiUrl) {
|
|
22258
22137
|
return {
|
|
@@ -22274,99 +22153,53 @@ async function checkAuthFile() {
|
|
|
22274
22153
|
};
|
|
22275
22154
|
}
|
|
22276
22155
|
}
|
|
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
22156
|
|
|
22324
22157
|
// src/lifecycle/install.ts
|
|
22325
|
-
import
|
|
22326
|
-
import
|
|
22327
|
-
import
|
|
22328
|
-
var WORKLE_HOME3 =
|
|
22329
|
-
var LAUNCH_AGENTS_DIR =
|
|
22330
|
-
var STAGED_CLI_PATH =
|
|
22158
|
+
import fs6 from "fs/promises";
|
|
22159
|
+
import os6 from "os";
|
|
22160
|
+
import path9 from "path";
|
|
22161
|
+
var WORKLE_HOME3 = path9.join(os6.homedir(), ".workle");
|
|
22162
|
+
var LAUNCH_AGENTS_DIR = path9.join(os6.homedir(), "Library", "LaunchAgents");
|
|
22163
|
+
var STAGED_CLI_PATH = path9.join(WORKLE_HOME3, "bin", "cli.js");
|
|
22331
22164
|
function escapeXml(value) {
|
|
22332
22165
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
22333
22166
|
}
|
|
22334
|
-
async function
|
|
22167
|
+
async function setupLocalDirectory() {
|
|
22335
22168
|
const dirs = [
|
|
22336
22169
|
WORKLE_HOME3,
|
|
22337
|
-
|
|
22338
|
-
|
|
22170
|
+
path9.join(WORKLE_HOME3, "logs"),
|
|
22171
|
+
path9.join(WORKLE_HOME3, "bin")
|
|
22339
22172
|
];
|
|
22340
22173
|
for (const dir of dirs) {
|
|
22341
|
-
await
|
|
22342
|
-
await
|
|
22174
|
+
await fs6.mkdir(dir, { recursive: true, mode: 448 });
|
|
22175
|
+
await fs6.chmod(dir, 448).catch(() => {
|
|
22343
22176
|
});
|
|
22344
22177
|
}
|
|
22345
|
-
const gitignorePath =
|
|
22178
|
+
const gitignorePath = path9.join(WORKLE_HOME3, ".gitignore");
|
|
22346
22179
|
try {
|
|
22347
|
-
await
|
|
22180
|
+
await fs6.access(gitignorePath);
|
|
22348
22181
|
} catch {
|
|
22349
|
-
await
|
|
22182
|
+
await fs6.writeFile(gitignorePath, "*\n", "utf-8");
|
|
22350
22183
|
}
|
|
22351
|
-
console.log(`[
|
|
22184
|
+
console.log(`[local] Directory structure created at ${WORKLE_HOME3}`);
|
|
22352
22185
|
}
|
|
22353
22186
|
async function stageCliForDaemon(currentCliPath) {
|
|
22354
22187
|
if (!currentCliPath) {
|
|
22355
22188
|
throw new Error("Cannot determine the current CLI path for daemon install.");
|
|
22356
22189
|
}
|
|
22357
|
-
await
|
|
22190
|
+
await setupLocalDirectory();
|
|
22358
22191
|
const isNpxCache = currentCliPath.includes("/_npx/") || currentCliPath.includes("\\_npx\\");
|
|
22359
22192
|
if (!isNpxCache) {
|
|
22360
22193
|
return currentCliPath;
|
|
22361
22194
|
}
|
|
22362
|
-
await
|
|
22363
|
-
await
|
|
22195
|
+
await fs6.copyFile(currentCliPath, STAGED_CLI_PATH);
|
|
22196
|
+
await fs6.chmod(STAGED_CLI_PATH, 448).catch(() => {
|
|
22364
22197
|
});
|
|
22365
22198
|
return STAGED_CLI_PATH;
|
|
22366
22199
|
}
|
|
22367
|
-
async function writeLaunchdPlist(serviceName = "com.workle.
|
|
22368
|
-
await
|
|
22369
|
-
const plistPath =
|
|
22200
|
+
async function writeLaunchdPlist(serviceName = "com.workle.local", executable = process.execPath, args = [STAGED_CLI_PATH, "start"]) {
|
|
22201
|
+
await fs6.mkdir(LAUNCH_AGENTS_DIR, { recursive: true });
|
|
22202
|
+
const plistPath = path9.join(LAUNCH_AGENTS_DIR, `${serviceName}.plist`);
|
|
22370
22203
|
const programArguments = [executable, ...args].map((arg) => ` <string>${escapeXml(arg)}</string>`).join("\n");
|
|
22371
22204
|
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
22372
22205
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -22390,10 +22223,10 @@ ${programArguments}
|
|
|
22390
22223
|
</dict>
|
|
22391
22224
|
|
|
22392
22225
|
<key>StandardOutPath</key>
|
|
22393
|
-
<string>${
|
|
22226
|
+
<string>${path9.join(WORKLE_HOME3, "logs", "stdout.log")}</string>
|
|
22394
22227
|
|
|
22395
22228
|
<key>StandardErrorPath</key>
|
|
22396
|
-
<string>${
|
|
22229
|
+
<string>${path9.join(WORKLE_HOME3, "logs", "stderr.log")}</string>
|
|
22397
22230
|
|
|
22398
22231
|
<key>WorkingDirectory</key>
|
|
22399
22232
|
<string>${WORKLE_HOME3}</string>
|
|
@@ -22403,867 +22236,19 @@ ${programArguments}
|
|
|
22403
22236
|
</dict>
|
|
22404
22237
|
</plist>
|
|
22405
22238
|
`;
|
|
22406
|
-
await
|
|
22407
|
-
console.log(`[
|
|
22239
|
+
await fs6.writeFile(plistPath, plist, "utf-8");
|
|
22240
|
+
console.log(`[local] Launchd plist written to ${plistPath}`);
|
|
22408
22241
|
console.log(
|
|
22409
|
-
`[
|
|
22242
|
+
`[local] To enable: launchctl load ${plistPath}`
|
|
22410
22243
|
);
|
|
22411
22244
|
return plistPath;
|
|
22412
22245
|
}
|
|
22413
22246
|
|
|
22414
22247
|
// 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
22248
|
function deriveRelayUrl2(apiUrl) {
|
|
23264
22249
|
const url2 = new URL(apiUrl);
|
|
23265
22250
|
url2.protocol = url2.protocol === "https:" ? "wss:" : "ws:";
|
|
23266
|
-
url2.pathname = "/api/
|
|
22251
|
+
url2.pathname = "/api/local/relay";
|
|
23267
22252
|
if (url2.hostname !== "localhost") {
|
|
23268
22253
|
url2.hostname = `relay.${url2.hostname}`;
|
|
23269
22254
|
}
|
|
@@ -23274,9 +22259,7 @@ var SyncService = class {
|
|
|
23274
22259
|
this.options = options;
|
|
23275
22260
|
}
|
|
23276
22261
|
auth = null;
|
|
23277
|
-
rpcClient = null;
|
|
23278
22262
|
relayClient = null;
|
|
23279
|
-
unsubscribeEvents = null;
|
|
23280
22263
|
unsubscribeRelayMessages = null;
|
|
23281
22264
|
running = false;
|
|
23282
22265
|
externalMessageHandler = null;
|
|
@@ -23312,30 +22295,14 @@ var SyncService = class {
|
|
|
23312
22295
|
*/
|
|
23313
22296
|
async start() {
|
|
23314
22297
|
if (this.running) {
|
|
23315
|
-
console.log("[
|
|
22298
|
+
console.log("[local] SyncService already running");
|
|
23316
22299
|
return;
|
|
23317
22300
|
}
|
|
23318
|
-
console.log("[
|
|
22301
|
+
console.log("[local] Starting SyncService...");
|
|
23319
22302
|
this.auth = await loadAuth();
|
|
23320
22303
|
console.log(
|
|
23321
|
-
`[
|
|
22304
|
+
`[local] Authenticated as instance ${this.auth.instanceId}`
|
|
23322
22305
|
);
|
|
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
22306
|
if (!this.options.localOnly) {
|
|
23340
22307
|
const relayUrl = this.options.relayUrl ?? deriveRelayUrl2(this.auth.apiUrl);
|
|
23341
22308
|
this.relayClient = new RelayClient(
|
|
@@ -23345,51 +22312,11 @@ var SyncService = class {
|
|
|
23345
22312
|
{
|
|
23346
22313
|
onConnect: () => {
|
|
23347
22314
|
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
22315
|
if (this.reconnectHandler) {
|
|
23389
22316
|
try {
|
|
23390
22317
|
await this.reconnectHandler();
|
|
23391
22318
|
} catch (err) {
|
|
23392
|
-
console.error("[
|
|
22319
|
+
console.error("[local] Agent config re-sync on reconnect failed:", err);
|
|
23393
22320
|
}
|
|
23394
22321
|
}
|
|
23395
22322
|
})();
|
|
@@ -23398,200 +22325,29 @@ var SyncService = class {
|
|
|
23398
22325
|
);
|
|
23399
22326
|
try {
|
|
23400
22327
|
await this.relayClient.connect();
|
|
23401
|
-
console.log("[
|
|
22328
|
+
console.log("[local] Connected to relay server");
|
|
23402
22329
|
} catch (err) {
|
|
23403
22330
|
console.warn(
|
|
23404
|
-
"[
|
|
22331
|
+
"[local] Could not connect to relay (will retry):",
|
|
23405
22332
|
err
|
|
23406
22333
|
);
|
|
23407
22334
|
}
|
|
23408
22335
|
this.unsubscribeRelayMessages = this.relayClient.onMessage((msg) => {
|
|
23409
|
-
|
|
23410
|
-
|
|
23411
|
-
|
|
23412
|
-
this.externalMessageHandler(msg);
|
|
23413
|
-
}
|
|
23414
|
-
return;
|
|
22336
|
+
console.log(`[local] Relay message received: type=${msg.type}`, JSON.stringify(msg).slice(0, 200));
|
|
22337
|
+
if (this.externalMessageHandler) {
|
|
22338
|
+
this.externalMessageHandler(msg);
|
|
23415
22339
|
}
|
|
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
22340
|
});
|
|
23574
22341
|
}
|
|
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
22342
|
this.running = true;
|
|
23583
|
-
console.log("[
|
|
22343
|
+
console.log("[local] SyncService started");
|
|
23584
22344
|
}
|
|
23585
22345
|
/**
|
|
23586
22346
|
* Gracefully stop the sync service.
|
|
23587
22347
|
*/
|
|
23588
22348
|
stop() {
|
|
23589
22349
|
if (!this.running) return;
|
|
23590
|
-
console.log("[
|
|
23591
|
-
if (this.unsubscribeEvents) {
|
|
23592
|
-
this.unsubscribeEvents();
|
|
23593
|
-
this.unsubscribeEvents = null;
|
|
23594
|
-
}
|
|
22350
|
+
console.log("[local] Stopping SyncService...");
|
|
23595
22351
|
if (this.unsubscribeRelayMessages) {
|
|
23596
22352
|
this.unsubscribeRelayMessages();
|
|
23597
22353
|
this.unsubscribeRelayMessages = null;
|
|
@@ -23600,54 +22356,16 @@ var SyncService = class {
|
|
|
23600
22356
|
this.relayClient.disconnect();
|
|
23601
22357
|
this.relayClient = null;
|
|
23602
22358
|
}
|
|
23603
|
-
if (this.rpcClient) {
|
|
23604
|
-
this.rpcClient.disconnect();
|
|
23605
|
-
this.rpcClient = null;
|
|
23606
|
-
}
|
|
23607
22359
|
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
|
-
);
|
|
22360
|
+
console.log("[local] SyncService stopped");
|
|
23643
22361
|
}
|
|
23644
22362
|
};
|
|
23645
22363
|
|
|
23646
22364
|
// src/cli.ts
|
|
23647
|
-
var
|
|
23648
|
-
var AUTH_FILE2 =
|
|
23649
|
-
var LAUNCH_AGENTS_DIR2 =
|
|
23650
|
-
var PLIST_LABEL = "com.workle.
|
|
22365
|
+
var WORKLE_HOME4 = path10.join(os7.homedir(), ".workle");
|
|
22366
|
+
var AUTH_FILE2 = path10.join(WORKLE_HOME4, "auth.json");
|
|
22367
|
+
var LAUNCH_AGENTS_DIR2 = path10.join(os7.homedir(), "Library", "LaunchAgents");
|
|
22368
|
+
var PLIST_LABEL = "com.workle.local";
|
|
23651
22369
|
var color = {
|
|
23652
22370
|
green: (s) => `\x1B[32m${s}\x1B[0m`,
|
|
23653
22371
|
red: (s) => `\x1B[31m${s}\x1B[0m`,
|
|
@@ -23659,7 +22377,7 @@ var color = {
|
|
|
23659
22377
|
function readPkgVersion() {
|
|
23660
22378
|
try {
|
|
23661
22379
|
const pkgPath = new URL("../package.json", import.meta.url);
|
|
23662
|
-
const raw =
|
|
22380
|
+
const raw = readFileSync2(pkgPath, "utf-8");
|
|
23663
22381
|
return JSON.parse(raw).version;
|
|
23664
22382
|
} catch {
|
|
23665
22383
|
return "0.0.0";
|
|
@@ -23684,16 +22402,16 @@ function pairingErrorMessage(status, fallback) {
|
|
|
23684
22402
|
return fallback;
|
|
23685
22403
|
}
|
|
23686
22404
|
async function saveAuth(data) {
|
|
23687
|
-
await
|
|
22405
|
+
await setupLocalDirectory();
|
|
23688
22406
|
const authData = {
|
|
23689
22407
|
instanceId: data.instanceId,
|
|
23690
22408
|
token: data.token,
|
|
23691
22409
|
apiUrl: data.apiUrl
|
|
23692
22410
|
};
|
|
23693
|
-
await
|
|
22411
|
+
await fs7.writeFile(AUTH_FILE2, JSON.stringify(authData, null, 2), {
|
|
23694
22412
|
mode: 384
|
|
23695
22413
|
});
|
|
23696
|
-
await
|
|
22414
|
+
await fs7.chmod(AUTH_FILE2, 384).catch(() => {
|
|
23697
22415
|
});
|
|
23698
22416
|
console.log("");
|
|
23699
22417
|
console.log(color.green("\u2713 Paired successfully!"));
|
|
@@ -23710,7 +22428,7 @@ async function saveAuth(data) {
|
|
|
23710
22428
|
console.log(` ${color.cyan("npx @getworkle/cli doctor")} Run health checks`);
|
|
23711
22429
|
}
|
|
23712
22430
|
async function exchangeBrowserCode(code, codeVerifier, apiUrl) {
|
|
23713
|
-
const res = await fetch(`${apiUrl}/api/
|
|
22431
|
+
const res = await fetch(`${apiUrl}/api/local/pair/exchange`, {
|
|
23714
22432
|
method: "POST",
|
|
23715
22433
|
headers: { "Content-Type": "application/json" },
|
|
23716
22434
|
body: JSON.stringify({ code, codeVerifier })
|
|
@@ -23728,7 +22446,7 @@ function browserAuth(apiUrl) {
|
|
|
23728
22446
|
const codeVerifier = createPairingCodeVerifier();
|
|
23729
22447
|
const codeChallenge = createPairingCodeChallenge(codeVerifier);
|
|
23730
22448
|
let settled = false;
|
|
23731
|
-
const server =
|
|
22449
|
+
const server = http.createServer((req, res) => {
|
|
23732
22450
|
void (async () => {
|
|
23733
22451
|
const url2 = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
23734
22452
|
if (url2.pathname !== "/callback") {
|
|
@@ -23758,7 +22476,7 @@ function browserAuth(apiUrl) {
|
|
|
23758
22476
|
res.end(
|
|
23759
22477
|
[
|
|
23760
22478
|
"<!DOCTYPE html>",
|
|
23761
|
-
"<html><head><title>Workle
|
|
22479
|
+
"<html><head><title>Workle Local</title>",
|
|
23762
22480
|
"<style>body{font-family:system-ui;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0;background:#fafafa;color:#111}",
|
|
23763
22481
|
".card{text-align:center;padding:3rem;border-radius:12px;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.1)}",
|
|
23764
22482
|
"h1{font-size:1.5rem;margin:0 0 .5rem}p{color:#666;margin:0}</style></head>",
|
|
@@ -23775,7 +22493,7 @@ function browserAuth(apiUrl) {
|
|
|
23775
22493
|
res.end(
|
|
23776
22494
|
[
|
|
23777
22495
|
"<!DOCTYPE html>",
|
|
23778
|
-
"<html><head><title>Workle
|
|
22496
|
+
"<html><head><title>Workle Local</title>",
|
|
23779
22497
|
"<style>body{font-family:system-ui;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0;background:#fafafa;color:#111}",
|
|
23780
22498
|
".card{text-align:center;padding:3rem;border-radius:12px;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.1)}",
|
|
23781
22499
|
"h1{font-size:1.5rem;margin:0 0 .5rem}p{color:#666;margin:0 0 1rem}</style></head>",
|
|
@@ -23807,12 +22525,12 @@ function browserAuth(apiUrl) {
|
|
|
23807
22525
|
return;
|
|
23808
22526
|
}
|
|
23809
22527
|
const port = addr.port;
|
|
23810
|
-
const pairUrl = `${apiUrl}/
|
|
22528
|
+
const pairUrl = `${apiUrl}/local/pair?port=${port}&state=${encodeURIComponent(callbackState)}&codeChallenge=${encodeURIComponent(codeChallenge)}`;
|
|
23811
22529
|
console.log(color.dim("Opening browser to authenticate..."));
|
|
23812
22530
|
console.log(color.dim(` ${pairUrl}
|
|
23813
22531
|
`));
|
|
23814
22532
|
try {
|
|
23815
|
-
|
|
22533
|
+
execSync(`open "${pairUrl}"`, { stdio: "ignore" });
|
|
23816
22534
|
} catch {
|
|
23817
22535
|
console.log(color.yellow("Could not open browser automatically."));
|
|
23818
22536
|
console.log("Open this URL in your browser:\n");
|
|
@@ -23841,7 +22559,7 @@ function browserAuth(apiUrl) {
|
|
|
23841
22559
|
}
|
|
23842
22560
|
async function tokenAuth(setupToken, apiUrl) {
|
|
23843
22561
|
console.log(color.dim("Activating with Workle..."));
|
|
23844
|
-
const res = await fetch(`${apiUrl}/api/
|
|
22562
|
+
const res = await fetch(`${apiUrl}/api/local/activate`, {
|
|
23845
22563
|
method: "POST",
|
|
23846
22564
|
headers: { "Content-Type": "application/json" },
|
|
23847
22565
|
body: JSON.stringify({ setupToken })
|
|
@@ -23882,7 +22600,7 @@ program2.command("setup").alias("pair").description("Pair this Mac with a Workle
|
|
|
23882
22600
|
process.exit(1);
|
|
23883
22601
|
}
|
|
23884
22602
|
});
|
|
23885
|
-
program2.command("start").description("Start the Workle
|
|
22603
|
+
program2.command("start").description("Start the Workle Local sync service").option("-d, --daemon", "Run as a background daemon via launchd").action(async (opts) => {
|
|
23886
22604
|
if (opts.daemon) {
|
|
23887
22605
|
console.log(color.dim("Installing launchd service..."));
|
|
23888
22606
|
const stagedCliPath = await stageCliForDaemon(process.argv[1] ?? "");
|
|
@@ -23891,12 +22609,12 @@ program2.command("start").description("Start the Workle Claw sync service").opti
|
|
|
23891
22609
|
"start"
|
|
23892
22610
|
]);
|
|
23893
22611
|
try {
|
|
23894
|
-
|
|
22612
|
+
execSync(`launchctl load -w "${plistPath}"`, { stdio: "inherit" });
|
|
23895
22613
|
console.log(color.green("\u2713 Daemon started"));
|
|
23896
22614
|
console.log("");
|
|
23897
22615
|
console.log(` Service: ${color.dim(PLIST_LABEL)}`);
|
|
23898
22616
|
console.log(` Plist: ${color.dim(plistPath)}`);
|
|
23899
|
-
console.log(` Logs: ${color.dim(
|
|
22617
|
+
console.log(` Logs: ${color.dim(path10.join(WORKLE_HOME4, "logs/"))}`);
|
|
23900
22618
|
console.log("");
|
|
23901
22619
|
console.log(
|
|
23902
22620
|
`To stop: ${color.cyan("npx @getworkle/cli stop")}`
|
|
@@ -23908,36 +22626,19 @@ program2.command("start").description("Start the Workle Claw sync service").opti
|
|
|
23908
22626
|
}
|
|
23909
22627
|
return;
|
|
23910
22628
|
}
|
|
23911
|
-
console.log(color.bold("Workle
|
|
22629
|
+
console.log(color.bold("Workle Local"));
|
|
23912
22630
|
console.log(color.dim("Starting sync service... (Ctrl+C to stop)\n"));
|
|
23913
|
-
const processManager = new ProcessManager();
|
|
23914
22631
|
const syncService = new SyncService();
|
|
23915
22632
|
let agentService = null;
|
|
23916
|
-
let weSpawnedGateway = false;
|
|
23917
22633
|
const shutdown = () => {
|
|
23918
22634
|
console.log("\n" + color.dim("Shutting down..."));
|
|
23919
22635
|
agentService?.stop();
|
|
23920
22636
|
syncService.stop();
|
|
23921
|
-
|
|
23922
|
-
void processManager.stop().then(() => {
|
|
23923
|
-
process.exit(0);
|
|
23924
|
-
});
|
|
23925
|
-
} else {
|
|
23926
|
-
process.exit(0);
|
|
23927
|
-
}
|
|
22637
|
+
process.exit(0);
|
|
23928
22638
|
};
|
|
23929
22639
|
process.on("SIGINT", shutdown);
|
|
23930
22640
|
process.on("SIGTERM", shutdown);
|
|
23931
22641
|
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
22642
|
await syncService.start();
|
|
23942
22643
|
try {
|
|
23943
22644
|
agentService = await createAgentService();
|
|
@@ -23962,41 +22663,26 @@ program2.command("start").description("Start the Workle Claw sync service").opti
|
|
|
23962
22663
|
)
|
|
23963
22664
|
);
|
|
23964
22665
|
syncService.stop();
|
|
23965
|
-
if (weSpawnedGateway) {
|
|
23966
|
-
await processManager.stop();
|
|
23967
|
-
}
|
|
23968
22666
|
process.exit(1);
|
|
23969
22667
|
}
|
|
23970
22668
|
});
|
|
23971
|
-
program2.command("stop").description("Stop the Workle
|
|
23972
|
-
const plistPath =
|
|
22669
|
+
program2.command("stop").description("Stop the Workle Local daemon").action(async () => {
|
|
22670
|
+
const plistPath = path10.join(LAUNCH_AGENTS_DIR2, `${PLIST_LABEL}.plist`);
|
|
23973
22671
|
try {
|
|
23974
|
-
|
|
22672
|
+
execSync(`launchctl unload "${plistPath}"`, { stdio: "inherit" });
|
|
23975
22673
|
console.log(color.green("\u2713 Daemon stopped"));
|
|
23976
22674
|
} catch {
|
|
23977
22675
|
console.log(color.dim("No launchd service loaded."));
|
|
23978
22676
|
}
|
|
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
22677
|
});
|
|
23992
|
-
program2.command("doctor").description("Run health checks for the Workle
|
|
22678
|
+
program2.command("doctor").description("Run health checks for the Workle Local environment").action(async () => {
|
|
23993
22679
|
const checks = await runDoctor();
|
|
23994
22680
|
const failures = checks.filter((c) => c.status === "fail");
|
|
23995
22681
|
process.exit(failures.length > 0 ? 1 : 0);
|
|
23996
22682
|
});
|
|
23997
22683
|
program2.command("status").description("Show connection state and instance info").action(async () => {
|
|
23998
22684
|
console.log("");
|
|
23999
|
-
console.log(color.bold(" Workle
|
|
22685
|
+
console.log(color.bold(" Workle Local Status"));
|
|
24000
22686
|
console.log("");
|
|
24001
22687
|
try {
|
|
24002
22688
|
const auth = await loadAuth();
|
|
@@ -24008,24 +22694,11 @@ program2.command("status").description("Show connection state and instance info"
|
|
|
24008
22694
|
console.log("");
|
|
24009
22695
|
return;
|
|
24010
22696
|
}
|
|
22697
|
+
const plistPath = path10.join(LAUNCH_AGENTS_DIR2, `${PLIST_LABEL}.plist`);
|
|
24011
22698
|
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);
|
|
22699
|
+
await fs7.access(plistPath);
|
|
24027
22700
|
try {
|
|
24028
|
-
const output =
|
|
22701
|
+
const output = execSync(`launchctl list ${PLIST_LABEL} 2>&1`, {
|
|
24029
22702
|
encoding: "utf-8"
|
|
24030
22703
|
});
|
|
24031
22704
|
if (output.includes(PLIST_LABEL)) {
|
|
@@ -24041,15 +22714,15 @@ program2.command("status").description("Show connection state and instance info"
|
|
|
24041
22714
|
}
|
|
24042
22715
|
console.log("");
|
|
24043
22716
|
});
|
|
24044
|
-
program2.command("logs").description("Tail the Workle
|
|
22717
|
+
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
22718
|
const lineCount = parseInt(opts.lines, 10);
|
|
24046
22719
|
if (!Number.isFinite(lineCount) || lineCount < 1) {
|
|
24047
22720
|
console.error(color.red("Error: --lines must be a positive integer."));
|
|
24048
22721
|
process.exit(1);
|
|
24049
22722
|
}
|
|
24050
22723
|
const logFiles = [
|
|
24051
|
-
|
|
24052
|
-
|
|
22724
|
+
path10.join(WORKLE_HOME4, "logs", "stdout.log"),
|
|
22725
|
+
path10.join(WORKLE_HOME4, "logs", "stderr.log")
|
|
24053
22726
|
];
|
|
24054
22727
|
const args = ["-n", String(lineCount)];
|
|
24055
22728
|
if (opts.follow) args.push("-f");
|
|
@@ -24057,7 +22730,7 @@ program2.command("logs").description("Tail the Workle Claw log files").option("-
|
|
|
24057
22730
|
const result = spawnSync("tail", args, { stdio: "inherit" });
|
|
24058
22731
|
if (result.error && !opts.follow) {
|
|
24059
22732
|
console.error(color.red(`Failed to read logs: ${result.error.message}`));
|
|
24060
|
-
console.error(`Expected log files at: ${
|
|
22733
|
+
console.error(`Expected log files at: ${WORKLE_HOME4}/logs/`);
|
|
24061
22734
|
}
|
|
24062
22735
|
});
|
|
24063
22736
|
var agentCmd = program2.command("agent").description("Manage and run Workle AI agents locally");
|
|
@@ -24213,12 +22886,12 @@ agentCmd.command("logs [agentId]").description("Tail agent run logs").option("-n
|
|
|
24213
22886
|
console.error(color.red("Error: --lines must be a positive integer."));
|
|
24214
22887
|
process.exit(1);
|
|
24215
22888
|
}
|
|
24216
|
-
const logsDir =
|
|
22889
|
+
const logsDir = path10.join(WORKLE_HOME4, "agents");
|
|
24217
22890
|
let logFile;
|
|
24218
22891
|
if (agentId) {
|
|
24219
|
-
logFile =
|
|
22892
|
+
logFile = path10.join(logsDir, agentId, "run.log");
|
|
24220
22893
|
} else {
|
|
24221
|
-
logFile =
|
|
22894
|
+
logFile = path10.join(logsDir, "*", "run.log");
|
|
24222
22895
|
}
|
|
24223
22896
|
const args = ["-n", String(lineCount)];
|
|
24224
22897
|
if (opts.follow) args.push("-f");
|