squad-openclaw 2026.2.2017 → 2026.2.2019
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/README.md +33 -3
- package/dist/index.js +160 -50
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,18 +15,48 @@ OpenClaw gateway plugin for [Squad](https://squad.ceo) — provides entity regis
|
|
|
15
15
|
|
|
16
16
|
## State Directory Resolution
|
|
17
17
|
|
|
18
|
-
All paths in this plugin (and throughout this README) that reference `~/.openclaw` resolve via
|
|
18
|
+
All paths in this plugin (and throughout this README) that reference `~/.openclaw` resolve via environment override when set. This supports Docker and other containerized deployments where the OpenClaw data directory may not be at the default location.
|
|
19
|
+
|
|
20
|
+
Resolution priority:
|
|
21
|
+
|
|
22
|
+
1. `OPENCLAW_STATE_DIR` (canonical)
|
|
23
|
+
2. `OPENCLAW_DIR` (compat alias)
|
|
24
|
+
3. `os.homedir() + "/.openclaw"` (default)
|
|
19
25
|
|
|
20
26
|
| Environment | Typical path | How it's resolved |
|
|
21
27
|
|---|---|---|
|
|
22
28
|
| Standard install | `~/.openclaw` | Default — `os.homedir() + "/.openclaw"` |
|
|
23
|
-
| Docker | `/
|
|
29
|
+
| Docker | `/data/.openclaw` | `OPENCLAW_STATE_DIR=/data/.openclaw` (or `OPENCLAW_DIR=/data/.openclaw`) |
|
|
24
30
|
| Custom / NAS | `/mnt/data/.openclaw` | `OPENCLAW_STATE_DIR=/mnt/data/.openclaw` |
|
|
25
31
|
|
|
26
32
|
**This variable only controls where the plugin looks for OpenClaw's own data directory.** It does not grant the plugin access to the parent directory or any other part of the filesystem. All security restrictions (blocked directories, allowed roots, write protection) are enforced relative to the resolved state directory — not the filesystem root.
|
|
27
33
|
|
|
28
34
|
The resolution logic lives in a single shared module ([`src/paths.ts`](src/paths.ts)) imported by every file that needs the state directory path.
|
|
29
35
|
|
|
36
|
+
### Docker Workspace Mapping
|
|
37
|
+
|
|
38
|
+
If your container mounts the real workspace outside the OpenClaw state directory (for example, `/data/workspace`), create a symlink so OpenClaw-compatible tools can still resolve `.../.openclaw/workspace`:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
mkdir -p /data/.openclaw /data/workspace
|
|
42
|
+
ln -sfn /data/workspace /data/.openclaw/workspace
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Then start your gateway/server with explicit env vars (useful when `.env` is not loaded):
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
PORT=3334 \
|
|
49
|
+
OPENCLAW_STATE_DIR=/data/.openclaw \
|
|
50
|
+
OPENCLAW_DIR=/data/.openclaw \
|
|
51
|
+
node server.js
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Quick verification:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
ls -ld /data/.openclaw /data/.openclaw/workspace /data/workspace /data/.openclaw/openclaw.json
|
|
58
|
+
```
|
|
59
|
+
|
|
30
60
|
## Security Model
|
|
31
61
|
|
|
32
62
|
This plugin enforces a **defense-in-depth** security model with four independent layers. All security rules are hard-coded and non-configurable (except `allowedRoots`) so they can be verified by reading the source code. The bundle is intentionally **not minified** to allow security auditing of the distributed code.
|
|
@@ -231,7 +261,7 @@ Configure in your gateway's `openclaw.json` under the plugin section:
|
|
|
231
261
|
|
|
232
262
|
| Key | Type | Default | Description |
|
|
233
263
|
|---|---|---|---|
|
|
234
|
-
| `fs.allowedRoots` | `string[]` | `[
|
|
264
|
+
| `fs.allowedRoots` | `string[]` | `[resolved OpenClaw state dir]` | Restrict filesystem operations to these directories. Hardcoded blocks on `credentials/`, `devices/`, `identity/`, `relay/squad-relay.json`, and `.bak` files always apply regardless. |
|
|
235
265
|
|
|
236
266
|
## Source Code
|
|
237
267
|
|
package/dist/index.js
CHANGED
|
@@ -105,7 +105,7 @@ function registerAgentMethods(api) {
|
|
|
105
105
|
// src/entities.ts
|
|
106
106
|
import { Type as T } from "@sinclair/typebox";
|
|
107
107
|
import path4 from "path";
|
|
108
|
-
import
|
|
108
|
+
import fs4 from "fs";
|
|
109
109
|
|
|
110
110
|
// src/watcher.ts
|
|
111
111
|
import path from "path";
|
|
@@ -360,14 +360,31 @@ function startWatcher(configDir, onFsChange) {
|
|
|
360
360
|
}
|
|
361
361
|
|
|
362
362
|
// src/filesystem.ts
|
|
363
|
-
import
|
|
363
|
+
import fs3 from "fs";
|
|
364
364
|
import path3 from "path";
|
|
365
365
|
|
|
366
366
|
// src/paths.ts
|
|
367
367
|
import path2 from "path";
|
|
368
368
|
import os from "os";
|
|
369
|
+
import fs2 from "fs";
|
|
369
370
|
function getOpenclawStateDir() {
|
|
370
|
-
|
|
371
|
+
if (process.env.OPENCLAW_STATE_DIR) {
|
|
372
|
+
return process.env.OPENCLAW_STATE_DIR;
|
|
373
|
+
}
|
|
374
|
+
if (process.env.OPENCLAW_CONFIG_PATH) {
|
|
375
|
+
return path2.dirname(process.env.OPENCLAW_CONFIG_PATH);
|
|
376
|
+
}
|
|
377
|
+
const legacyDir = process.env.OPENCLAW_DIR;
|
|
378
|
+
if (legacyDir) {
|
|
379
|
+
const resolvedLegacyDir = path2.resolve(legacyDir);
|
|
380
|
+
const configPath = path2.join(resolvedLegacyDir, "openclaw.json");
|
|
381
|
+
const hasStateMarkers = fs2.existsSync(configPath) || fs2.existsSync(path2.join(resolvedLegacyDir, "agents")) || fs2.existsSync(path2.join(resolvedLegacyDir, "workspace"));
|
|
382
|
+
const looksLikeStateDir = resolvedLegacyDir.endsWith(`${path2.sep}.openclaw`);
|
|
383
|
+
if (hasStateMarkers || looksLikeStateDir) {
|
|
384
|
+
return resolvedLegacyDir;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return path2.join(os.homedir(), ".openclaw");
|
|
371
388
|
}
|
|
372
389
|
|
|
373
390
|
// src/filesystem.ts
|
|
@@ -491,7 +508,7 @@ function err(message) {
|
|
|
491
508
|
};
|
|
492
509
|
}
|
|
493
510
|
function listDir(dirPath, opts) {
|
|
494
|
-
const dirents =
|
|
511
|
+
const dirents = fs3.readdirSync(dirPath, { withFileTypes: true });
|
|
495
512
|
const results = [];
|
|
496
513
|
for (const dirent of dirents) {
|
|
497
514
|
if (!opts.includeHidden && dirent.name.startsWith(".")) continue;
|
|
@@ -502,7 +519,7 @@ function listDir(dirPath, opts) {
|
|
|
502
519
|
else if (dirent.isSymbolicLink()) type = "symlink";
|
|
503
520
|
const entry = { name: dirent.name, path: entryPath, type };
|
|
504
521
|
try {
|
|
505
|
-
const stat =
|
|
522
|
+
const stat = fs3.statSync(entryPath);
|
|
506
523
|
entry.size = stat.size;
|
|
507
524
|
entry.modified = stat.mtime.toISOString();
|
|
508
525
|
} catch {
|
|
@@ -526,7 +543,7 @@ function filterSensitiveEntries(entries) {
|
|
|
526
543
|
});
|
|
527
544
|
}
|
|
528
545
|
function registerFilesystemTools(api) {
|
|
529
|
-
const DEFAULT_ALLOWED_ROOTS = [
|
|
546
|
+
const DEFAULT_ALLOWED_ROOTS = [OPENCLAW_DIR];
|
|
530
547
|
const allowedRoots = api.pluginConfig?.["fs.allowedRoots"] ?? DEFAULT_ALLOWED_ROOTS;
|
|
531
548
|
api.registerTool({
|
|
532
549
|
name: "fs_read",
|
|
@@ -551,8 +568,8 @@ function registerFilesystemTools(api) {
|
|
|
551
568
|
try {
|
|
552
569
|
const filePath = validateAndBlockSensitive(params.path, allowedRoots);
|
|
553
570
|
const encoding = params.encoding ?? "utf-8";
|
|
554
|
-
let content =
|
|
555
|
-
const stat =
|
|
571
|
+
let content = fs3.readFileSync(filePath, encoding);
|
|
572
|
+
const stat = fs3.statSync(filePath);
|
|
556
573
|
if (isOpenclawJson(filePath) && encoding === "utf-8") {
|
|
557
574
|
content = redactOpenclawJson(content);
|
|
558
575
|
}
|
|
@@ -602,10 +619,10 @@ function registerFilesystemTools(api) {
|
|
|
602
619
|
const encoding = params.encoding ?? "utf-8";
|
|
603
620
|
const mkdir = params.mkdir !== false;
|
|
604
621
|
if (mkdir) {
|
|
605
|
-
|
|
622
|
+
fs3.mkdirSync(path3.dirname(filePath), { recursive: true });
|
|
606
623
|
}
|
|
607
|
-
|
|
608
|
-
const stat =
|
|
624
|
+
fs3.writeFileSync(filePath, content, encoding);
|
|
625
|
+
const stat = fs3.statSync(filePath);
|
|
609
626
|
return ok({
|
|
610
627
|
path: filePath,
|
|
611
628
|
size: stat.size,
|
|
@@ -674,7 +691,7 @@ function registerFilesystemTools(api) {
|
|
|
674
691
|
async execute(_id, params) {
|
|
675
692
|
try {
|
|
676
693
|
const targetPath = validateWritePath(params.path, allowedRoots);
|
|
677
|
-
|
|
694
|
+
fs3.mkdirSync(targetPath, { recursive: true });
|
|
678
695
|
return ok({
|
|
679
696
|
path: targetPath,
|
|
680
697
|
created: true
|
|
@@ -707,7 +724,7 @@ function registerFilesystemTools(api) {
|
|
|
707
724
|
try {
|
|
708
725
|
const resolvedOld = validateWritePath(params.oldPath, allowedRoots);
|
|
709
726
|
const resolvedNew = validateWritePath(params.newPath, allowedRoots);
|
|
710
|
-
|
|
727
|
+
fs3.renameSync(resolvedOld, resolvedNew);
|
|
711
728
|
return ok({
|
|
712
729
|
oldPath: resolvedOld,
|
|
713
730
|
newPath: resolvedNew,
|
|
@@ -736,12 +753,12 @@ function registerFilesystemTools(api) {
|
|
|
736
753
|
async execute(_id, params) {
|
|
737
754
|
try {
|
|
738
755
|
const targetPath = validateWritePath(params.path, allowedRoots);
|
|
739
|
-
const stat =
|
|
756
|
+
const stat = fs3.statSync(targetPath);
|
|
740
757
|
const wasDirectory = stat.isDirectory();
|
|
741
758
|
if (wasDirectory) {
|
|
742
|
-
|
|
759
|
+
fs3.rmSync(targetPath, { recursive: true });
|
|
743
760
|
} else {
|
|
744
|
-
|
|
761
|
+
fs3.unlinkSync(targetPath);
|
|
745
762
|
}
|
|
746
763
|
return ok({
|
|
747
764
|
path: targetPath,
|
|
@@ -793,7 +810,7 @@ function scanAgents(configDir) {
|
|
|
793
810
|
const now = Date.now();
|
|
794
811
|
let entries;
|
|
795
812
|
try {
|
|
796
|
-
entries =
|
|
813
|
+
entries = fs4.readdirSync(configDir, { withFileTypes: true });
|
|
797
814
|
} catch {
|
|
798
815
|
return;
|
|
799
816
|
}
|
|
@@ -807,7 +824,7 @@ function scanAgents(configDir) {
|
|
|
807
824
|
const metadata = { workspacePath };
|
|
808
825
|
const identityPath = path4.join(workspacePath, "IDENTITY.md");
|
|
809
826
|
try {
|
|
810
|
-
const content =
|
|
827
|
+
const content = fs4.readFileSync(identityPath, "utf-8");
|
|
811
828
|
const parsed = parseIdentityName(content);
|
|
812
829
|
if (parsed) name = parsed;
|
|
813
830
|
} catch {
|
|
@@ -815,7 +832,7 @@ function scanAgents(configDir) {
|
|
|
815
832
|
if (name === agentId) {
|
|
816
833
|
const agentJsonPath = path4.join(workspacePath, "agent.json");
|
|
817
834
|
try {
|
|
818
|
-
const raw =
|
|
835
|
+
const raw = fs4.readFileSync(agentJsonPath, "utf-8");
|
|
819
836
|
const config = JSON.parse(raw);
|
|
820
837
|
if (config.displayName) name = config.displayName;
|
|
821
838
|
if (config.model) metadata.model = config.model;
|
|
@@ -844,7 +861,7 @@ function scanSkills(configDir) {
|
|
|
844
861
|
scanSkillsDir(globalSkillsDir, "global", now);
|
|
845
862
|
let entries;
|
|
846
863
|
try {
|
|
847
|
-
entries =
|
|
864
|
+
entries = fs4.readdirSync(configDir, { withFileTypes: true });
|
|
848
865
|
} catch {
|
|
849
866
|
return;
|
|
850
867
|
}
|
|
@@ -860,7 +877,7 @@ function scanSkills(configDir) {
|
|
|
860
877
|
function scanSkillsDir(skillsDir, scope, now) {
|
|
861
878
|
let entries;
|
|
862
879
|
try {
|
|
863
|
-
entries =
|
|
880
|
+
entries = fs4.readdirSync(skillsDir, { withFileTypes: true });
|
|
864
881
|
} catch {
|
|
865
882
|
return;
|
|
866
883
|
}
|
|
@@ -871,7 +888,7 @@ function scanSkillsDir(skillsDir, scope, now) {
|
|
|
871
888
|
let name = skillKey;
|
|
872
889
|
for (const manifestName of ["manifest.json", "package.json"]) {
|
|
873
890
|
try {
|
|
874
|
-
const raw =
|
|
891
|
+
const raw = fs4.readFileSync(
|
|
875
892
|
path4.join(skillPath, manifestName),
|
|
876
893
|
"utf-8"
|
|
877
894
|
);
|
|
@@ -902,7 +919,7 @@ function scanPlugins2(configDir) {
|
|
|
902
919
|
const extensionsDir = path4.join(configDir, "extensions");
|
|
903
920
|
let entries;
|
|
904
921
|
try {
|
|
905
|
-
entries =
|
|
922
|
+
entries = fs4.readdirSync(extensionsDir, { withFileTypes: true });
|
|
906
923
|
} catch {
|
|
907
924
|
return;
|
|
908
925
|
}
|
|
@@ -911,7 +928,7 @@ function scanPlugins2(configDir) {
|
|
|
911
928
|
const pluginDir = path4.join(extensionsDir, dir.name);
|
|
912
929
|
const manifestPath = path4.join(pluginDir, "openclaw.plugin.json");
|
|
913
930
|
try {
|
|
914
|
-
const raw =
|
|
931
|
+
const raw = fs4.readFileSync(manifestPath, "utf-8");
|
|
915
932
|
const manifest = JSON.parse(raw);
|
|
916
933
|
const pluginId = manifest.id || dir.name;
|
|
917
934
|
const name = manifest.name || pluginId;
|
|
@@ -934,7 +951,7 @@ function scanPlugins2(configDir) {
|
|
|
934
951
|
function scanTools(configDir) {
|
|
935
952
|
const now = Date.now();
|
|
936
953
|
try {
|
|
937
|
-
const raw =
|
|
954
|
+
const raw = fs4.readFileSync(
|
|
938
955
|
path4.join(configDir, "openclaw.json"),
|
|
939
956
|
"utf-8"
|
|
940
957
|
);
|
|
@@ -997,7 +1014,7 @@ function scanMedia(configDir) {
|
|
|
997
1014
|
function scanMediaDir(dirPath, now) {
|
|
998
1015
|
let entries;
|
|
999
1016
|
try {
|
|
1000
|
-
entries =
|
|
1017
|
+
entries = fs4.readdirSync(dirPath, { withFileTypes: true });
|
|
1001
1018
|
} catch {
|
|
1002
1019
|
return;
|
|
1003
1020
|
}
|
|
@@ -1024,7 +1041,7 @@ function scanMediaDir(dirPath, now) {
|
|
|
1024
1041
|
let size;
|
|
1025
1042
|
let mtime = now;
|
|
1026
1043
|
try {
|
|
1027
|
-
const stat =
|
|
1044
|
+
const stat = fs4.statSync(entryPath);
|
|
1028
1045
|
size = stat.size;
|
|
1029
1046
|
mtime = stat.mtimeMs;
|
|
1030
1047
|
} catch {
|
|
@@ -1142,7 +1159,7 @@ function registerEntityTools(api, onFsChange) {
|
|
|
1142
1159
|
// src/sql.ts
|
|
1143
1160
|
import { execFile } from "child_process";
|
|
1144
1161
|
import path5 from "path";
|
|
1145
|
-
import
|
|
1162
|
+
import fs5 from "fs";
|
|
1146
1163
|
import { Type as T2 } from "@sinclair/typebox";
|
|
1147
1164
|
var HOME_DIR2 = process.env.HOME ?? "/root";
|
|
1148
1165
|
var ALLOWED_DATA_DIR = path5.join(getOpenclawStateDir(), "squad-ceo-data");
|
|
@@ -1158,7 +1175,7 @@ function validateDbPath(dbPath) {
|
|
|
1158
1175
|
);
|
|
1159
1176
|
}
|
|
1160
1177
|
try {
|
|
1161
|
-
const stat =
|
|
1178
|
+
const stat = fs5.statSync(resolved);
|
|
1162
1179
|
if (!stat.isFile()) {
|
|
1163
1180
|
throw new Error(`Not a file: ${dbPath}`);
|
|
1164
1181
|
}
|
|
@@ -1225,7 +1242,7 @@ function registerSqlTools(api) {
|
|
|
1225
1242
|
|
|
1226
1243
|
// src/version.ts
|
|
1227
1244
|
import { execSync as execSync2 } from "child_process";
|
|
1228
|
-
import
|
|
1245
|
+
import fs6 from "fs";
|
|
1229
1246
|
import path6 from "path";
|
|
1230
1247
|
import { fileURLToPath } from "url";
|
|
1231
1248
|
var PACKAGE_NAME = "squad-openclaw";
|
|
@@ -1236,7 +1253,7 @@ var VERIFY_INTERVAL_MS = 500;
|
|
|
1236
1253
|
var RESTART_BUFFER_MS = 5e3;
|
|
1237
1254
|
function readInstalledVersionFromConfig() {
|
|
1238
1255
|
try {
|
|
1239
|
-
const raw =
|
|
1256
|
+
const raw = fs6.readFileSync(CONFIG_PATH, "utf-8");
|
|
1240
1257
|
const cfg = JSON.parse(raw);
|
|
1241
1258
|
const v = cfg?.plugins?.installs?.[PACKAGE_NAME]?.version;
|
|
1242
1259
|
return typeof v === "string" ? v : null;
|
|
@@ -1247,7 +1264,7 @@ function readInstalledVersionFromConfig() {
|
|
|
1247
1264
|
function reconcileInstallMetadata(verification) {
|
|
1248
1265
|
if (!verification.installPath || !verification.packageVersion) return;
|
|
1249
1266
|
try {
|
|
1250
|
-
const raw =
|
|
1267
|
+
const raw = fs6.readFileSync(CONFIG_PATH, "utf-8");
|
|
1251
1268
|
const config = JSON.parse(raw);
|
|
1252
1269
|
if (!config.plugins || typeof config.plugins !== "object") config.plugins = {};
|
|
1253
1270
|
if (!config.plugins.installs || typeof config.plugins.installs !== "object") {
|
|
@@ -1270,7 +1287,7 @@ function reconcileInstallMetadata(verification) {
|
|
|
1270
1287
|
...entry,
|
|
1271
1288
|
enabled: true
|
|
1272
1289
|
};
|
|
1273
|
-
|
|
1290
|
+
fs6.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
|
|
1274
1291
|
} catch {
|
|
1275
1292
|
}
|
|
1276
1293
|
}
|
|
@@ -1278,7 +1295,7 @@ function getCurrentVersion() {
|
|
|
1278
1295
|
const thisFile = fileURLToPath(import.meta.url);
|
|
1279
1296
|
const pkgPath = path6.resolve(path6.dirname(thisFile), "..", "package.json");
|
|
1280
1297
|
try {
|
|
1281
|
-
const pkg = JSON.parse(
|
|
1298
|
+
const pkg = JSON.parse(fs6.readFileSync(pkgPath, "utf-8"));
|
|
1282
1299
|
return pkg.version ?? "0.0.0";
|
|
1283
1300
|
} catch {
|
|
1284
1301
|
return "0.0.0";
|
|
@@ -1323,7 +1340,7 @@ function compareVersions(a, b) {
|
|
|
1323
1340
|
function verifyInstalledPluginState() {
|
|
1324
1341
|
let configRaw;
|
|
1325
1342
|
try {
|
|
1326
|
-
configRaw =
|
|
1343
|
+
configRaw = fs6.readFileSync(CONFIG_PATH, "utf-8");
|
|
1327
1344
|
} catch (err2) {
|
|
1328
1345
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
1329
1346
|
return {
|
|
@@ -1367,7 +1384,7 @@ function verifyInstalledPluginState() {
|
|
|
1367
1384
|
path6.join(installPath, "openclaw.plugin.json"),
|
|
1368
1385
|
path6.join(installPath, "dist", "index.js")
|
|
1369
1386
|
];
|
|
1370
|
-
const requiredFilesMissing = requiredFiles.filter((p) => !
|
|
1387
|
+
const requiredFilesMissing = requiredFiles.filter((p) => !fs6.existsSync(p));
|
|
1371
1388
|
if (requiredFilesMissing.length > 0) {
|
|
1372
1389
|
return {
|
|
1373
1390
|
ok: false,
|
|
@@ -1381,7 +1398,7 @@ function verifyInstalledPluginState() {
|
|
|
1381
1398
|
let installedPackage;
|
|
1382
1399
|
try {
|
|
1383
1400
|
installedPackage = JSON.parse(
|
|
1384
|
-
|
|
1401
|
+
fs6.readFileSync(path6.join(installPath, "package.json"), "utf-8")
|
|
1385
1402
|
);
|
|
1386
1403
|
} catch (err2) {
|
|
1387
1404
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
@@ -1396,7 +1413,7 @@ function verifyInstalledPluginState() {
|
|
|
1396
1413
|
}
|
|
1397
1414
|
try {
|
|
1398
1415
|
JSON.parse(
|
|
1399
|
-
|
|
1416
|
+
fs6.readFileSync(path6.join(installPath, "openclaw.plugin.json"), "utf-8")
|
|
1400
1417
|
);
|
|
1401
1418
|
} catch (err2) {
|
|
1402
1419
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
@@ -1486,7 +1503,7 @@ function registerVersionMethods(api) {
|
|
|
1486
1503
|
let updateOutput = "";
|
|
1487
1504
|
let configBackup = null;
|
|
1488
1505
|
try {
|
|
1489
|
-
configBackup =
|
|
1506
|
+
configBackup = fs6.readFileSync(CONFIG_PATH, "utf-8");
|
|
1490
1507
|
} catch {
|
|
1491
1508
|
}
|
|
1492
1509
|
runDoctorFixSilently();
|
|
@@ -1505,7 +1522,7 @@ function registerVersionMethods(api) {
|
|
|
1505
1522
|
} catch (installErr) {
|
|
1506
1523
|
if (configBackup) {
|
|
1507
1524
|
try {
|
|
1508
|
-
|
|
1525
|
+
fs6.writeFileSync(CONFIG_PATH, configBackup, "utf-8");
|
|
1509
1526
|
} catch {
|
|
1510
1527
|
}
|
|
1511
1528
|
}
|
|
@@ -1523,7 +1540,7 @@ function registerVersionMethods(api) {
|
|
|
1523
1540
|
if (!verification.ok) {
|
|
1524
1541
|
if (configBackup) {
|
|
1525
1542
|
try {
|
|
1526
|
-
|
|
1543
|
+
fs6.writeFileSync(CONFIG_PATH, configBackup, "utf-8");
|
|
1527
1544
|
} catch {
|
|
1528
1545
|
}
|
|
1529
1546
|
}
|
|
@@ -1581,7 +1598,7 @@ function registerVersionMethods(api) {
|
|
|
1581
1598
|
// src/relay-client.ts
|
|
1582
1599
|
import { WebSocket as NodeWebSocket } from "ws";
|
|
1583
1600
|
import crypto3 from "crypto";
|
|
1584
|
-
import
|
|
1601
|
+
import fs8 from "fs";
|
|
1585
1602
|
import path8 from "path";
|
|
1586
1603
|
|
|
1587
1604
|
// src/e2e-crypto.ts
|
|
@@ -1663,24 +1680,24 @@ var E2ECrypto = class {
|
|
|
1663
1680
|
|
|
1664
1681
|
// src/device-keys.ts
|
|
1665
1682
|
import crypto2 from "crypto";
|
|
1666
|
-
import
|
|
1683
|
+
import fs7 from "fs";
|
|
1667
1684
|
import path7 from "path";
|
|
1668
1685
|
var RELAY_DATA_DIR = path7.join(getOpenclawStateDir(), "squad-ceo-data", "relay");
|
|
1669
1686
|
var RELAY_STATE_PATH = path7.join(RELAY_DATA_DIR, "squad-relay.json");
|
|
1670
1687
|
var PENDING_APPROVAL_PATH = path7.join(RELAY_DATA_DIR, "pending-approval.json");
|
|
1671
1688
|
function readRelayState() {
|
|
1672
1689
|
try {
|
|
1673
|
-
const raw =
|
|
1690
|
+
const raw = fs7.readFileSync(RELAY_STATE_PATH, "utf-8");
|
|
1674
1691
|
return JSON.parse(raw);
|
|
1675
1692
|
} catch {
|
|
1676
1693
|
return {};
|
|
1677
1694
|
}
|
|
1678
1695
|
}
|
|
1679
1696
|
function writeRelayState(state) {
|
|
1680
|
-
if (!
|
|
1681
|
-
|
|
1697
|
+
if (!fs7.existsSync(RELAY_DATA_DIR)) {
|
|
1698
|
+
fs7.mkdirSync(RELAY_DATA_DIR, { recursive: true });
|
|
1682
1699
|
}
|
|
1683
|
-
|
|
1700
|
+
fs7.writeFileSync(RELAY_STATE_PATH, JSON.stringify(state, null, 2), { mode: 384 });
|
|
1684
1701
|
}
|
|
1685
1702
|
function toBase64Url(buf) {
|
|
1686
1703
|
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
@@ -1712,7 +1729,7 @@ function writeDeviceInfoFile(keys) {
|
|
|
1712
1729
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1713
1730
|
};
|
|
1714
1731
|
try {
|
|
1715
|
-
|
|
1732
|
+
fs7.writeFileSync(infoPath, JSON.stringify(info, null, 2));
|
|
1716
1733
|
} catch (err2) {
|
|
1717
1734
|
console.error("[device-keys] Failed to write relay-device-info.json:", err2);
|
|
1718
1735
|
}
|
|
@@ -1723,7 +1740,7 @@ function readOperatorToken() {
|
|
|
1723
1740
|
const stateDir = getOpenclawStateDir();
|
|
1724
1741
|
const configPath = path8.join(stateDir, "openclaw.json");
|
|
1725
1742
|
try {
|
|
1726
|
-
const raw =
|
|
1743
|
+
const raw = fs8.readFileSync(configPath, "utf-8");
|
|
1727
1744
|
const config = JSON.parse(raw);
|
|
1728
1745
|
return config?.gateway?.auth?.token ?? config?.gateway?.remote?.token ?? config?.gateway?.token ?? null;
|
|
1729
1746
|
} catch {
|
|
@@ -1739,7 +1756,7 @@ function readGatewayLocalWsConfig() {
|
|
|
1739
1756
|
const stateDir = getOpenclawStateDir();
|
|
1740
1757
|
const configPath = path8.join(stateDir, "openclaw.json");
|
|
1741
1758
|
try {
|
|
1742
|
-
const raw =
|
|
1759
|
+
const raw = fs8.readFileSync(configPath, "utf-8");
|
|
1743
1760
|
const config = JSON.parse(raw);
|
|
1744
1761
|
const parsedPort = Number(config?.gateway?.port);
|
|
1745
1762
|
if (Number.isFinite(parsedPort) && parsedPort > 0) {
|
|
@@ -2285,6 +2302,88 @@ function broadcastToUsers(event, payload) {
|
|
|
2285
2302
|
relayClient?.broadcastToUsers(event, payload);
|
|
2286
2303
|
}
|
|
2287
2304
|
|
|
2305
|
+
// src/layout.ts
|
|
2306
|
+
import fs9 from "fs";
|
|
2307
|
+
import path9 from "path";
|
|
2308
|
+
function resolveMaybeRelativePath(stateDir, p) {
|
|
2309
|
+
if (path9.isAbsolute(p)) return path9.resolve(p);
|
|
2310
|
+
return path9.resolve(stateDir, p);
|
|
2311
|
+
}
|
|
2312
|
+
function listWorkspaceFallbacks(stateDir) {
|
|
2313
|
+
let entries;
|
|
2314
|
+
try {
|
|
2315
|
+
entries = fs9.readdirSync(stateDir, { withFileTypes: true });
|
|
2316
|
+
} catch {
|
|
2317
|
+
return [];
|
|
2318
|
+
}
|
|
2319
|
+
return entries.filter((entry) => entry.isDirectory() && (entry.name === "workspace" || entry.name.startsWith("workspace-"))).map((entry) => {
|
|
2320
|
+
const agentId = entry.name === "workspace" ? "main" : entry.name.replace("workspace-", "");
|
|
2321
|
+
const workspacePath = path9.join(stateDir, entry.name);
|
|
2322
|
+
return {
|
|
2323
|
+
agentId,
|
|
2324
|
+
path: workspacePath,
|
|
2325
|
+
source: "filesystem",
|
|
2326
|
+
exists: true
|
|
2327
|
+
};
|
|
2328
|
+
});
|
|
2329
|
+
}
|
|
2330
|
+
function readOpenclawConfig(configPath) {
|
|
2331
|
+
try {
|
|
2332
|
+
const raw = fs9.readFileSync(configPath, "utf-8");
|
|
2333
|
+
return JSON.parse(raw);
|
|
2334
|
+
} catch {
|
|
2335
|
+
return null;
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
function resolveGatewayLayout() {
|
|
2339
|
+
const stateDir = getOpenclawStateDir();
|
|
2340
|
+
const configPath = path9.join(stateDir, "openclaw.json");
|
|
2341
|
+
const config = readOpenclawConfig(configPath);
|
|
2342
|
+
const workspaces = [];
|
|
2343
|
+
if (config?.agents?.main?.workspace || config?.agents?.main?.workspacePath) {
|
|
2344
|
+
const rawPath = config.agents.main.workspace ?? config.agents.main.workspacePath;
|
|
2345
|
+
if (rawPath) {
|
|
2346
|
+
const resolvedPath = resolveMaybeRelativePath(stateDir, rawPath);
|
|
2347
|
+
workspaces.push({
|
|
2348
|
+
agentId: "main",
|
|
2349
|
+
path: resolvedPath,
|
|
2350
|
+
source: "config",
|
|
2351
|
+
exists: fs9.existsSync(resolvedPath)
|
|
2352
|
+
});
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
for (const agent of config?.agents?.list ?? []) {
|
|
2356
|
+
const agentId = typeof agent.id === "string" && agent.id.trim() ? agent.id : null;
|
|
2357
|
+
const rawPath = agent.workspace ?? agent.workspacePath;
|
|
2358
|
+
if (!agentId || !rawPath) continue;
|
|
2359
|
+
const resolvedPath = resolveMaybeRelativePath(stateDir, rawPath);
|
|
2360
|
+
workspaces.push({
|
|
2361
|
+
agentId,
|
|
2362
|
+
path: resolvedPath,
|
|
2363
|
+
source: "config",
|
|
2364
|
+
exists: fs9.existsSync(resolvedPath)
|
|
2365
|
+
});
|
|
2366
|
+
}
|
|
2367
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
2368
|
+
for (const ws of [...workspaces, ...listWorkspaceFallbacks(stateDir)]) {
|
|
2369
|
+
if (!deduped.has(ws.agentId)) {
|
|
2370
|
+
deduped.set(ws.agentId, ws);
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
const resolvedWorkspaces = Array.from(deduped.values());
|
|
2374
|
+
const mainWorkspace = resolvedWorkspaces.find((ws) => ws.agentId === "main");
|
|
2375
|
+
const defaultFileBrowserRoot = mainWorkspace?.path ?? stateDir;
|
|
2376
|
+
return {
|
|
2377
|
+
stateDir,
|
|
2378
|
+
configPath,
|
|
2379
|
+
mediaDir: path9.join(stateDir, "media"),
|
|
2380
|
+
skillsDir: path9.join(stateDir, "skills"),
|
|
2381
|
+
extensionsDir: path9.join(stateDir, "extensions"),
|
|
2382
|
+
defaultFileBrowserRoot,
|
|
2383
|
+
workspaces: resolvedWorkspaces
|
|
2384
|
+
};
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2288
2387
|
// src/index.ts
|
|
2289
2388
|
function squadAppPlugin(api) {
|
|
2290
2389
|
const toolExecutors = /* @__PURE__ */ new Map();
|
|
@@ -2367,6 +2466,17 @@ function squadAppPlugin(api) {
|
|
|
2367
2466
|
respond(true, { tools: [...coreTools, ...groups, ...pluginTools] });
|
|
2368
2467
|
}
|
|
2369
2468
|
);
|
|
2469
|
+
api.registerGatewayMethod(
|
|
2470
|
+
"squad.layout.get",
|
|
2471
|
+
async ({ respond }) => {
|
|
2472
|
+
try {
|
|
2473
|
+
respond(true, resolveGatewayLayout());
|
|
2474
|
+
} catch (err2) {
|
|
2475
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
2476
|
+
respond(false, { errorMessage: msg });
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
);
|
|
2370
2480
|
const relayState = readRelayState();
|
|
2371
2481
|
const relayEnabled = !!(relayState.claimToken || relayState.roomId);
|
|
2372
2482
|
if (relayEnabled) {
|
package/package.json
CHANGED