squad-openclaw 2026.2.2021 → 2026.2.2024
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/index.js +164 -72
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -440,9 +440,28 @@ var RelayClient = class {
|
|
|
440
440
|
}
|
|
441
441
|
}
|
|
442
442
|
if (msg.type === "req" && msg.method === "connect") {
|
|
443
|
+
const connectRequestId = typeof msg.id === "string" ? msg.id : null;
|
|
443
444
|
if (conn.connectHandshakeComplete) {
|
|
445
|
+
if (connectRequestId && conn.lastSuccessfulConnectRequestId === connectRequestId) {
|
|
446
|
+
console.log(`[relay-client] Duplicate connect id for ${userId} (${connectRequestId}) \u2014 reusing existing session`);
|
|
447
|
+
this.sendToRelay({
|
|
448
|
+
type: "relay.forward",
|
|
449
|
+
userId,
|
|
450
|
+
inner: {
|
|
451
|
+
type: "res",
|
|
452
|
+
id: connectRequestId,
|
|
453
|
+
ok: true,
|
|
454
|
+
payload: { protocol: 3 }
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
444
459
|
console.log(`[relay-client] New connect from ${userId} \u2014 creating fresh local WS for handshake`);
|
|
445
|
-
this.createUserConnection(userId
|
|
460
|
+
this.createUserConnection(userId, {
|
|
461
|
+
pendingConnect: null,
|
|
462
|
+
pendingMessages: conn.pendingMessages,
|
|
463
|
+
e2e: conn.e2e
|
|
464
|
+
});
|
|
446
465
|
conn = this.userConnections.get(userId);
|
|
447
466
|
if (!conn) return;
|
|
448
467
|
}
|
|
@@ -523,7 +542,8 @@ var RelayClient = class {
|
|
|
523
542
|
connectHandshakeComplete: false,
|
|
524
543
|
challengeNonce: null,
|
|
525
544
|
pendingConnect: carry?.pendingConnect ?? null,
|
|
526
|
-
pendingMessages: carry?.pendingMessages ?? []
|
|
545
|
+
pendingMessages: carry?.pendingMessages ?? [],
|
|
546
|
+
lastSuccessfulConnectRequestId: null
|
|
527
547
|
};
|
|
528
548
|
this.userConnections.set(userId, conn);
|
|
529
549
|
localWs.on("open", () => {
|
|
@@ -609,8 +629,9 @@ Device ID: ${this.deviceKeys.deviceId}`
|
|
|
609
629
|
}
|
|
610
630
|
}
|
|
611
631
|
}
|
|
612
|
-
if (parsed.type === "res" && parsed.id === "connect-
|
|
632
|
+
if (parsed.type === "res" && typeof parsed.id === "string" && parsed.id.startsWith("connect-") && parsed.ok) {
|
|
613
633
|
conn.connectHandshakeComplete = true;
|
|
634
|
+
conn.lastSuccessfulConnectRequestId = parsed.id;
|
|
614
635
|
if (conn.pendingMessages.length > 0) {
|
|
615
636
|
console.log(`[relay-client] Flushing ${conn.pendingMessages.length} buffered messages for ${userId}`);
|
|
616
637
|
for (const queued of conn.pendingMessages) {
|
|
@@ -731,6 +752,11 @@ function broadcastToUsers(event, payload) {
|
|
|
731
752
|
|
|
732
753
|
// src/agents.ts
|
|
733
754
|
import { execSync } from "child_process";
|
|
755
|
+
import path4 from "path";
|
|
756
|
+
function deriveAgentIdFromName(name) {
|
|
757
|
+
const normalized = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
758
|
+
return normalized || "agent";
|
|
759
|
+
}
|
|
734
760
|
function registerAgentMethods(api) {
|
|
735
761
|
const callGateway = async (ctx, method, params = {}) => {
|
|
736
762
|
const ctxRequest = ctx.request;
|
|
@@ -745,6 +771,8 @@ function registerAgentMethods(api) {
|
|
|
745
771
|
"squad.agents.add",
|
|
746
772
|
async ({ params, respond }) => {
|
|
747
773
|
const name = params?.name;
|
|
774
|
+
const agentId = params?.agentId;
|
|
775
|
+
const workspace = params?.workspace;
|
|
748
776
|
const model = params?.model;
|
|
749
777
|
if (!name || typeof name !== "string" || !name.trim()) {
|
|
750
778
|
respond(false, { error: "Missing or empty 'name' parameter" });
|
|
@@ -755,8 +783,19 @@ function registerAgentMethods(api) {
|
|
|
755
783
|
respond(false, { error: "Agent name must start with a letter/number and contain only letters, numbers, spaces, hyphens, or underscores" });
|
|
756
784
|
return;
|
|
757
785
|
}
|
|
786
|
+
const providedAgentId = typeof agentId === "string" ? agentId.trim() : "";
|
|
787
|
+
if (providedAgentId && !/^[a-z0-9][a-z0-9-]*$/.test(providedAgentId)) {
|
|
788
|
+
respond(false, { error: "Invalid agentId format" });
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
const effectiveAgentId = providedAgentId || deriveAgentIdFromName(safeName);
|
|
792
|
+
const defaultWorkspace = path4.join(
|
|
793
|
+
getOpenclawStateDir(),
|
|
794
|
+
effectiveAgentId === "main" ? "workspace" : `workspace-${effectiveAgentId}`
|
|
795
|
+
);
|
|
796
|
+
const workspacePath = typeof workspace === "string" && workspace.trim() ? workspace.trim() : defaultWorkspace;
|
|
758
797
|
try {
|
|
759
|
-
let cmd = `openclaw agents add ${JSON.stringify(safeName)} --non-interactive`;
|
|
798
|
+
let cmd = `openclaw agents add ${JSON.stringify(safeName)} --non-interactive --workspace ${JSON.stringify(workspacePath)}`;
|
|
760
799
|
if (model) {
|
|
761
800
|
cmd += ` --model ${JSON.stringify(model)}`;
|
|
762
801
|
}
|
|
@@ -910,11 +949,11 @@ function registerAgentMethods(api) {
|
|
|
910
949
|
|
|
911
950
|
// src/entities.ts
|
|
912
951
|
import { Type as T } from "@sinclair/typebox";
|
|
913
|
-
import
|
|
952
|
+
import path8 from "path";
|
|
914
953
|
import fs7 from "fs";
|
|
915
954
|
|
|
916
955
|
// src/watcher.ts
|
|
917
|
-
import
|
|
956
|
+
import path5 from "path";
|
|
918
957
|
import fs4 from "fs";
|
|
919
958
|
import chokidar from "chokidar";
|
|
920
959
|
var debounceTimers = /* @__PURE__ */ new Map();
|
|
@@ -945,29 +984,29 @@ function debouncedFs(relPath, action, fn) {
|
|
|
945
984
|
);
|
|
946
985
|
}
|
|
947
986
|
function isWorkspaceIdentity(filePath, configDir) {
|
|
948
|
-
const rel =
|
|
987
|
+
const rel = path5.relative(configDir, filePath);
|
|
949
988
|
const match = rel.match(/^(workspace(?:-([^/]+))?)\/IDENTITY\.md$/);
|
|
950
989
|
if (!match) return null;
|
|
951
990
|
const dirName = match[1];
|
|
952
991
|
const agentId = match[2] ?? "main";
|
|
953
|
-
return { agentId, workspacePath:
|
|
992
|
+
return { agentId, workspacePath: path5.join(configDir, dirName) };
|
|
954
993
|
}
|
|
955
994
|
function isWorkspaceAgentJson(filePath, configDir) {
|
|
956
|
-
const rel =
|
|
995
|
+
const rel = path5.relative(configDir, filePath);
|
|
957
996
|
const match = rel.match(/^(workspace(?:-([^/]+))?)\/agent\.json$/);
|
|
958
997
|
if (!match) return null;
|
|
959
998
|
const dirName = match[1];
|
|
960
999
|
const agentId = match[2] ?? "main";
|
|
961
|
-
return { agentId, workspacePath:
|
|
1000
|
+
return { agentId, workspacePath: path5.join(configDir, dirName) };
|
|
962
1001
|
}
|
|
963
1002
|
function isGlobalSkillDir(filePath, configDir) {
|
|
964
|
-
const rel =
|
|
1003
|
+
const rel = path5.relative(configDir, filePath);
|
|
965
1004
|
const match = rel.match(/^skills\/([^/]+)\/?$/);
|
|
966
1005
|
if (!match) return null;
|
|
967
1006
|
return { skillKey: match[1] };
|
|
968
1007
|
}
|
|
969
1008
|
function isWorkspaceSkillDir(filePath, configDir) {
|
|
970
|
-
const rel =
|
|
1009
|
+
const rel = path5.relative(configDir, filePath);
|
|
971
1010
|
const match = rel.match(
|
|
972
1011
|
/^workspace(?:-([^/]+))?\/skills\/([^/]+)\/?$/
|
|
973
1012
|
);
|
|
@@ -975,13 +1014,13 @@ function isWorkspaceSkillDir(filePath, configDir) {
|
|
|
975
1014
|
return { agentId: match[1] ?? "main", skillKey: match[2] };
|
|
976
1015
|
}
|
|
977
1016
|
function isPluginManifest(filePath, configDir) {
|
|
978
|
-
const rel =
|
|
1017
|
+
const rel = path5.relative(configDir, filePath);
|
|
979
1018
|
const match = rel.match(/^extensions\/([^/]+)\/openclaw\.plugin\.json$/);
|
|
980
1019
|
if (!match) return null;
|
|
981
1020
|
return { pluginDirName: match[1] };
|
|
982
1021
|
}
|
|
983
1022
|
function isOpenClawConfig(filePath, configDir) {
|
|
984
|
-
return
|
|
1023
|
+
return path5.relative(configDir, filePath) === "openclaw.json";
|
|
985
1024
|
}
|
|
986
1025
|
function updateAgent(agentId, workspacePath) {
|
|
987
1026
|
const now = Date.now();
|
|
@@ -989,7 +1028,7 @@ function updateAgent(agentId, workspacePath) {
|
|
|
989
1028
|
const metadata = { workspacePath };
|
|
990
1029
|
try {
|
|
991
1030
|
const content = fs4.readFileSync(
|
|
992
|
-
|
|
1031
|
+
path5.join(workspacePath, "IDENTITY.md"),
|
|
993
1032
|
"utf-8"
|
|
994
1033
|
);
|
|
995
1034
|
const parsed = parseIdentityName(content);
|
|
@@ -999,7 +1038,7 @@ function updateAgent(agentId, workspacePath) {
|
|
|
999
1038
|
if (name === agentId) {
|
|
1000
1039
|
try {
|
|
1001
1040
|
const raw = fs4.readFileSync(
|
|
1002
|
-
|
|
1041
|
+
path5.join(workspacePath, "agent.json"),
|
|
1003
1042
|
"utf-8"
|
|
1004
1043
|
);
|
|
1005
1044
|
const config = JSON.parse(raw);
|
|
@@ -1023,7 +1062,7 @@ function updateAgent(agentId, workspacePath) {
|
|
|
1023
1062
|
}
|
|
1024
1063
|
function updatePlugin(pluginDirName, configDir) {
|
|
1025
1064
|
const now = Date.now();
|
|
1026
|
-
const manifestPath =
|
|
1065
|
+
const manifestPath = path5.join(
|
|
1027
1066
|
configDir,
|
|
1028
1067
|
"extensions",
|
|
1029
1068
|
pluginDirName,
|
|
@@ -1040,7 +1079,7 @@ function updatePlugin(pluginDirName, configDir) {
|
|
|
1040
1079
|
name,
|
|
1041
1080
|
title: name,
|
|
1042
1081
|
description: manifest.description || null,
|
|
1043
|
-
metadata: { pluginId, pluginDir:
|
|
1082
|
+
metadata: { pluginId, pluginDir: path5.dirname(manifestPath) },
|
|
1044
1083
|
source: "filesystem",
|
|
1045
1084
|
source_key: manifestPath,
|
|
1046
1085
|
created_at: now,
|
|
@@ -1067,7 +1106,7 @@ function startWatcher(configDir, onFsChange) {
|
|
|
1067
1106
|
});
|
|
1068
1107
|
const emitFsChange = (action, filePath) => {
|
|
1069
1108
|
if (!onFsChange) return;
|
|
1070
|
-
const rel =
|
|
1109
|
+
const rel = path5.relative(configDir, filePath);
|
|
1071
1110
|
debouncedFs(rel, action, () => {
|
|
1072
1111
|
onFsChange({ action, path: rel });
|
|
1073
1112
|
});
|
|
@@ -1121,7 +1160,7 @@ function startWatcher(configDir, onFsChange) {
|
|
|
1121
1160
|
);
|
|
1122
1161
|
return;
|
|
1123
1162
|
}
|
|
1124
|
-
const rel =
|
|
1163
|
+
const rel = path5.relative(configDir, dirPath);
|
|
1125
1164
|
if (/^workspace(-[^/]+)?$/.test(rel)) {
|
|
1126
1165
|
debounced("agents", () => scanAgents(configDir));
|
|
1127
1166
|
return;
|
|
@@ -1129,7 +1168,7 @@ function startWatcher(configDir, onFsChange) {
|
|
|
1129
1168
|
};
|
|
1130
1169
|
const handleUnlinkDir = (dirPath) => {
|
|
1131
1170
|
emitFsChange("unlinkDir", dirPath);
|
|
1132
|
-
const rel =
|
|
1171
|
+
const rel = path5.relative(configDir, dirPath);
|
|
1133
1172
|
const wsMatch = rel.match(/^workspace(?:-([^/]+))?$/);
|
|
1134
1173
|
if (wsMatch) {
|
|
1135
1174
|
const agentId = wsMatch[1] ?? "main";
|
|
@@ -1167,14 +1206,14 @@ function startWatcher(configDir, onFsChange) {
|
|
|
1167
1206
|
|
|
1168
1207
|
// src/filesystem.ts
|
|
1169
1208
|
import fs6 from "fs";
|
|
1170
|
-
import
|
|
1209
|
+
import path7 from "path";
|
|
1171
1210
|
|
|
1172
1211
|
// src/layout.ts
|
|
1173
1212
|
import fs5 from "fs";
|
|
1174
|
-
import
|
|
1213
|
+
import path6 from "path";
|
|
1175
1214
|
function resolveMaybeRelativePath(stateDir, p) {
|
|
1176
|
-
if (
|
|
1177
|
-
return
|
|
1215
|
+
if (path6.isAbsolute(p)) return path6.resolve(p);
|
|
1216
|
+
return path6.resolve(stateDir, p);
|
|
1178
1217
|
}
|
|
1179
1218
|
function listWorkspaceFallbacks(stateDir) {
|
|
1180
1219
|
let entries;
|
|
@@ -1185,7 +1224,7 @@ function listWorkspaceFallbacks(stateDir) {
|
|
|
1185
1224
|
}
|
|
1186
1225
|
return entries.filter((entry) => entry.isDirectory() && (entry.name === "workspace" || entry.name.startsWith("workspace-"))).map((entry) => {
|
|
1187
1226
|
const agentId = entry.name === "workspace" ? "main" : entry.name.replace("workspace-", "");
|
|
1188
|
-
const workspacePath =
|
|
1227
|
+
const workspacePath = path6.join(stateDir, entry.name);
|
|
1189
1228
|
return {
|
|
1190
1229
|
agentId,
|
|
1191
1230
|
path: workspacePath,
|
|
@@ -1204,7 +1243,7 @@ function readOpenclawConfig(configPath) {
|
|
|
1204
1243
|
}
|
|
1205
1244
|
function resolveGatewayLayout() {
|
|
1206
1245
|
const stateDir = getOpenclawStateDir();
|
|
1207
|
-
const configPath =
|
|
1246
|
+
const configPath = path6.join(stateDir, "openclaw.json");
|
|
1208
1247
|
const config = readOpenclawConfig(configPath);
|
|
1209
1248
|
const workspaces = [];
|
|
1210
1249
|
if (config?.agents?.main?.workspace || config?.agents?.main?.workspacePath) {
|
|
@@ -1238,14 +1277,13 @@ function resolveGatewayLayout() {
|
|
|
1238
1277
|
}
|
|
1239
1278
|
}
|
|
1240
1279
|
const resolvedWorkspaces = Array.from(deduped.values());
|
|
1241
|
-
const
|
|
1242
|
-
const defaultFileBrowserRoot = mainWorkspace?.path ?? stateDir;
|
|
1280
|
+
const defaultFileBrowserRoot = stateDir;
|
|
1243
1281
|
return {
|
|
1244
1282
|
stateDir,
|
|
1245
1283
|
configPath,
|
|
1246
|
-
mediaDir:
|
|
1247
|
-
skillsDir:
|
|
1248
|
-
extensionsDir:
|
|
1284
|
+
mediaDir: path6.join(stateDir, "media"),
|
|
1285
|
+
skillsDir: path6.join(stateDir, "skills"),
|
|
1286
|
+
extensionsDir: path6.join(stateDir, "extensions"),
|
|
1249
1287
|
defaultFileBrowserRoot,
|
|
1250
1288
|
workspaces: resolvedWorkspaces
|
|
1251
1289
|
};
|
|
@@ -1255,16 +1293,16 @@ function resolveGatewayLayout() {
|
|
|
1255
1293
|
var HOME_DIR = process.env.HOME ?? "/root";
|
|
1256
1294
|
var OPENCLAW_DIR = getOpenclawStateDir();
|
|
1257
1295
|
var SENSITIVE_BLOCKED_DIRS = [
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1296
|
+
path7.join(OPENCLAW_DIR, "credentials"),
|
|
1297
|
+
path7.join(OPENCLAW_DIR, "devices"),
|
|
1298
|
+
path7.join(OPENCLAW_DIR, "identity")
|
|
1261
1299
|
];
|
|
1262
1300
|
var SENSITIVE_BLOCKED_FILES = [
|
|
1263
|
-
|
|
1301
|
+
path7.join(OPENCLAW_DIR, "squad-ceo-data", "relay", "squad-relay.json")
|
|
1264
1302
|
];
|
|
1265
1303
|
function isSensitivePath(resolvedPath) {
|
|
1266
1304
|
for (const blocked of SENSITIVE_BLOCKED_DIRS) {
|
|
1267
|
-
if (resolvedPath === blocked || resolvedPath.startsWith(blocked +
|
|
1305
|
+
if (resolvedPath === blocked || resolvedPath.startsWith(blocked + path7.sep)) {
|
|
1268
1306
|
return true;
|
|
1269
1307
|
}
|
|
1270
1308
|
}
|
|
@@ -1273,7 +1311,7 @@ function isSensitivePath(resolvedPath) {
|
|
|
1273
1311
|
return true;
|
|
1274
1312
|
}
|
|
1275
1313
|
}
|
|
1276
|
-
if (
|
|
1314
|
+
if (path7.dirname(resolvedPath) === OPENCLAW_DIR && resolvedPath.endsWith(".bak")) {
|
|
1277
1315
|
return true;
|
|
1278
1316
|
}
|
|
1279
1317
|
return false;
|
|
@@ -1322,20 +1360,20 @@ function redactOpenclawJson(rawContent) {
|
|
|
1322
1360
|
return JSON.stringify(config, null, 2);
|
|
1323
1361
|
}
|
|
1324
1362
|
function isOpenclawJson(resolvedPath) {
|
|
1325
|
-
return
|
|
1363
|
+
return path7.basename(resolvedPath) === OPENCLAW_JSON_FILENAME && resolvedPath.startsWith(OPENCLAW_DIR);
|
|
1326
1364
|
}
|
|
1327
1365
|
function expandHome(p) {
|
|
1328
1366
|
if (p.startsWith("~/") || p === "~") {
|
|
1329
|
-
return
|
|
1367
|
+
return path7.join(HOME_DIR, p.slice(1));
|
|
1330
1368
|
}
|
|
1331
1369
|
return p;
|
|
1332
1370
|
}
|
|
1333
1371
|
function validatePath(p, allowedRoots) {
|
|
1334
|
-
const resolved =
|
|
1372
|
+
const resolved = path7.resolve(expandHome(p));
|
|
1335
1373
|
if (!allowedRoots || allowedRoots.length === 0) return resolved;
|
|
1336
1374
|
const allowed = allowedRoots.some((root) => {
|
|
1337
|
-
const resolvedRoot =
|
|
1338
|
-
return resolved === resolvedRoot || resolved.startsWith(resolvedRoot +
|
|
1375
|
+
const resolvedRoot = path7.resolve(expandHome(root));
|
|
1376
|
+
return resolved === resolvedRoot || resolved.startsWith(resolvedRoot + path7.sep);
|
|
1339
1377
|
});
|
|
1340
1378
|
if (!allowed) {
|
|
1341
1379
|
throw new Error(`Path "${p}" is outside allowed roots`);
|
|
@@ -1376,7 +1414,7 @@ function listDir(dirPath, opts) {
|
|
|
1376
1414
|
const results = [];
|
|
1377
1415
|
for (const dirent of dirents) {
|
|
1378
1416
|
if (!opts.includeHidden && dirent.name.startsWith(".")) continue;
|
|
1379
|
-
const entryPath =
|
|
1417
|
+
const entryPath = path7.join(dirPath, dirent.name);
|
|
1380
1418
|
let type = "other";
|
|
1381
1419
|
if (dirent.isFile()) type = "file";
|
|
1382
1420
|
else if (dirent.isDirectory()) type = "directory";
|
|
@@ -1487,7 +1525,7 @@ function registerFilesystemTools(api) {
|
|
|
1487
1525
|
const encoding = params.encoding ?? "utf-8";
|
|
1488
1526
|
const mkdir = params.mkdir !== false;
|
|
1489
1527
|
if (mkdir) {
|
|
1490
|
-
fs6.mkdirSync(
|
|
1528
|
+
fs6.mkdirSync(path7.dirname(filePath), { recursive: true });
|
|
1491
1529
|
}
|
|
1492
1530
|
fs6.writeFileSync(filePath, content, encoding);
|
|
1493
1531
|
const stat = fs6.statSync(filePath);
|
|
@@ -1687,10 +1725,10 @@ function scanAgents(configDir) {
|
|
|
1687
1725
|
);
|
|
1688
1726
|
for (const dir of workspaceDirs) {
|
|
1689
1727
|
const agentId = dir.name === "workspace" ? "main" : dir.name.replace("workspace-", "");
|
|
1690
|
-
const workspacePath =
|
|
1728
|
+
const workspacePath = path8.join(configDir, dir.name);
|
|
1691
1729
|
let name = agentId;
|
|
1692
1730
|
const metadata = { workspacePath };
|
|
1693
|
-
const identityPath =
|
|
1731
|
+
const identityPath = path8.join(workspacePath, "IDENTITY.md");
|
|
1694
1732
|
try {
|
|
1695
1733
|
const content = fs7.readFileSync(identityPath, "utf-8");
|
|
1696
1734
|
const parsed = parseIdentityName(content);
|
|
@@ -1698,7 +1736,7 @@ function scanAgents(configDir) {
|
|
|
1698
1736
|
} catch {
|
|
1699
1737
|
}
|
|
1700
1738
|
if (name === agentId) {
|
|
1701
|
-
const agentJsonPath =
|
|
1739
|
+
const agentJsonPath = path8.join(workspacePath, "agent.json");
|
|
1702
1740
|
try {
|
|
1703
1741
|
const raw = fs7.readFileSync(agentJsonPath, "utf-8");
|
|
1704
1742
|
const config = JSON.parse(raw);
|
|
@@ -1725,7 +1763,7 @@ function scanAgents(configDir) {
|
|
|
1725
1763
|
}
|
|
1726
1764
|
function scanSkills(configDir) {
|
|
1727
1765
|
const now = Date.now();
|
|
1728
|
-
const globalSkillsDir =
|
|
1766
|
+
const globalSkillsDir = path8.join(configDir, "skills");
|
|
1729
1767
|
scanSkillsDir(globalSkillsDir, "global", now);
|
|
1730
1768
|
let entries;
|
|
1731
1769
|
try {
|
|
@@ -1738,7 +1776,7 @@ function scanSkills(configDir) {
|
|
|
1738
1776
|
continue;
|
|
1739
1777
|
}
|
|
1740
1778
|
const agentId = dir.name === "workspace" ? "main" : dir.name.replace("workspace-", "");
|
|
1741
|
-
const agentSkillsDir =
|
|
1779
|
+
const agentSkillsDir = path8.join(configDir, dir.name, "skills");
|
|
1742
1780
|
scanSkillsDir(agentSkillsDir, agentId, now);
|
|
1743
1781
|
}
|
|
1744
1782
|
}
|
|
@@ -1752,12 +1790,12 @@ function scanSkillsDir(skillsDir, scope, now) {
|
|
|
1752
1790
|
for (const entry of entries) {
|
|
1753
1791
|
if (!entry.isDirectory()) continue;
|
|
1754
1792
|
const skillKey = entry.name;
|
|
1755
|
-
const skillPath =
|
|
1793
|
+
const skillPath = path8.join(skillsDir, skillKey);
|
|
1756
1794
|
let name = skillKey;
|
|
1757
1795
|
for (const manifestName of ["manifest.json", "package.json"]) {
|
|
1758
1796
|
try {
|
|
1759
1797
|
const raw = fs7.readFileSync(
|
|
1760
|
-
|
|
1798
|
+
path8.join(skillPath, manifestName),
|
|
1761
1799
|
"utf-8"
|
|
1762
1800
|
);
|
|
1763
1801
|
const manifest = JSON.parse(raw);
|
|
@@ -1784,7 +1822,7 @@ function scanSkillsDir(skillsDir, scope, now) {
|
|
|
1784
1822
|
}
|
|
1785
1823
|
function scanPlugins2(configDir) {
|
|
1786
1824
|
const now = Date.now();
|
|
1787
|
-
const extensionsDir =
|
|
1825
|
+
const extensionsDir = path8.join(configDir, "extensions");
|
|
1788
1826
|
let entries;
|
|
1789
1827
|
try {
|
|
1790
1828
|
entries = fs7.readdirSync(extensionsDir, { withFileTypes: true });
|
|
@@ -1793,8 +1831,8 @@ function scanPlugins2(configDir) {
|
|
|
1793
1831
|
}
|
|
1794
1832
|
for (const dir of entries) {
|
|
1795
1833
|
if (!dir.isDirectory()) continue;
|
|
1796
|
-
const pluginDir =
|
|
1797
|
-
const manifestPath =
|
|
1834
|
+
const pluginDir = path8.join(extensionsDir, dir.name);
|
|
1835
|
+
const manifestPath = path8.join(pluginDir, "openclaw.plugin.json");
|
|
1798
1836
|
try {
|
|
1799
1837
|
const raw = fs7.readFileSync(manifestPath, "utf-8");
|
|
1800
1838
|
const manifest = JSON.parse(raw);
|
|
@@ -1820,7 +1858,7 @@ function scanTools(configDir) {
|
|
|
1820
1858
|
const now = Date.now();
|
|
1821
1859
|
try {
|
|
1822
1860
|
const raw = fs7.readFileSync(
|
|
1823
|
-
|
|
1861
|
+
path8.join(configDir, "openclaw.json"),
|
|
1824
1862
|
"utf-8"
|
|
1825
1863
|
);
|
|
1826
1864
|
const config = JSON.parse(raw);
|
|
@@ -1871,12 +1909,12 @@ var MIME_MAP = {
|
|
|
1871
1909
|
".gz": "application/gzip"
|
|
1872
1910
|
};
|
|
1873
1911
|
function getMimeType(filename) {
|
|
1874
|
-
const ext =
|
|
1912
|
+
const ext = path8.extname(filename).toLowerCase();
|
|
1875
1913
|
return MIME_MAP[ext] ?? "application/octet-stream";
|
|
1876
1914
|
}
|
|
1877
1915
|
function scanMedia(configDir) {
|
|
1878
1916
|
const now = Date.now();
|
|
1879
|
-
const mediaDir =
|
|
1917
|
+
const mediaDir = path8.join(configDir, "media");
|
|
1880
1918
|
scanMediaDir(mediaDir, now);
|
|
1881
1919
|
}
|
|
1882
1920
|
function scanMediaDir(dirPath, now) {
|
|
@@ -1888,7 +1926,7 @@ function scanMediaDir(dirPath, now) {
|
|
|
1888
1926
|
}
|
|
1889
1927
|
for (const entry of entries) {
|
|
1890
1928
|
if (entry.name.startsWith(".")) continue;
|
|
1891
|
-
const entryPath =
|
|
1929
|
+
const entryPath = path8.join(dirPath, entry.name);
|
|
1892
1930
|
if (isSensitivePath(entryPath)) continue;
|
|
1893
1931
|
if (entry.isDirectory()) {
|
|
1894
1932
|
registrySet({
|
|
@@ -2026,18 +2064,18 @@ function registerEntityTools(api, onFsChange) {
|
|
|
2026
2064
|
|
|
2027
2065
|
// src/sql.ts
|
|
2028
2066
|
import { execFile } from "child_process";
|
|
2029
|
-
import
|
|
2067
|
+
import path9 from "path";
|
|
2030
2068
|
import fs8 from "fs";
|
|
2031
2069
|
import { Type as T2 } from "@sinclair/typebox";
|
|
2032
2070
|
var HOME_DIR2 = process.env.HOME ?? "/root";
|
|
2033
|
-
var ALLOWED_DATA_DIR =
|
|
2071
|
+
var ALLOWED_DATA_DIR = path9.join(getOpenclawStateDir(), "squad-ceo-data");
|
|
2034
2072
|
function validateDbPath(dbPath) {
|
|
2035
2073
|
let expanded = dbPath;
|
|
2036
2074
|
if (expanded.startsWith("~/") || expanded === "~") {
|
|
2037
|
-
expanded =
|
|
2075
|
+
expanded = path9.join(HOME_DIR2, expanded.slice(1));
|
|
2038
2076
|
}
|
|
2039
|
-
const resolved =
|
|
2040
|
-
if (resolved !== ALLOWED_DATA_DIR && !resolved.startsWith(ALLOWED_DATA_DIR +
|
|
2077
|
+
const resolved = path9.resolve(expanded);
|
|
2078
|
+
if (resolved !== ALLOWED_DATA_DIR && !resolved.startsWith(ALLOWED_DATA_DIR + path9.sep)) {
|
|
2041
2079
|
throw new Error(
|
|
2042
2080
|
`Access denied: database path must be within ~/.openclaw/squad-ceo-data/`
|
|
2043
2081
|
);
|
|
@@ -2111,10 +2149,10 @@ function registerSqlTools(api) {
|
|
|
2111
2149
|
// src/version.ts
|
|
2112
2150
|
import { execSync as execSync2 } from "child_process";
|
|
2113
2151
|
import fs9 from "fs";
|
|
2114
|
-
import
|
|
2152
|
+
import path10 from "path";
|
|
2115
2153
|
import { fileURLToPath } from "url";
|
|
2116
2154
|
var PACKAGE_NAME = "squad-openclaw";
|
|
2117
|
-
var CONFIG_PATH =
|
|
2155
|
+
var CONFIG_PATH = path10.join(getOpenclawStateDir(), "openclaw.json");
|
|
2118
2156
|
var updateInProgress = false;
|
|
2119
2157
|
var VERIFY_TIMEOUT_MS = 2e4;
|
|
2120
2158
|
var VERIFY_INTERVAL_MS = 500;
|
|
@@ -2161,7 +2199,7 @@ function reconcileInstallMetadata(verification) {
|
|
|
2161
2199
|
}
|
|
2162
2200
|
function getCurrentVersion() {
|
|
2163
2201
|
const thisFile = fileURLToPath(import.meta.url);
|
|
2164
|
-
const pkgPath =
|
|
2202
|
+
const pkgPath = path10.resolve(path10.dirname(thisFile), "..", "package.json");
|
|
2165
2203
|
try {
|
|
2166
2204
|
const pkg = JSON.parse(fs9.readFileSync(pkgPath, "utf-8"));
|
|
2167
2205
|
return pkg.version ?? "0.0.0";
|
|
@@ -2248,9 +2286,9 @@ function verifyInstalledPluginState() {
|
|
|
2248
2286
|
};
|
|
2249
2287
|
}
|
|
2250
2288
|
const requiredFiles = [
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2289
|
+
path10.join(installPath, "package.json"),
|
|
2290
|
+
path10.join(installPath, "openclaw.plugin.json"),
|
|
2291
|
+
path10.join(installPath, "dist", "index.js")
|
|
2254
2292
|
];
|
|
2255
2293
|
const requiredFilesMissing = requiredFiles.filter((p) => !fs9.existsSync(p));
|
|
2256
2294
|
if (requiredFilesMissing.length > 0) {
|
|
@@ -2266,7 +2304,7 @@ function verifyInstalledPluginState() {
|
|
|
2266
2304
|
let installedPackage;
|
|
2267
2305
|
try {
|
|
2268
2306
|
installedPackage = JSON.parse(
|
|
2269
|
-
fs9.readFileSync(
|
|
2307
|
+
fs9.readFileSync(path10.join(installPath, "package.json"), "utf-8")
|
|
2270
2308
|
);
|
|
2271
2309
|
} catch (err2) {
|
|
2272
2310
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
@@ -2281,7 +2319,7 @@ function verifyInstalledPluginState() {
|
|
|
2281
2319
|
}
|
|
2282
2320
|
try {
|
|
2283
2321
|
JSON.parse(
|
|
2284
|
-
fs9.readFileSync(
|
|
2322
|
+
fs9.readFileSync(path10.join(installPath, "openclaw.plugin.json"), "utf-8")
|
|
2285
2323
|
);
|
|
2286
2324
|
} catch (err2) {
|
|
2287
2325
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
@@ -2463,6 +2501,59 @@ function registerVersionMethods(api) {
|
|
|
2463
2501
|
);
|
|
2464
2502
|
}
|
|
2465
2503
|
|
|
2504
|
+
// src/questions.ts
|
|
2505
|
+
var MARKER = "[HUMAN_INPUT_REQUIRED]";
|
|
2506
|
+
function normalizeEnvelope(raw) {
|
|
2507
|
+
if (raw.blocking !== true) return null;
|
|
2508
|
+
if (typeof raw.qid !== "string" || !raw.qid.trim()) return null;
|
|
2509
|
+
if (typeof raw.sessionKey !== "string" || !raw.sessionKey.trim()) return null;
|
|
2510
|
+
if (typeof raw.title !== "string" || !raw.title.trim()) return null;
|
|
2511
|
+
if (typeof raw.question !== "string" || !raw.question.trim()) return null;
|
|
2512
|
+
const agentId = typeof raw.agentId === "string" && raw.agentId.trim() ? raw.agentId.trim() : void 0;
|
|
2513
|
+
return {
|
|
2514
|
+
qid: raw.qid.trim(),
|
|
2515
|
+
sessionKey: raw.sessionKey.trim(),
|
|
2516
|
+
agentId,
|
|
2517
|
+
title: raw.title.trim(),
|
|
2518
|
+
question: raw.question.trim(),
|
|
2519
|
+
blocking: true
|
|
2520
|
+
};
|
|
2521
|
+
}
|
|
2522
|
+
function validateHumanQuestionEnvelope(text) {
|
|
2523
|
+
const markerIndex = text.indexOf(MARKER);
|
|
2524
|
+
if (markerIndex < 0) return { valid: false, markerFound: false };
|
|
2525
|
+
const tail = text.slice(markerIndex + MARKER.length).trimStart();
|
|
2526
|
+
const firstLine = tail.split("\n")[0]?.trim() ?? "";
|
|
2527
|
+
if (!firstLine.startsWith("{")) {
|
|
2528
|
+
return { valid: false, markerFound: true, errorCode: "missing_json_line" };
|
|
2529
|
+
}
|
|
2530
|
+
let parsed = null;
|
|
2531
|
+
try {
|
|
2532
|
+
parsed = JSON.parse(firstLine);
|
|
2533
|
+
} catch {
|
|
2534
|
+
return { valid: false, markerFound: true, errorCode: "invalid_json" };
|
|
2535
|
+
}
|
|
2536
|
+
const normalized = normalizeEnvelope(parsed);
|
|
2537
|
+
if (!normalized) {
|
|
2538
|
+
return { valid: false, markerFound: true, errorCode: "schema_invalid" };
|
|
2539
|
+
}
|
|
2540
|
+
return { valid: true, markerFound: true, normalizedEnvelope: normalized };
|
|
2541
|
+
}
|
|
2542
|
+
function registerQuestionMethods(api) {
|
|
2543
|
+
api.registerGatewayMethod(
|
|
2544
|
+
"squad.questions.validate-envelope",
|
|
2545
|
+
async ({ params, respond }) => {
|
|
2546
|
+
const text = typeof params?.text === "string" ? params.text : "";
|
|
2547
|
+
if (!text) {
|
|
2548
|
+
respond(false, { error: "Missing 'text' parameter" });
|
|
2549
|
+
return;
|
|
2550
|
+
}
|
|
2551
|
+
const result = validateHumanQuestionEnvelope(text);
|
|
2552
|
+
respond(true, result);
|
|
2553
|
+
}
|
|
2554
|
+
);
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2466
2557
|
// src/shared-api.ts
|
|
2467
2558
|
var CORE_TOOLS = [
|
|
2468
2559
|
"exec",
|
|
@@ -2514,6 +2605,7 @@ function registerSquadSharedApi(api, onFsChange) {
|
|
|
2514
2605
|
registerSqlTools(api);
|
|
2515
2606
|
registerVersionMethods(api);
|
|
2516
2607
|
registerAgentMethods(api);
|
|
2608
|
+
registerQuestionMethods(api);
|
|
2517
2609
|
const invokeTool = async (tool, args) => {
|
|
2518
2610
|
const executeFn = toolExecutors.get(tool);
|
|
2519
2611
|
if (!executeFn) {
|
package/package.json
CHANGED