squad-openclaw 2026.2.2021 → 2026.2.2022

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 +139 -67
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -731,6 +731,11 @@ function broadcastToUsers(event, payload) {
731
731
 
732
732
  // src/agents.ts
733
733
  import { execSync } from "child_process";
734
+ import path4 from "path";
735
+ function deriveAgentIdFromName(name) {
736
+ const normalized = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
737
+ return normalized || "agent";
738
+ }
734
739
  function registerAgentMethods(api) {
735
740
  const callGateway = async (ctx, method, params = {}) => {
736
741
  const ctxRequest = ctx.request;
@@ -745,6 +750,8 @@ function registerAgentMethods(api) {
745
750
  "squad.agents.add",
746
751
  async ({ params, respond }) => {
747
752
  const name = params?.name;
753
+ const agentId = params?.agentId;
754
+ const workspace = params?.workspace;
748
755
  const model = params?.model;
749
756
  if (!name || typeof name !== "string" || !name.trim()) {
750
757
  respond(false, { error: "Missing or empty 'name' parameter" });
@@ -755,8 +762,19 @@ function registerAgentMethods(api) {
755
762
  respond(false, { error: "Agent name must start with a letter/number and contain only letters, numbers, spaces, hyphens, or underscores" });
756
763
  return;
757
764
  }
765
+ const providedAgentId = typeof agentId === "string" ? agentId.trim() : "";
766
+ if (providedAgentId && !/^[a-z0-9][a-z0-9-]*$/.test(providedAgentId)) {
767
+ respond(false, { error: "Invalid agentId format" });
768
+ return;
769
+ }
770
+ const effectiveAgentId = providedAgentId || deriveAgentIdFromName(safeName);
771
+ const defaultWorkspace = path4.join(
772
+ getOpenclawStateDir(),
773
+ effectiveAgentId === "main" ? "workspace" : `workspace-${effectiveAgentId}`
774
+ );
775
+ const workspacePath = typeof workspace === "string" && workspace.trim() ? workspace.trim() : defaultWorkspace;
758
776
  try {
759
- let cmd = `openclaw agents add ${JSON.stringify(safeName)} --non-interactive`;
777
+ let cmd = `openclaw agents add ${JSON.stringify(safeName)} --non-interactive --workspace ${JSON.stringify(workspacePath)}`;
760
778
  if (model) {
761
779
  cmd += ` --model ${JSON.stringify(model)}`;
762
780
  }
@@ -910,11 +928,11 @@ function registerAgentMethods(api) {
910
928
 
911
929
  // src/entities.ts
912
930
  import { Type as T } from "@sinclair/typebox";
913
- import path7 from "path";
931
+ import path8 from "path";
914
932
  import fs7 from "fs";
915
933
 
916
934
  // src/watcher.ts
917
- import path4 from "path";
935
+ import path5 from "path";
918
936
  import fs4 from "fs";
919
937
  import chokidar from "chokidar";
920
938
  var debounceTimers = /* @__PURE__ */ new Map();
@@ -945,29 +963,29 @@ function debouncedFs(relPath, action, fn) {
945
963
  );
946
964
  }
947
965
  function isWorkspaceIdentity(filePath, configDir) {
948
- const rel = path4.relative(configDir, filePath);
966
+ const rel = path5.relative(configDir, filePath);
949
967
  const match = rel.match(/^(workspace(?:-([^/]+))?)\/IDENTITY\.md$/);
950
968
  if (!match) return null;
951
969
  const dirName = match[1];
952
970
  const agentId = match[2] ?? "main";
953
- return { agentId, workspacePath: path4.join(configDir, dirName) };
971
+ return { agentId, workspacePath: path5.join(configDir, dirName) };
954
972
  }
955
973
  function isWorkspaceAgentJson(filePath, configDir) {
956
- const rel = path4.relative(configDir, filePath);
974
+ const rel = path5.relative(configDir, filePath);
957
975
  const match = rel.match(/^(workspace(?:-([^/]+))?)\/agent\.json$/);
958
976
  if (!match) return null;
959
977
  const dirName = match[1];
960
978
  const agentId = match[2] ?? "main";
961
- return { agentId, workspacePath: path4.join(configDir, dirName) };
979
+ return { agentId, workspacePath: path5.join(configDir, dirName) };
962
980
  }
963
981
  function isGlobalSkillDir(filePath, configDir) {
964
- const rel = path4.relative(configDir, filePath);
982
+ const rel = path5.relative(configDir, filePath);
965
983
  const match = rel.match(/^skills\/([^/]+)\/?$/);
966
984
  if (!match) return null;
967
985
  return { skillKey: match[1] };
968
986
  }
969
987
  function isWorkspaceSkillDir(filePath, configDir) {
970
- const rel = path4.relative(configDir, filePath);
988
+ const rel = path5.relative(configDir, filePath);
971
989
  const match = rel.match(
972
990
  /^workspace(?:-([^/]+))?\/skills\/([^/]+)\/?$/
973
991
  );
@@ -975,13 +993,13 @@ function isWorkspaceSkillDir(filePath, configDir) {
975
993
  return { agentId: match[1] ?? "main", skillKey: match[2] };
976
994
  }
977
995
  function isPluginManifest(filePath, configDir) {
978
- const rel = path4.relative(configDir, filePath);
996
+ const rel = path5.relative(configDir, filePath);
979
997
  const match = rel.match(/^extensions\/([^/]+)\/openclaw\.plugin\.json$/);
980
998
  if (!match) return null;
981
999
  return { pluginDirName: match[1] };
982
1000
  }
983
1001
  function isOpenClawConfig(filePath, configDir) {
984
- return path4.relative(configDir, filePath) === "openclaw.json";
1002
+ return path5.relative(configDir, filePath) === "openclaw.json";
985
1003
  }
986
1004
  function updateAgent(agentId, workspacePath) {
987
1005
  const now = Date.now();
@@ -989,7 +1007,7 @@ function updateAgent(agentId, workspacePath) {
989
1007
  const metadata = { workspacePath };
990
1008
  try {
991
1009
  const content = fs4.readFileSync(
992
- path4.join(workspacePath, "IDENTITY.md"),
1010
+ path5.join(workspacePath, "IDENTITY.md"),
993
1011
  "utf-8"
994
1012
  );
995
1013
  const parsed = parseIdentityName(content);
@@ -999,7 +1017,7 @@ function updateAgent(agentId, workspacePath) {
999
1017
  if (name === agentId) {
1000
1018
  try {
1001
1019
  const raw = fs4.readFileSync(
1002
- path4.join(workspacePath, "agent.json"),
1020
+ path5.join(workspacePath, "agent.json"),
1003
1021
  "utf-8"
1004
1022
  );
1005
1023
  const config = JSON.parse(raw);
@@ -1023,7 +1041,7 @@ function updateAgent(agentId, workspacePath) {
1023
1041
  }
1024
1042
  function updatePlugin(pluginDirName, configDir) {
1025
1043
  const now = Date.now();
1026
- const manifestPath = path4.join(
1044
+ const manifestPath = path5.join(
1027
1045
  configDir,
1028
1046
  "extensions",
1029
1047
  pluginDirName,
@@ -1040,7 +1058,7 @@ function updatePlugin(pluginDirName, configDir) {
1040
1058
  name,
1041
1059
  title: name,
1042
1060
  description: manifest.description || null,
1043
- metadata: { pluginId, pluginDir: path4.dirname(manifestPath) },
1061
+ metadata: { pluginId, pluginDir: path5.dirname(manifestPath) },
1044
1062
  source: "filesystem",
1045
1063
  source_key: manifestPath,
1046
1064
  created_at: now,
@@ -1067,7 +1085,7 @@ function startWatcher(configDir, onFsChange) {
1067
1085
  });
1068
1086
  const emitFsChange = (action, filePath) => {
1069
1087
  if (!onFsChange) return;
1070
- const rel = path4.relative(configDir, filePath);
1088
+ const rel = path5.relative(configDir, filePath);
1071
1089
  debouncedFs(rel, action, () => {
1072
1090
  onFsChange({ action, path: rel });
1073
1091
  });
@@ -1121,7 +1139,7 @@ function startWatcher(configDir, onFsChange) {
1121
1139
  );
1122
1140
  return;
1123
1141
  }
1124
- const rel = path4.relative(configDir, dirPath);
1142
+ const rel = path5.relative(configDir, dirPath);
1125
1143
  if (/^workspace(-[^/]+)?$/.test(rel)) {
1126
1144
  debounced("agents", () => scanAgents(configDir));
1127
1145
  return;
@@ -1129,7 +1147,7 @@ function startWatcher(configDir, onFsChange) {
1129
1147
  };
1130
1148
  const handleUnlinkDir = (dirPath) => {
1131
1149
  emitFsChange("unlinkDir", dirPath);
1132
- const rel = path4.relative(configDir, dirPath);
1150
+ const rel = path5.relative(configDir, dirPath);
1133
1151
  const wsMatch = rel.match(/^workspace(?:-([^/]+))?$/);
1134
1152
  if (wsMatch) {
1135
1153
  const agentId = wsMatch[1] ?? "main";
@@ -1167,14 +1185,14 @@ function startWatcher(configDir, onFsChange) {
1167
1185
 
1168
1186
  // src/filesystem.ts
1169
1187
  import fs6 from "fs";
1170
- import path6 from "path";
1188
+ import path7 from "path";
1171
1189
 
1172
1190
  // src/layout.ts
1173
1191
  import fs5 from "fs";
1174
- import path5 from "path";
1192
+ import path6 from "path";
1175
1193
  function resolveMaybeRelativePath(stateDir, p) {
1176
- if (path5.isAbsolute(p)) return path5.resolve(p);
1177
- return path5.resolve(stateDir, p);
1194
+ if (path6.isAbsolute(p)) return path6.resolve(p);
1195
+ return path6.resolve(stateDir, p);
1178
1196
  }
1179
1197
  function listWorkspaceFallbacks(stateDir) {
1180
1198
  let entries;
@@ -1185,7 +1203,7 @@ function listWorkspaceFallbacks(stateDir) {
1185
1203
  }
1186
1204
  return entries.filter((entry) => entry.isDirectory() && (entry.name === "workspace" || entry.name.startsWith("workspace-"))).map((entry) => {
1187
1205
  const agentId = entry.name === "workspace" ? "main" : entry.name.replace("workspace-", "");
1188
- const workspacePath = path5.join(stateDir, entry.name);
1206
+ const workspacePath = path6.join(stateDir, entry.name);
1189
1207
  return {
1190
1208
  agentId,
1191
1209
  path: workspacePath,
@@ -1204,7 +1222,7 @@ function readOpenclawConfig(configPath) {
1204
1222
  }
1205
1223
  function resolveGatewayLayout() {
1206
1224
  const stateDir = getOpenclawStateDir();
1207
- const configPath = path5.join(stateDir, "openclaw.json");
1225
+ const configPath = path6.join(stateDir, "openclaw.json");
1208
1226
  const config = readOpenclawConfig(configPath);
1209
1227
  const workspaces = [];
1210
1228
  if (config?.agents?.main?.workspace || config?.agents?.main?.workspacePath) {
@@ -1243,9 +1261,9 @@ function resolveGatewayLayout() {
1243
1261
  return {
1244
1262
  stateDir,
1245
1263
  configPath,
1246
- mediaDir: path5.join(stateDir, "media"),
1247
- skillsDir: path5.join(stateDir, "skills"),
1248
- extensionsDir: path5.join(stateDir, "extensions"),
1264
+ mediaDir: path6.join(stateDir, "media"),
1265
+ skillsDir: path6.join(stateDir, "skills"),
1266
+ extensionsDir: path6.join(stateDir, "extensions"),
1249
1267
  defaultFileBrowserRoot,
1250
1268
  workspaces: resolvedWorkspaces
1251
1269
  };
@@ -1255,16 +1273,16 @@ function resolveGatewayLayout() {
1255
1273
  var HOME_DIR = process.env.HOME ?? "/root";
1256
1274
  var OPENCLAW_DIR = getOpenclawStateDir();
1257
1275
  var SENSITIVE_BLOCKED_DIRS = [
1258
- path6.join(OPENCLAW_DIR, "credentials"),
1259
- path6.join(OPENCLAW_DIR, "devices"),
1260
- path6.join(OPENCLAW_DIR, "identity")
1276
+ path7.join(OPENCLAW_DIR, "credentials"),
1277
+ path7.join(OPENCLAW_DIR, "devices"),
1278
+ path7.join(OPENCLAW_DIR, "identity")
1261
1279
  ];
1262
1280
  var SENSITIVE_BLOCKED_FILES = [
1263
- path6.join(OPENCLAW_DIR, "squad-ceo-data", "relay", "squad-relay.json")
1281
+ path7.join(OPENCLAW_DIR, "squad-ceo-data", "relay", "squad-relay.json")
1264
1282
  ];
1265
1283
  function isSensitivePath(resolvedPath) {
1266
1284
  for (const blocked of SENSITIVE_BLOCKED_DIRS) {
1267
- if (resolvedPath === blocked || resolvedPath.startsWith(blocked + path6.sep)) {
1285
+ if (resolvedPath === blocked || resolvedPath.startsWith(blocked + path7.sep)) {
1268
1286
  return true;
1269
1287
  }
1270
1288
  }
@@ -1273,7 +1291,7 @@ function isSensitivePath(resolvedPath) {
1273
1291
  return true;
1274
1292
  }
1275
1293
  }
1276
- if (path6.dirname(resolvedPath) === OPENCLAW_DIR && resolvedPath.endsWith(".bak")) {
1294
+ if (path7.dirname(resolvedPath) === OPENCLAW_DIR && resolvedPath.endsWith(".bak")) {
1277
1295
  return true;
1278
1296
  }
1279
1297
  return false;
@@ -1322,20 +1340,20 @@ function redactOpenclawJson(rawContent) {
1322
1340
  return JSON.stringify(config, null, 2);
1323
1341
  }
1324
1342
  function isOpenclawJson(resolvedPath) {
1325
- return path6.basename(resolvedPath) === OPENCLAW_JSON_FILENAME && resolvedPath.startsWith(OPENCLAW_DIR);
1343
+ return path7.basename(resolvedPath) === OPENCLAW_JSON_FILENAME && resolvedPath.startsWith(OPENCLAW_DIR);
1326
1344
  }
1327
1345
  function expandHome(p) {
1328
1346
  if (p.startsWith("~/") || p === "~") {
1329
- return path6.join(HOME_DIR, p.slice(1));
1347
+ return path7.join(HOME_DIR, p.slice(1));
1330
1348
  }
1331
1349
  return p;
1332
1350
  }
1333
1351
  function validatePath(p, allowedRoots) {
1334
- const resolved = path6.resolve(expandHome(p));
1352
+ const resolved = path7.resolve(expandHome(p));
1335
1353
  if (!allowedRoots || allowedRoots.length === 0) return resolved;
1336
1354
  const allowed = allowedRoots.some((root) => {
1337
- const resolvedRoot = path6.resolve(expandHome(root));
1338
- return resolved === resolvedRoot || resolved.startsWith(resolvedRoot + path6.sep);
1355
+ const resolvedRoot = path7.resolve(expandHome(root));
1356
+ return resolved === resolvedRoot || resolved.startsWith(resolvedRoot + path7.sep);
1339
1357
  });
1340
1358
  if (!allowed) {
1341
1359
  throw new Error(`Path "${p}" is outside allowed roots`);
@@ -1376,7 +1394,7 @@ function listDir(dirPath, opts) {
1376
1394
  const results = [];
1377
1395
  for (const dirent of dirents) {
1378
1396
  if (!opts.includeHidden && dirent.name.startsWith(".")) continue;
1379
- const entryPath = path6.join(dirPath, dirent.name);
1397
+ const entryPath = path7.join(dirPath, dirent.name);
1380
1398
  let type = "other";
1381
1399
  if (dirent.isFile()) type = "file";
1382
1400
  else if (dirent.isDirectory()) type = "directory";
@@ -1487,7 +1505,7 @@ function registerFilesystemTools(api) {
1487
1505
  const encoding = params.encoding ?? "utf-8";
1488
1506
  const mkdir = params.mkdir !== false;
1489
1507
  if (mkdir) {
1490
- fs6.mkdirSync(path6.dirname(filePath), { recursive: true });
1508
+ fs6.mkdirSync(path7.dirname(filePath), { recursive: true });
1491
1509
  }
1492
1510
  fs6.writeFileSync(filePath, content, encoding);
1493
1511
  const stat = fs6.statSync(filePath);
@@ -1687,10 +1705,10 @@ function scanAgents(configDir) {
1687
1705
  );
1688
1706
  for (const dir of workspaceDirs) {
1689
1707
  const agentId = dir.name === "workspace" ? "main" : dir.name.replace("workspace-", "");
1690
- const workspacePath = path7.join(configDir, dir.name);
1708
+ const workspacePath = path8.join(configDir, dir.name);
1691
1709
  let name = agentId;
1692
1710
  const metadata = { workspacePath };
1693
- const identityPath = path7.join(workspacePath, "IDENTITY.md");
1711
+ const identityPath = path8.join(workspacePath, "IDENTITY.md");
1694
1712
  try {
1695
1713
  const content = fs7.readFileSync(identityPath, "utf-8");
1696
1714
  const parsed = parseIdentityName(content);
@@ -1698,7 +1716,7 @@ function scanAgents(configDir) {
1698
1716
  } catch {
1699
1717
  }
1700
1718
  if (name === agentId) {
1701
- const agentJsonPath = path7.join(workspacePath, "agent.json");
1719
+ const agentJsonPath = path8.join(workspacePath, "agent.json");
1702
1720
  try {
1703
1721
  const raw = fs7.readFileSync(agentJsonPath, "utf-8");
1704
1722
  const config = JSON.parse(raw);
@@ -1725,7 +1743,7 @@ function scanAgents(configDir) {
1725
1743
  }
1726
1744
  function scanSkills(configDir) {
1727
1745
  const now = Date.now();
1728
- const globalSkillsDir = path7.join(configDir, "skills");
1746
+ const globalSkillsDir = path8.join(configDir, "skills");
1729
1747
  scanSkillsDir(globalSkillsDir, "global", now);
1730
1748
  let entries;
1731
1749
  try {
@@ -1738,7 +1756,7 @@ function scanSkills(configDir) {
1738
1756
  continue;
1739
1757
  }
1740
1758
  const agentId = dir.name === "workspace" ? "main" : dir.name.replace("workspace-", "");
1741
- const agentSkillsDir = path7.join(configDir, dir.name, "skills");
1759
+ const agentSkillsDir = path8.join(configDir, dir.name, "skills");
1742
1760
  scanSkillsDir(agentSkillsDir, agentId, now);
1743
1761
  }
1744
1762
  }
@@ -1752,12 +1770,12 @@ function scanSkillsDir(skillsDir, scope, now) {
1752
1770
  for (const entry of entries) {
1753
1771
  if (!entry.isDirectory()) continue;
1754
1772
  const skillKey = entry.name;
1755
- const skillPath = path7.join(skillsDir, skillKey);
1773
+ const skillPath = path8.join(skillsDir, skillKey);
1756
1774
  let name = skillKey;
1757
1775
  for (const manifestName of ["manifest.json", "package.json"]) {
1758
1776
  try {
1759
1777
  const raw = fs7.readFileSync(
1760
- path7.join(skillPath, manifestName),
1778
+ path8.join(skillPath, manifestName),
1761
1779
  "utf-8"
1762
1780
  );
1763
1781
  const manifest = JSON.parse(raw);
@@ -1784,7 +1802,7 @@ function scanSkillsDir(skillsDir, scope, now) {
1784
1802
  }
1785
1803
  function scanPlugins2(configDir) {
1786
1804
  const now = Date.now();
1787
- const extensionsDir = path7.join(configDir, "extensions");
1805
+ const extensionsDir = path8.join(configDir, "extensions");
1788
1806
  let entries;
1789
1807
  try {
1790
1808
  entries = fs7.readdirSync(extensionsDir, { withFileTypes: true });
@@ -1793,8 +1811,8 @@ function scanPlugins2(configDir) {
1793
1811
  }
1794
1812
  for (const dir of entries) {
1795
1813
  if (!dir.isDirectory()) continue;
1796
- const pluginDir = path7.join(extensionsDir, dir.name);
1797
- const manifestPath = path7.join(pluginDir, "openclaw.plugin.json");
1814
+ const pluginDir = path8.join(extensionsDir, dir.name);
1815
+ const manifestPath = path8.join(pluginDir, "openclaw.plugin.json");
1798
1816
  try {
1799
1817
  const raw = fs7.readFileSync(manifestPath, "utf-8");
1800
1818
  const manifest = JSON.parse(raw);
@@ -1820,7 +1838,7 @@ function scanTools(configDir) {
1820
1838
  const now = Date.now();
1821
1839
  try {
1822
1840
  const raw = fs7.readFileSync(
1823
- path7.join(configDir, "openclaw.json"),
1841
+ path8.join(configDir, "openclaw.json"),
1824
1842
  "utf-8"
1825
1843
  );
1826
1844
  const config = JSON.parse(raw);
@@ -1871,12 +1889,12 @@ var MIME_MAP = {
1871
1889
  ".gz": "application/gzip"
1872
1890
  };
1873
1891
  function getMimeType(filename) {
1874
- const ext = path7.extname(filename).toLowerCase();
1892
+ const ext = path8.extname(filename).toLowerCase();
1875
1893
  return MIME_MAP[ext] ?? "application/octet-stream";
1876
1894
  }
1877
1895
  function scanMedia(configDir) {
1878
1896
  const now = Date.now();
1879
- const mediaDir = path7.join(configDir, "media");
1897
+ const mediaDir = path8.join(configDir, "media");
1880
1898
  scanMediaDir(mediaDir, now);
1881
1899
  }
1882
1900
  function scanMediaDir(dirPath, now) {
@@ -1888,7 +1906,7 @@ function scanMediaDir(dirPath, now) {
1888
1906
  }
1889
1907
  for (const entry of entries) {
1890
1908
  if (entry.name.startsWith(".")) continue;
1891
- const entryPath = path7.join(dirPath, entry.name);
1909
+ const entryPath = path8.join(dirPath, entry.name);
1892
1910
  if (isSensitivePath(entryPath)) continue;
1893
1911
  if (entry.isDirectory()) {
1894
1912
  registrySet({
@@ -2026,18 +2044,18 @@ function registerEntityTools(api, onFsChange) {
2026
2044
 
2027
2045
  // src/sql.ts
2028
2046
  import { execFile } from "child_process";
2029
- import path8 from "path";
2047
+ import path9 from "path";
2030
2048
  import fs8 from "fs";
2031
2049
  import { Type as T2 } from "@sinclair/typebox";
2032
2050
  var HOME_DIR2 = process.env.HOME ?? "/root";
2033
- var ALLOWED_DATA_DIR = path8.join(getOpenclawStateDir(), "squad-ceo-data");
2051
+ var ALLOWED_DATA_DIR = path9.join(getOpenclawStateDir(), "squad-ceo-data");
2034
2052
  function validateDbPath(dbPath) {
2035
2053
  let expanded = dbPath;
2036
2054
  if (expanded.startsWith("~/") || expanded === "~") {
2037
- expanded = path8.join(HOME_DIR2, expanded.slice(1));
2055
+ expanded = path9.join(HOME_DIR2, expanded.slice(1));
2038
2056
  }
2039
- const resolved = path8.resolve(expanded);
2040
- if (resolved !== ALLOWED_DATA_DIR && !resolved.startsWith(ALLOWED_DATA_DIR + path8.sep)) {
2057
+ const resolved = path9.resolve(expanded);
2058
+ if (resolved !== ALLOWED_DATA_DIR && !resolved.startsWith(ALLOWED_DATA_DIR + path9.sep)) {
2041
2059
  throw new Error(
2042
2060
  `Access denied: database path must be within ~/.openclaw/squad-ceo-data/`
2043
2061
  );
@@ -2111,10 +2129,10 @@ function registerSqlTools(api) {
2111
2129
  // src/version.ts
2112
2130
  import { execSync as execSync2 } from "child_process";
2113
2131
  import fs9 from "fs";
2114
- import path9 from "path";
2132
+ import path10 from "path";
2115
2133
  import { fileURLToPath } from "url";
2116
2134
  var PACKAGE_NAME = "squad-openclaw";
2117
- var CONFIG_PATH = path9.join(getOpenclawStateDir(), "openclaw.json");
2135
+ var CONFIG_PATH = path10.join(getOpenclawStateDir(), "openclaw.json");
2118
2136
  var updateInProgress = false;
2119
2137
  var VERIFY_TIMEOUT_MS = 2e4;
2120
2138
  var VERIFY_INTERVAL_MS = 500;
@@ -2161,7 +2179,7 @@ function reconcileInstallMetadata(verification) {
2161
2179
  }
2162
2180
  function getCurrentVersion() {
2163
2181
  const thisFile = fileURLToPath(import.meta.url);
2164
- const pkgPath = path9.resolve(path9.dirname(thisFile), "..", "package.json");
2182
+ const pkgPath = path10.resolve(path10.dirname(thisFile), "..", "package.json");
2165
2183
  try {
2166
2184
  const pkg = JSON.parse(fs9.readFileSync(pkgPath, "utf-8"));
2167
2185
  return pkg.version ?? "0.0.0";
@@ -2248,9 +2266,9 @@ function verifyInstalledPluginState() {
2248
2266
  };
2249
2267
  }
2250
2268
  const requiredFiles = [
2251
- path9.join(installPath, "package.json"),
2252
- path9.join(installPath, "openclaw.plugin.json"),
2253
- path9.join(installPath, "dist", "index.js")
2269
+ path10.join(installPath, "package.json"),
2270
+ path10.join(installPath, "openclaw.plugin.json"),
2271
+ path10.join(installPath, "dist", "index.js")
2254
2272
  ];
2255
2273
  const requiredFilesMissing = requiredFiles.filter((p) => !fs9.existsSync(p));
2256
2274
  if (requiredFilesMissing.length > 0) {
@@ -2266,7 +2284,7 @@ function verifyInstalledPluginState() {
2266
2284
  let installedPackage;
2267
2285
  try {
2268
2286
  installedPackage = JSON.parse(
2269
- fs9.readFileSync(path9.join(installPath, "package.json"), "utf-8")
2287
+ fs9.readFileSync(path10.join(installPath, "package.json"), "utf-8")
2270
2288
  );
2271
2289
  } catch (err2) {
2272
2290
  const msg = err2 instanceof Error ? err2.message : String(err2);
@@ -2281,7 +2299,7 @@ function verifyInstalledPluginState() {
2281
2299
  }
2282
2300
  try {
2283
2301
  JSON.parse(
2284
- fs9.readFileSync(path9.join(installPath, "openclaw.plugin.json"), "utf-8")
2302
+ fs9.readFileSync(path10.join(installPath, "openclaw.plugin.json"), "utf-8")
2285
2303
  );
2286
2304
  } catch (err2) {
2287
2305
  const msg = err2 instanceof Error ? err2.message : String(err2);
@@ -2463,6 +2481,59 @@ function registerVersionMethods(api) {
2463
2481
  );
2464
2482
  }
2465
2483
 
2484
+ // src/questions.ts
2485
+ var MARKER = "[HUMAN_INPUT_REQUIRED]";
2486
+ function normalizeEnvelope(raw) {
2487
+ if (raw.blocking !== true) return null;
2488
+ if (typeof raw.qid !== "string" || !raw.qid.trim()) return null;
2489
+ if (typeof raw.sessionKey !== "string" || !raw.sessionKey.trim()) return null;
2490
+ if (typeof raw.title !== "string" || !raw.title.trim()) return null;
2491
+ if (typeof raw.question !== "string" || !raw.question.trim()) return null;
2492
+ const agentId = typeof raw.agentId === "string" && raw.agentId.trim() ? raw.agentId.trim() : void 0;
2493
+ return {
2494
+ qid: raw.qid.trim(),
2495
+ sessionKey: raw.sessionKey.trim(),
2496
+ agentId,
2497
+ title: raw.title.trim(),
2498
+ question: raw.question.trim(),
2499
+ blocking: true
2500
+ };
2501
+ }
2502
+ function validateHumanQuestionEnvelope(text) {
2503
+ const markerIndex = text.indexOf(MARKER);
2504
+ if (markerIndex < 0) return { valid: false, markerFound: false };
2505
+ const tail = text.slice(markerIndex + MARKER.length).trimStart();
2506
+ const firstLine = tail.split("\n")[0]?.trim() ?? "";
2507
+ if (!firstLine.startsWith("{")) {
2508
+ return { valid: false, markerFound: true, errorCode: "missing_json_line" };
2509
+ }
2510
+ let parsed = null;
2511
+ try {
2512
+ parsed = JSON.parse(firstLine);
2513
+ } catch {
2514
+ return { valid: false, markerFound: true, errorCode: "invalid_json" };
2515
+ }
2516
+ const normalized = normalizeEnvelope(parsed);
2517
+ if (!normalized) {
2518
+ return { valid: false, markerFound: true, errorCode: "schema_invalid" };
2519
+ }
2520
+ return { valid: true, markerFound: true, normalizedEnvelope: normalized };
2521
+ }
2522
+ function registerQuestionMethods(api) {
2523
+ api.registerGatewayMethod(
2524
+ "squad.questions.validate-envelope",
2525
+ async ({ params, respond }) => {
2526
+ const text = typeof params?.text === "string" ? params.text : "";
2527
+ if (!text) {
2528
+ respond(false, { error: "Missing 'text' parameter" });
2529
+ return;
2530
+ }
2531
+ const result = validateHumanQuestionEnvelope(text);
2532
+ respond(true, result);
2533
+ }
2534
+ );
2535
+ }
2536
+
2466
2537
  // src/shared-api.ts
2467
2538
  var CORE_TOOLS = [
2468
2539
  "exec",
@@ -2514,6 +2585,7 @@ function registerSquadSharedApi(api, onFsChange) {
2514
2585
  registerSqlTools(api);
2515
2586
  registerVersionMethods(api);
2516
2587
  registerAgentMethods(api);
2588
+ registerQuestionMethods(api);
2517
2589
  const invokeTool = async (tool, args) => {
2518
2590
  const executeFn = toolExecutors.get(tool);
2519
2591
  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.2022",
4
4
  "description": "Entity registry, filesystem tools, and version management plugin for OpenClaw gateway",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",