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.
Files changed (2) hide show
  1. package/dist/index.js +164 -72
  2. 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-1" && parsed.ok) {
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 path7 from "path";
952
+ import path8 from "path";
914
953
  import fs7 from "fs";
915
954
 
916
955
  // src/watcher.ts
917
- import path4 from "path";
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 = path4.relative(configDir, filePath);
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: path4.join(configDir, dirName) };
992
+ return { agentId, workspacePath: path5.join(configDir, dirName) };
954
993
  }
955
994
  function isWorkspaceAgentJson(filePath, configDir) {
956
- const rel = path4.relative(configDir, filePath);
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: path4.join(configDir, dirName) };
1000
+ return { agentId, workspacePath: path5.join(configDir, dirName) };
962
1001
  }
963
1002
  function isGlobalSkillDir(filePath, configDir) {
964
- const rel = path4.relative(configDir, filePath);
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 = path4.relative(configDir, filePath);
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 = path4.relative(configDir, filePath);
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 path4.relative(configDir, filePath) === "openclaw.json";
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
- path4.join(workspacePath, "IDENTITY.md"),
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
- path4.join(workspacePath, "agent.json"),
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 = path4.join(
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: path4.dirname(manifestPath) },
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 = path4.relative(configDir, filePath);
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 = path4.relative(configDir, dirPath);
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 = path4.relative(configDir, dirPath);
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 path6 from "path";
1209
+ import path7 from "path";
1171
1210
 
1172
1211
  // src/layout.ts
1173
1212
  import fs5 from "fs";
1174
- import path5 from "path";
1213
+ import path6 from "path";
1175
1214
  function resolveMaybeRelativePath(stateDir, p) {
1176
- if (path5.isAbsolute(p)) return path5.resolve(p);
1177
- return path5.resolve(stateDir, p);
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 = path5.join(stateDir, entry.name);
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 = path5.join(stateDir, "openclaw.json");
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 mainWorkspace = resolvedWorkspaces.find((ws) => ws.agentId === "main");
1242
- const defaultFileBrowserRoot = mainWorkspace?.path ?? stateDir;
1280
+ const defaultFileBrowserRoot = stateDir;
1243
1281
  return {
1244
1282
  stateDir,
1245
1283
  configPath,
1246
- mediaDir: path5.join(stateDir, "media"),
1247
- skillsDir: path5.join(stateDir, "skills"),
1248
- extensionsDir: path5.join(stateDir, "extensions"),
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
- path6.join(OPENCLAW_DIR, "credentials"),
1259
- path6.join(OPENCLAW_DIR, "devices"),
1260
- path6.join(OPENCLAW_DIR, "identity")
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
- path6.join(OPENCLAW_DIR, "squad-ceo-data", "relay", "squad-relay.json")
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 + path6.sep)) {
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 (path6.dirname(resolvedPath) === OPENCLAW_DIR && resolvedPath.endsWith(".bak")) {
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 path6.basename(resolvedPath) === OPENCLAW_JSON_FILENAME && resolvedPath.startsWith(OPENCLAW_DIR);
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 path6.join(HOME_DIR, p.slice(1));
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 = path6.resolve(expandHome(p));
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 = path6.resolve(expandHome(root));
1338
- return resolved === resolvedRoot || resolved.startsWith(resolvedRoot + path6.sep);
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 = path6.join(dirPath, dirent.name);
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(path6.dirname(filePath), { recursive: true });
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 = path7.join(configDir, dir.name);
1728
+ const workspacePath = path8.join(configDir, dir.name);
1691
1729
  let name = agentId;
1692
1730
  const metadata = { workspacePath };
1693
- const identityPath = path7.join(workspacePath, "IDENTITY.md");
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 = path7.join(workspacePath, "agent.json");
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 = path7.join(configDir, "skills");
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 = path7.join(configDir, dir.name, "skills");
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 = path7.join(skillsDir, skillKey);
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
- path7.join(skillPath, manifestName),
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 = path7.join(configDir, "extensions");
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 = path7.join(extensionsDir, dir.name);
1797
- const manifestPath = path7.join(pluginDir, "openclaw.plugin.json");
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
- path7.join(configDir, "openclaw.json"),
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 = path7.extname(filename).toLowerCase();
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 = path7.join(configDir, "media");
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 = path7.join(dirPath, entry.name);
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 path8 from "path";
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 = path8.join(getOpenclawStateDir(), "squad-ceo-data");
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 = path8.join(HOME_DIR2, expanded.slice(1));
2075
+ expanded = path9.join(HOME_DIR2, expanded.slice(1));
2038
2076
  }
2039
- const resolved = path8.resolve(expanded);
2040
- if (resolved !== ALLOWED_DATA_DIR && !resolved.startsWith(ALLOWED_DATA_DIR + path8.sep)) {
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 path9 from "path";
2152
+ import path10 from "path";
2115
2153
  import { fileURLToPath } from "url";
2116
2154
  var PACKAGE_NAME = "squad-openclaw";
2117
- var CONFIG_PATH = path9.join(getOpenclawStateDir(), "openclaw.json");
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 = path9.resolve(path9.dirname(thisFile), "..", "package.json");
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
- path9.join(installPath, "package.json"),
2252
- path9.join(installPath, "openclaw.plugin.json"),
2253
- path9.join(installPath, "dist", "index.js")
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(path9.join(installPath, "package.json"), "utf-8")
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(path9.join(installPath, "openclaw.plugin.json"), "utf-8")
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squad-openclaw",
3
- "version": "2026.2.2021",
3
+ "version": "2026.2.2024",
4
4
  "description": "Entity registry, filesystem tools, and version management plugin for OpenClaw gateway",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",