squad-openclaw 2026.2.2706 → 2026.2.2707

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 (3) hide show
  1. package/README.md +4 -0
  2. package/dist/index.js +796 -108
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -11,6 +11,8 @@ OpenClaw gateway plugin for [Squad](https://squad.ceo) — provides entity regis
11
11
  | `squad.agents.add` | Plugin wrapper for creating agent scaffolding (workspace seed files, sessions skeleton, and config list entry) |
12
12
  | `squad.version.check` | Plugin version reporting |
13
13
  | `squad.questions.validate-envelope` | HUMAN_INPUT_REQUIRED envelope validation |
14
+ | `squad.extensions.list`, `squad.extensions.update`, `squad.extensions.updateStatus` | Installed extension metadata, cached version checks, and update lifecycle status |
15
+ | `squad.gateway.restart` | Trigger gateway restart for extension-update activation |
14
16
  | `tools.invoke`, `tools.list`, `squad.layout.get` | Core plugin RPC entrypoints for tool invocation/listing and gateway layout metadata |
15
17
  | `squad.plugin.status`, `squad.plugin.recover`, `squad.plugin.disable` | Plugin safety-state RPC control (status, recovery, manual disable) |
16
18
  | `GET /squad-internal/health` | Tailnet internal health + pairing capability metadata |
@@ -31,6 +33,8 @@ All `/squad-internal/*` routes enforce Tailnet context and origin checks. CORS p
31
33
  | `/squad-internal/pairing/request` | `POST` | Creates pairing request via gateway-native pairing methods (`node/devices/device.pair.request`) |
32
34
  | `/squad-internal/pairing/status` | `GET` | Resolves pairing status via gateway-native status methods (`node/devices/device.pair.status/get`) |
33
35
 
36
+ Extension-management routes intentionally do not expose install commands. Only update/status/restart are available via RPC methods.
37
+
34
38
  ## State Directory Resolution
35
39
 
36
40
  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.
package/dist/index.js CHANGED
@@ -1412,12 +1412,662 @@ async function withTimeout(promise, timeoutMs, operation) {
1412
1412
  }
1413
1413
  }
1414
1414
 
1415
- // src/plugin-safety-state.ts
1416
- import crypto from "crypto";
1415
+ // src/extensions-management.ts
1417
1416
  import fs8 from "fs";
1418
1417
  import path8 from "path";
1419
- var SAFETY_DIR = path8.join(getOpenclawStateDir(), "squad-ceo-data", "safety");
1420
- var PLUGIN_SAFETY_STATE_PATH = path8.join(SAFETY_DIR, "plugin-state.json");
1418
+ import { spawn } from "child_process";
1419
+
1420
+ // src/gateway-invoke.ts
1421
+ function asRecord2(value) {
1422
+ return value && typeof value === "object" ? value : null;
1423
+ }
1424
+ function isInvoker(fn) {
1425
+ return typeof fn === "function";
1426
+ }
1427
+ function isUnknownGatewayMethodError(message) {
1428
+ return /unknown method|method .* unavailable|not found|invalid[_ ]request|does not exist/i.test(
1429
+ message
1430
+ );
1431
+ }
1432
+ async function callGatewayAny(ctx, api, method, params) {
1433
+ const ctxGateway = asRecord2(ctx.gateway);
1434
+ const apiGateway = asRecord2(api?.gateway);
1435
+ const candidates = [
1436
+ ctx.request,
1437
+ ctx.callGatewayMethod,
1438
+ ctx.gatewayRequest,
1439
+ ctx.invokeGatewayMethod,
1440
+ ctxGateway?.request,
1441
+ ctxGateway?.callGatewayMethod,
1442
+ api?.request,
1443
+ api?.callGatewayMethod,
1444
+ api?.gatewayRequest,
1445
+ api?.invokeGatewayMethod,
1446
+ apiGateway?.request,
1447
+ apiGateway?.callGatewayMethod
1448
+ ];
1449
+ let lastErr = null;
1450
+ for (const candidate of candidates) {
1451
+ if (!isInvoker(candidate)) continue;
1452
+ try {
1453
+ return await candidate(method, params);
1454
+ } catch (err2) {
1455
+ lastErr = err2;
1456
+ const msg = err2 instanceof Error ? err2.message : String(err2);
1457
+ if (isUnknownGatewayMethodError(msg)) {
1458
+ continue;
1459
+ }
1460
+ throw err2;
1461
+ }
1462
+ }
1463
+ if (lastErr) throw lastErr;
1464
+ throw new Error("Gateway method invocation API unavailable in plugin context");
1465
+ }
1466
+
1467
+ // src/extensions-management.ts
1468
+ var DEFAULT_TTL_MS = 60 * 60 * 1e3;
1469
+ var MIN_TTL_MS = 1e4;
1470
+ var MAX_TTL_MS = 24 * 60 * 60 * 1e3;
1471
+ var NPM_FETCH_TIMEOUT_MS = readTimeoutMs("SQUAD_EXTENSIONS_NPM_FETCH_TIMEOUT_MS", 5e3, 1e3, 3e4);
1472
+ var RESTART_METHOD_TIMEOUT_MS = readTimeoutMs("SQUAD_GATEWAY_RESTART_METHOD_TIMEOUT_MS", 2e3, 500, 1e4);
1473
+ var OPENCLAW_BIN = process.env.SQUAD_OPENCLAW_BIN?.trim() || "openclaw";
1474
+ var DEFAULT_REGISTRY_BASE_URL = "https://registry.npmjs.org";
1475
+ var NPM_REGISTRY_BASE_URL = (process.env.SQUAD_NPM_REGISTRY_BASE_URL?.trim() || DEFAULT_REGISTRY_BASE_URL).replace(/\/+$/g, "");
1476
+ var updateStatuses = /* @__PURE__ */ new Map();
1477
+ var activeUpdatePluginId = null;
1478
+ var ExtensionsManagementError = class extends Error {
1479
+ code;
1480
+ details;
1481
+ constructor(code, message, details = {}) {
1482
+ super(message);
1483
+ this.code = code;
1484
+ this.details = details;
1485
+ }
1486
+ };
1487
+ function isExtensionsManagementError(error) {
1488
+ return error instanceof ExtensionsManagementError;
1489
+ }
1490
+ function toExtensionsErrorPayload(error) {
1491
+ return {
1492
+ code: error.code,
1493
+ error: error.message,
1494
+ errorCode: error.code,
1495
+ errorMessage: error.message,
1496
+ ...error.details
1497
+ };
1498
+ }
1499
+ function nowIso() {
1500
+ return (/* @__PURE__ */ new Date()).toISOString();
1501
+ }
1502
+ function parseMs(value) {
1503
+ if (!value) return null;
1504
+ const parsed = Date.parse(value);
1505
+ return Number.isFinite(parsed) ? parsed : null;
1506
+ }
1507
+ function normalizeRefresh(value) {
1508
+ if (value === "force" || value === "cache-only") return value;
1509
+ return "if-stale";
1510
+ }
1511
+ function normalizeTtl(value) {
1512
+ if (typeof value !== "number" || !Number.isFinite(value)) {
1513
+ return DEFAULT_TTL_MS;
1514
+ }
1515
+ const rounded = Math.floor(value);
1516
+ if (rounded < MIN_TTL_MS) return MIN_TTL_MS;
1517
+ if (rounded > MAX_TTL_MS) return MAX_TTL_MS;
1518
+ return rounded;
1519
+ }
1520
+ function asNonEmptyString(value) {
1521
+ if (typeof value !== "string") return null;
1522
+ const trimmed = value.trim();
1523
+ return trimmed.length > 0 ? trimmed : null;
1524
+ }
1525
+ function readJsonFile(filePath) {
1526
+ try {
1527
+ return JSON.parse(fs8.readFileSync(filePath, "utf-8"));
1528
+ } catch {
1529
+ return null;
1530
+ }
1531
+ }
1532
+ function ensureParentDir(filePath) {
1533
+ fs8.mkdirSync(path8.dirname(filePath), { recursive: true });
1534
+ }
1535
+ function writeJsonFileAtomic(filePath, payload) {
1536
+ ensureParentDir(filePath);
1537
+ const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
1538
+ fs8.writeFileSync(tempPath, `${JSON.stringify(payload, null, 2)}
1539
+ `, "utf-8");
1540
+ fs8.renameSync(tempPath, filePath);
1541
+ }
1542
+ function normalizeSource(value) {
1543
+ if (value === "npm") return "npm";
1544
+ if (typeof value === "string" && value.trim()) return "other";
1545
+ return "unknown";
1546
+ }
1547
+ function normalizePluginStatus(value) {
1548
+ if (value === "up_to_date" || value === "update_available" || value === "unknown" || value === "error") {
1549
+ return value;
1550
+ }
1551
+ return "unknown";
1552
+ }
1553
+ function defaultUpdateStatus() {
1554
+ return {
1555
+ state: "idle",
1556
+ startedAt: null,
1557
+ finishedAt: null,
1558
+ needsRestart: false,
1559
+ error: null
1560
+ };
1561
+ }
1562
+ function setUpdateStatus(pluginId, next) {
1563
+ updateStatuses.set(pluginId, next);
1564
+ }
1565
+ function appendOutputTail(current, chunk, maxChars = 6e3) {
1566
+ const combined = current + chunk;
1567
+ if (combined.length <= maxChars) return combined;
1568
+ return combined.slice(-maxChars);
1569
+ }
1570
+ function updateCachePath(stateDir) {
1571
+ return path8.join(stateDir, "squad-ceo-data", "extensions", "update-check-cache.json");
1572
+ }
1573
+ function readUpdateCheckCache(stateDir) {
1574
+ const filePath = updateCachePath(stateDir);
1575
+ const raw = readJsonFile(filePath);
1576
+ const entries = {};
1577
+ const rawEntries = raw?.entries;
1578
+ if (rawEntries && typeof rawEntries === "object" && !Array.isArray(rawEntries)) {
1579
+ for (const [pluginId, value] of Object.entries(rawEntries)) {
1580
+ if (!value || typeof value !== "object" || Array.isArray(value)) continue;
1581
+ const record = value;
1582
+ const checkedAt = asNonEmptyString(record.checkedAt);
1583
+ if (!checkedAt) continue;
1584
+ entries[pluginId] = {
1585
+ pluginId,
1586
+ packageName: asNonEmptyString(record.packageName),
1587
+ checkedAt,
1588
+ latestVersion: asNonEmptyString(record.latestVersion),
1589
+ status: normalizePluginStatus(record.status),
1590
+ checkError: asNonEmptyString(record.checkError)
1591
+ };
1592
+ }
1593
+ }
1594
+ return {
1595
+ version: 1,
1596
+ updatedAt: asNonEmptyString(raw?.updatedAt) ?? nowIso(),
1597
+ entries
1598
+ };
1599
+ }
1600
+ function writeUpdateCheckCache(stateDir, cache) {
1601
+ const filePath = updateCachePath(stateDir);
1602
+ writeJsonFileAtomic(filePath, cache);
1603
+ }
1604
+ function invalidateUpdateCheckCacheEntry(stateDir, pluginId) {
1605
+ const cache = readUpdateCheckCache(stateDir);
1606
+ if (!(pluginId in cache.entries)) return;
1607
+ delete cache.entries[pluginId];
1608
+ cache.updatedAt = nowIso();
1609
+ writeUpdateCheckCache(stateDir, cache);
1610
+ }
1611
+ function loadInstallConfig(stateDir) {
1612
+ const configPath = path8.join(stateDir, "openclaw.json");
1613
+ const config = readJsonFile(configPath) ?? {};
1614
+ const plugins = config.plugins ?? {};
1615
+ const installs = plugins.installs ?? {};
1616
+ const result = {};
1617
+ for (const [pluginId, value] of Object.entries(installs)) {
1618
+ const record = value && typeof value === "object" && !Array.isArray(value) ? value : {};
1619
+ result[pluginId] = {
1620
+ source: normalizeSource(record.source),
1621
+ spec: asNonEmptyString(record.spec),
1622
+ version: asNonEmptyString(record.version),
1623
+ installPath: asNonEmptyString(record.installPath)
1624
+ };
1625
+ }
1626
+ return result;
1627
+ }
1628
+ function derivePackageNameFromSpec(spec) {
1629
+ if (!spec) return null;
1630
+ let normalized = spec.trim();
1631
+ if (!normalized) return null;
1632
+ if (normalized.startsWith("npm:")) {
1633
+ normalized = normalized.slice(4);
1634
+ }
1635
+ if (normalized.startsWith("@")) {
1636
+ const slashIndex = normalized.indexOf("/");
1637
+ if (slashIndex < 1) return null;
1638
+ const versionAtIndex2 = normalized.indexOf("@", slashIndex + 1);
1639
+ return versionAtIndex2 === -1 ? normalized : normalized.slice(0, versionAtIndex2);
1640
+ }
1641
+ const versionAtIndex = normalized.indexOf("@");
1642
+ return versionAtIndex === -1 ? normalized : normalized.slice(0, versionAtIndex);
1643
+ }
1644
+ function isValidNpmPackageName(value) {
1645
+ if (!value) return false;
1646
+ return /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i.test(value);
1647
+ }
1648
+ function isValidSpecForCommand(value) {
1649
+ if (!value) return false;
1650
+ return /^[a-z0-9@._/-]+$/i.test(value);
1651
+ }
1652
+ function readInstalledPluginDescriptors(stateDir) {
1653
+ const extensionsDir = path8.join(stateDir, "extensions");
1654
+ let entries = [];
1655
+ try {
1656
+ entries = fs8.readdirSync(extensionsDir, { withFileTypes: true });
1657
+ } catch {
1658
+ return [];
1659
+ }
1660
+ const installs = loadInstallConfig(stateDir);
1661
+ const descriptors = [];
1662
+ for (const entry of entries) {
1663
+ if (!entry.isDirectory()) continue;
1664
+ const pluginDir = path8.join(extensionsDir, entry.name);
1665
+ const manifestPath = path8.join(pluginDir, "openclaw.plugin.json");
1666
+ const manifest = readJsonFile(manifestPath);
1667
+ if (!manifest) continue;
1668
+ const pluginId = asNonEmptyString(manifest.id) ?? entry.name;
1669
+ const packageJson = readJsonFile(path8.join(pluginDir, "package.json"));
1670
+ const install = installs[pluginId] ?? {
1671
+ source: "unknown",
1672
+ spec: null,
1673
+ version: null,
1674
+ installPath: null
1675
+ };
1676
+ const packageName = asNonEmptyString(packageJson?.name) ?? derivePackageNameFromSpec(install.spec);
1677
+ const source = install.source;
1678
+ descriptors.push({
1679
+ pluginId,
1680
+ name: asNonEmptyString(manifest.name) ?? asNonEmptyString(packageJson?.name) ?? pluginId,
1681
+ description: asNonEmptyString(manifest.description) ?? asNonEmptyString(packageJson?.description),
1682
+ pluginDir,
1683
+ packageName: isValidNpmPackageName(packageName) ? packageName : null,
1684
+ currentVersion: asNonEmptyString(packageJson?.version) ?? install.version,
1685
+ source,
1686
+ spec: install.spec
1687
+ });
1688
+ }
1689
+ descriptors.sort((a, b) => a.name.localeCompare(b.name));
1690
+ return descriptors;
1691
+ }
1692
+ function comparePrerelease(a, b) {
1693
+ if (!a && !b) return 0;
1694
+ if (!a) return 1;
1695
+ if (!b) return -1;
1696
+ const aParts = a.split(".");
1697
+ const bParts = b.split(".");
1698
+ const maxLen = Math.max(aParts.length, bParts.length);
1699
+ for (let i = 0; i < maxLen; i++) {
1700
+ const aPart = aParts[i];
1701
+ const bPart = bParts[i];
1702
+ if (aPart == null) return -1;
1703
+ if (bPart == null) return 1;
1704
+ const aNum = Number(aPart);
1705
+ const bNum = Number(bPart);
1706
+ const bothNumeric = Number.isInteger(aNum) && Number.isInteger(bNum);
1707
+ if (bothNumeric) {
1708
+ if (aNum > bNum) return 1;
1709
+ if (aNum < bNum) return -1;
1710
+ continue;
1711
+ }
1712
+ const cmp = aPart.localeCompare(bPart);
1713
+ if (cmp !== 0) return cmp > 0 ? 1 : -1;
1714
+ }
1715
+ return 0;
1716
+ }
1717
+ function compareVersionValues(a, b) {
1718
+ const semverRe = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
1719
+ const aMatch = a.match(semverRe);
1720
+ const bMatch = b.match(semverRe);
1721
+ if (!aMatch || !bMatch) {
1722
+ const cmp = a.localeCompare(b, void 0, { numeric: true, sensitivity: "base" });
1723
+ if (cmp === 0) return 0;
1724
+ return cmp > 0 ? 1 : -1;
1725
+ }
1726
+ for (let i = 1; i <= 3; i++) {
1727
+ const aPart = Number(aMatch[i]);
1728
+ const bPart = Number(bMatch[i]);
1729
+ if (aPart > bPart) return 1;
1730
+ if (aPart < bPart) return -1;
1731
+ }
1732
+ return comparePrerelease(aMatch[4] ?? null, bMatch[4] ?? null);
1733
+ }
1734
+ function supportsNpmUpdate(plugin) {
1735
+ return plugin.source === "npm" && isValidNpmPackageName(plugin.packageName);
1736
+ }
1737
+ function resolveUpdateSpec(plugin) {
1738
+ if (isValidSpecForCommand(plugin.spec)) return plugin.spec;
1739
+ if (isValidSpecForCommand(plugin.packageName)) return plugin.packageName;
1740
+ return null;
1741
+ }
1742
+ async function fetchLatestVersion(packageName) {
1743
+ const encoded = encodeURIComponent(packageName);
1744
+ const url = `${NPM_REGISTRY_BASE_URL}/${encoded}/latest`;
1745
+ const controller = new AbortController();
1746
+ const timer = setTimeout(() => controller.abort(), NPM_FETCH_TIMEOUT_MS);
1747
+ try {
1748
+ const response = await fetch(url, { signal: controller.signal });
1749
+ if (!response.ok) {
1750
+ return {
1751
+ latestVersion: null,
1752
+ checkError: `Registry request failed (${response.status})`
1753
+ };
1754
+ }
1755
+ const json2 = await response.json();
1756
+ const latestVersion = asNonEmptyString(json2?.version);
1757
+ if (!latestVersion) {
1758
+ return {
1759
+ latestVersion: null,
1760
+ checkError: "Registry response did not include a valid version."
1761
+ };
1762
+ }
1763
+ return { latestVersion, checkError: null };
1764
+ } catch (error) {
1765
+ const isAbortError = !!(error && typeof error === "object" && "name" in error && error.name === "AbortError");
1766
+ if (isAbortError) {
1767
+ return { latestVersion: null, checkError: `Registry request timed out after ${NPM_FETCH_TIMEOUT_MS}ms` };
1768
+ }
1769
+ const message = error instanceof Error ? error.message : String(error);
1770
+ return { latestVersion: null, checkError: message };
1771
+ } finally {
1772
+ clearTimeout(timer);
1773
+ }
1774
+ }
1775
+ function shouldRefreshCacheEntry(mode, ttlMs, now, entry) {
1776
+ if (mode === "cache-only") return false;
1777
+ if (mode === "force") return true;
1778
+ if (!entry) return true;
1779
+ const checkedAtMs = parseMs(entry.checkedAt);
1780
+ if (checkedAtMs == null) return true;
1781
+ return now - checkedAtMs > ttlMs;
1782
+ }
1783
+ async function listExtensions(params = {}) {
1784
+ const stateDir = getOpenclawStateDir();
1785
+ const refreshMode = normalizeRefresh(params.refresh);
1786
+ const ttlMs = normalizeTtl(params.ttlMs);
1787
+ const nowMs = Date.now();
1788
+ const now = nowIso();
1789
+ const plugins = readInstalledPluginDescriptors(stateDir);
1790
+ const cache = readUpdateCheckCache(stateDir);
1791
+ let cacheDirty = false;
1792
+ let maxCheckedAtMs = null;
1793
+ const resultPlugins = [];
1794
+ for (const plugin of plugins) {
1795
+ const updateSupport = supportsNpmUpdate(plugin) ? "supported" : "unsupported";
1796
+ let latestVersion = null;
1797
+ let status = "unknown";
1798
+ let checkError = null;
1799
+ let checkedAt = null;
1800
+ const cacheEntry = cache.entries[plugin.pluginId] ?? null;
1801
+ if (updateSupport === "supported") {
1802
+ if (shouldRefreshCacheEntry(refreshMode, ttlMs, nowMs, cacheEntry)) {
1803
+ const fetchResult = await fetchLatestVersion(plugin.packageName);
1804
+ if (fetchResult.checkError) {
1805
+ latestVersion = cacheEntry?.latestVersion ?? null;
1806
+ status = "error";
1807
+ checkError = fetchResult.checkError;
1808
+ checkedAt = now;
1809
+ } else {
1810
+ latestVersion = fetchResult.latestVersion;
1811
+ status = "unknown";
1812
+ checkError = null;
1813
+ checkedAt = now;
1814
+ }
1815
+ cache.entries[plugin.pluginId] = {
1816
+ pluginId: plugin.pluginId,
1817
+ packageName: plugin.packageName,
1818
+ checkedAt,
1819
+ latestVersion,
1820
+ status,
1821
+ checkError
1822
+ };
1823
+ cacheDirty = true;
1824
+ } else if (cacheEntry) {
1825
+ latestVersion = cacheEntry.latestVersion;
1826
+ status = cacheEntry.status;
1827
+ checkError = cacheEntry.checkError;
1828
+ checkedAt = cacheEntry.checkedAt;
1829
+ }
1830
+ if (plugin.currentVersion && latestVersion) {
1831
+ const updateAvailable2 = compareVersionValues(latestVersion, plugin.currentVersion) > 0;
1832
+ if (status !== "error") {
1833
+ status = updateAvailable2 ? "update_available" : "up_to_date";
1834
+ }
1835
+ } else if (status !== "error") {
1836
+ status = "unknown";
1837
+ }
1838
+ } else {
1839
+ status = "unknown";
1840
+ }
1841
+ const checkedAtMs = parseMs(checkedAt);
1842
+ if (checkedAtMs != null && (maxCheckedAtMs == null || checkedAtMs > maxCheckedAtMs)) {
1843
+ maxCheckedAtMs = checkedAtMs;
1844
+ }
1845
+ const updateAvailable = updateSupport === "supported" && latestVersion && plugin.currentVersion ? compareVersionValues(latestVersion, plugin.currentVersion) > 0 : null;
1846
+ resultPlugins.push({
1847
+ pluginId: plugin.pluginId,
1848
+ name: plugin.name,
1849
+ description: plugin.description,
1850
+ currentVersion: plugin.currentVersion,
1851
+ latestVersion,
1852
+ updateAvailable,
1853
+ updateSupport,
1854
+ source: plugin.source,
1855
+ spec: plugin.spec,
1856
+ packageName: plugin.packageName,
1857
+ status,
1858
+ checkError,
1859
+ checkedAt
1860
+ });
1861
+ }
1862
+ if (cacheDirty) {
1863
+ cache.updatedAt = now;
1864
+ writeUpdateCheckCache(stateDir, cache);
1865
+ }
1866
+ return {
1867
+ checkedAt: maxCheckedAtMs == null ? null : new Date(maxCheckedAtMs).toISOString(),
1868
+ ttlMs,
1869
+ plugins: resultPlugins
1870
+ };
1871
+ }
1872
+ function buildOpenClawCommandEnv(stateDir) {
1873
+ return {
1874
+ ...process.env,
1875
+ OPENCLAW_STATE_DIR: process.env.OPENCLAW_STATE_DIR ?? stateDir,
1876
+ OPENCLAW_CONFIG_PATH: process.env.OPENCLAW_CONFIG_PATH ?? path8.join(stateDir, "openclaw.json"),
1877
+ OPENCLAW_HOME: process.env.OPENCLAW_HOME ?? path8.join(stateDir, "home")
1878
+ };
1879
+ }
1880
+ function listInstalledPluginIds(stateDir) {
1881
+ return readInstalledPluginDescriptors(stateDir).map((plugin) => plugin.pluginId);
1882
+ }
1883
+ function normalizePluginIds(value) {
1884
+ if (!Array.isArray(value)) return null;
1885
+ const ids = [];
1886
+ for (const entry of value) {
1887
+ const id = asNonEmptyString(entry);
1888
+ if (!id) continue;
1889
+ if (!ids.includes(id)) ids.push(id);
1890
+ }
1891
+ return ids;
1892
+ }
1893
+ function getExtensionsUpdateStatus(params = {}) {
1894
+ const stateDir = getOpenclawStateDir();
1895
+ const requestedIds = normalizePluginIds(params.pluginIds);
1896
+ const pluginIds = requestedIds ?? listInstalledPluginIds(stateDir);
1897
+ for (const id of updateStatuses.keys()) {
1898
+ if (!pluginIds.includes(id) && requestedIds == null) {
1899
+ pluginIds.push(id);
1900
+ }
1901
+ }
1902
+ const statuses = {};
1903
+ for (const pluginId of pluginIds) {
1904
+ statuses[pluginId] = updateStatuses.get(pluginId) ?? defaultUpdateStatus();
1905
+ }
1906
+ return { statuses };
1907
+ }
1908
+ function updateFinishMessage(command, code, signal, stdoutTail, stderrTail) {
1909
+ const details = [];
1910
+ details.push(`${command} exited with code=${String(code)} signal=${String(signal)}`);
1911
+ if (stderrTail.trim()) {
1912
+ details.push(`stderr: ${stderrTail.trim()}`);
1913
+ } else if (stdoutTail.trim()) {
1914
+ details.push(`stdout: ${stdoutTail.trim()}`);
1915
+ }
1916
+ return details.join(" | ");
1917
+ }
1918
+ function startExtensionUpdate(params) {
1919
+ const pluginId = asNonEmptyString(params.pluginId);
1920
+ if (!pluginId) {
1921
+ throw new ExtensionsManagementError("INVALID_REQUEST", "pluginId is required");
1922
+ }
1923
+ if (activeUpdatePluginId) {
1924
+ const activeStatus = updateStatuses.get(activeUpdatePluginId);
1925
+ if (activeStatus?.state === "running") {
1926
+ throw new ExtensionsManagementError(
1927
+ "UPDATE_IN_PROGRESS",
1928
+ `Another extension update is already in progress (${activeUpdatePluginId}).`,
1929
+ { inProgressPluginId: activeUpdatePluginId }
1930
+ );
1931
+ }
1932
+ }
1933
+ const stateDir = getOpenclawStateDir();
1934
+ const plugins = readInstalledPluginDescriptors(stateDir);
1935
+ const plugin = plugins.find((entry) => entry.pluginId === pluginId);
1936
+ if (!plugin) {
1937
+ throw new ExtensionsManagementError("EXTENSION_NOT_FOUND", `Extension '${pluginId}' is not installed.`);
1938
+ }
1939
+ if (!supportsNpmUpdate(plugin)) {
1940
+ throw new ExtensionsManagementError(
1941
+ "UPDATE_NOT_SUPPORTED",
1942
+ `Extension '${pluginId}' does not support updates via npm.`
1943
+ );
1944
+ }
1945
+ const spec = resolveUpdateSpec(plugin);
1946
+ if (!spec) {
1947
+ throw new ExtensionsManagementError(
1948
+ "UPDATE_NOT_SUPPORTED",
1949
+ `Extension '${pluginId}' does not have a valid npm spec for updates.`
1950
+ );
1951
+ }
1952
+ const startedAt = nowIso();
1953
+ setUpdateStatus(pluginId, {
1954
+ state: "running",
1955
+ startedAt,
1956
+ finishedAt: null,
1957
+ needsRestart: false,
1958
+ error: null
1959
+ });
1960
+ activeUpdatePluginId = pluginId;
1961
+ const commandArgs = ["plugins", "update", spec];
1962
+ const child = spawn(OPENCLAW_BIN, commandArgs, {
1963
+ env: buildOpenClawCommandEnv(stateDir),
1964
+ stdio: ["ignore", "pipe", "pipe"]
1965
+ });
1966
+ let stdoutTail = "";
1967
+ let stderrTail = "";
1968
+ let finalized = false;
1969
+ const finalize = (status) => {
1970
+ if (finalized) return;
1971
+ finalized = true;
1972
+ setUpdateStatus(pluginId, status);
1973
+ if (activeUpdatePluginId === pluginId) {
1974
+ activeUpdatePluginId = null;
1975
+ }
1976
+ invalidateUpdateCheckCacheEntry(stateDir, pluginId);
1977
+ };
1978
+ child.stdout?.on("data", (chunk) => {
1979
+ stdoutTail = appendOutputTail(stdoutTail, chunk.toString("utf-8"));
1980
+ });
1981
+ child.stderr?.on("data", (chunk) => {
1982
+ stderrTail = appendOutputTail(stderrTail, chunk.toString("utf-8"));
1983
+ });
1984
+ child.on("error", (error) => {
1985
+ const message = error instanceof Error ? error.message : String(error);
1986
+ finalize({
1987
+ state: "failed",
1988
+ startedAt,
1989
+ finishedAt: nowIso(),
1990
+ needsRestart: false,
1991
+ error: `Failed to start update command: ${message}`
1992
+ });
1993
+ });
1994
+ child.on("exit", (code, signal) => {
1995
+ const finishedAt = nowIso();
1996
+ if (code === 0) {
1997
+ finalize({
1998
+ state: "success",
1999
+ startedAt,
2000
+ finishedAt,
2001
+ needsRestart: true,
2002
+ error: null
2003
+ });
2004
+ return;
2005
+ }
2006
+ finalize({
2007
+ state: "failed",
2008
+ startedAt,
2009
+ finishedAt,
2010
+ needsRestart: false,
2011
+ error: updateFinishMessage(`${OPENCLAW_BIN} ${commandArgs.join(" ")}`, code, signal, stdoutTail, stderrTail)
2012
+ });
2013
+ });
2014
+ return {
2015
+ accepted: true,
2016
+ pluginId,
2017
+ state: "running"
2018
+ };
2019
+ }
2020
+ async function tryGatewayRestartMethod(payload, api) {
2021
+ const methods = [
2022
+ "gateway.restart",
2023
+ "openclaw.gateway.restart",
2024
+ "system.restart",
2025
+ "node.restart"
2026
+ ];
2027
+ for (const method of methods) {
2028
+ try {
2029
+ await withTimeout(
2030
+ callGatewayAny(payload, api, method, {}),
2031
+ RESTART_METHOD_TIMEOUT_MS,
2032
+ `restart method ${method}`
2033
+ );
2034
+ return true;
2035
+ } catch (error) {
2036
+ const message = error instanceof Error ? error.message : String(error);
2037
+ if (isUnknownGatewayMethodError(message)) {
2038
+ continue;
2039
+ }
2040
+ return false;
2041
+ }
2042
+ }
2043
+ return false;
2044
+ }
2045
+ function spawnGatewayRestartCommand(stateDir) {
2046
+ const child = spawn(OPENCLAW_BIN, ["gateway", "restart"], {
2047
+ env: buildOpenClawCommandEnv(stateDir),
2048
+ detached: true,
2049
+ stdio: "ignore"
2050
+ });
2051
+ child.unref();
2052
+ }
2053
+ async function restartGateway(payload, api) {
2054
+ const stateDir = getOpenclawStateDir();
2055
+ const usedGatewayMethod = await tryGatewayRestartMethod(payload, api);
2056
+ if (!usedGatewayMethod) {
2057
+ spawnGatewayRestartCommand(stateDir);
2058
+ }
2059
+ return {
2060
+ accepted: true,
2061
+ issuedAt: nowIso()
2062
+ };
2063
+ }
2064
+
2065
+ // src/plugin-safety-state.ts
2066
+ import crypto from "crypto";
2067
+ import fs9 from "fs";
2068
+ import path9 from "path";
2069
+ var SAFETY_DIR = path9.join(getOpenclawStateDir(), "squad-ceo-data", "safety");
2070
+ var PLUGIN_SAFETY_STATE_PATH = path9.join(SAFETY_DIR, "plugin-state.json");
1421
2071
  var FAILURE_THRESHOLD = readIntegerEnv("SQUAD_PLUGIN_FAILURE_THRESHOLD", 3, 1, 50);
1422
2072
  var FAILURE_WINDOW_MS = readTimeoutMs("SQUAD_PLUGIN_FAILURE_WINDOW_MS", 5 * 60 * 1e3, 1e3, 24 * 60 * 60 * 1e3);
1423
2073
  var QUARANTINE_MS = readTimeoutMs("SQUAD_PLUGIN_QUARANTINE_MS", 10 * 60 * 1e3, 1e3, 24 * 60 * 60 * 1e3);
@@ -1431,7 +2081,7 @@ function readIntegerEnv(envName, fallback, min, max) {
1431
2081
  if (rounded > max) return max;
1432
2082
  return rounded;
1433
2083
  }
1434
- function nowIso() {
2084
+ function nowIso2() {
1435
2085
  return (/* @__PURE__ */ new Date()).toISOString();
1436
2086
  }
1437
2087
  function normalizeIso(value) {
@@ -1456,7 +2106,7 @@ function normalizeFailureCount(value) {
1456
2106
  const rounded = Math.floor(value);
1457
2107
  return rounded > 0 ? rounded : 0;
1458
2108
  }
1459
- function parseMs(value) {
2109
+ function parseMs2(value) {
1460
2110
  if (!value) return null;
1461
2111
  const parsed = Date.parse(value);
1462
2112
  if (!Number.isFinite(parsed)) return null;
@@ -1477,7 +2127,7 @@ function defaultPersistedState() {
1477
2127
  failureWindowStartedAt: null,
1478
2128
  quarantineUntil: null,
1479
2129
  lastErrorId: null,
1480
- updatedAt: nowIso()
2130
+ updatedAt: nowIso2()
1481
2131
  };
1482
2132
  }
1483
2133
  function sanitizePersistedState(value) {
@@ -1498,12 +2148,12 @@ function sanitizePersistedState(value) {
1498
2148
  failureWindowStartedAt: normalizeIso(record.failureWindowStartedAt),
1499
2149
  quarantineUntil: normalizeIso(record.quarantineUntil),
1500
2150
  lastErrorId: normalizeString(record.lastErrorId),
1501
- updatedAt: normalizeIso(record.updatedAt) ?? nowIso()
2151
+ updatedAt: normalizeIso(record.updatedAt) ?? nowIso2()
1502
2152
  };
1503
2153
  }
1504
2154
  function readPersistedState() {
1505
2155
  try {
1506
- const raw = fs8.readFileSync(PLUGIN_SAFETY_STATE_PATH, "utf-8");
2156
+ const raw = fs9.readFileSync(PLUGIN_SAFETY_STATE_PATH, "utf-8");
1507
2157
  return sanitizePersistedState(JSON.parse(raw));
1508
2158
  } catch {
1509
2159
  return defaultPersistedState();
@@ -1511,11 +2161,11 @@ function readPersistedState() {
1511
2161
  }
1512
2162
  function writePersistedState(state) {
1513
2163
  try {
1514
- fs8.mkdirSync(SAFETY_DIR, { recursive: true });
2164
+ fs9.mkdirSync(SAFETY_DIR, { recursive: true });
1515
2165
  const tempPath = `${PLUGIN_SAFETY_STATE_PATH}.${process.pid}.${Date.now()}.tmp`;
1516
- fs8.writeFileSync(tempPath, `${JSON.stringify(state, null, 2)}
2166
+ fs9.writeFileSync(tempPath, `${JSON.stringify(state, null, 2)}
1517
2167
  `, "utf-8");
1518
- fs8.renameSync(tempPath, PLUGIN_SAFETY_STATE_PATH);
2168
+ fs9.renameSync(tempPath, PLUGIN_SAFETY_STATE_PATH);
1519
2169
  } catch (error) {
1520
2170
  const message = error instanceof Error ? error.message : String(error);
1521
2171
  console.warn(`[squad-openclaw] failed to persist plugin safety state: ${message}`);
@@ -1554,7 +2204,7 @@ function snapshotFromState(state, source, canRecover) {
1554
2204
  }
1555
2205
  function maybeReleaseExpiredQuarantine(state) {
1556
2206
  if (state.state !== "QUARANTINED_AUTO") return state;
1557
- const untilMs = parseMs(state.quarantineUntil);
2207
+ const untilMs = parseMs2(state.quarantineUntil);
1558
2208
  if (untilMs == null || Date.now() < untilMs) return state;
1559
2209
  const released = {
1560
2210
  ...state,
@@ -1562,11 +2212,11 @@ function maybeReleaseExpiredQuarantine(state) {
1562
2212
  reasonCode: null,
1563
2213
  reasonMessage: null,
1564
2214
  remediation: null,
1565
- triggeredAt: nowIso(),
2215
+ triggeredAt: nowIso2(),
1566
2216
  quarantineUntil: null,
1567
2217
  failureCount: 0,
1568
2218
  failureWindowStartedAt: null,
1569
- updatedAt: nowIso()
2219
+ updatedAt: nowIso2()
1570
2220
  };
1571
2221
  writePersistedState(released);
1572
2222
  return released;
@@ -1587,8 +2237,8 @@ function getPluginSafetySnapshot() {
1587
2237
  reasonCode: "ENV_KILL_SWITCH",
1588
2238
  reasonMessage: envKillSwitchReason(),
1589
2239
  remediation: "Unset SQUAD_PLUGIN_DISABLED and restart the gateway process.",
1590
- triggeredAt: persisted.triggeredAt ?? nowIso(),
1591
- updatedAt: nowIso()
2240
+ triggeredAt: persisted.triggeredAt ?? nowIso2(),
2241
+ updatedAt: nowIso2()
1592
2242
  };
1593
2243
  return snapshotFromState(envState, "env", false);
1594
2244
  }
@@ -1602,7 +2252,7 @@ function recordPluginFailure(failureCode, failureMessage, remediation) {
1602
2252
  const state = readPersistedState();
1603
2253
  const nowMs = Date.now();
1604
2254
  const now = new Date(nowMs).toISOString();
1605
- const windowStartMs = parseMs(state.failureWindowStartedAt);
2255
+ const windowStartMs = parseMs2(state.failureWindowStartedAt);
1606
2256
  if (windowStartMs == null || nowMs - windowStartMs > FAILURE_WINDOW_MS) {
1607
2257
  state.failureWindowStartedAt = now;
1608
2258
  state.failureCount = 1;
@@ -1630,7 +2280,7 @@ function recordPluginFailure(failureCode, failureMessage, remediation) {
1630
2280
  function setPluginManualDisabled(reasonCode = "MANUAL_KILL_SWITCH", reasonMessage = "Plugin manually disabled", remediation = "Run squad.plugin.recover after resolving plugin issues.") {
1631
2281
  if (isEnvKillSwitchActive()) return getPluginSafetySnapshot();
1632
2282
  const state = readPersistedState();
1633
- const now = nowIso();
2283
+ const now = nowIso2();
1634
2284
  state.state = "DISABLED_MANUAL";
1635
2285
  state.reasonCode = reasonCode;
1636
2286
  state.reasonMessage = reasonMessage;
@@ -1650,7 +2300,7 @@ function recoverPlugin(reasonMessage = "Plugin manually recovered") {
1650
2300
  };
1651
2301
  }
1652
2302
  const state = readPersistedState();
1653
- const now = nowIso();
2303
+ const now = nowIso2();
1654
2304
  state.state = "ACTIVE";
1655
2305
  state.reasonCode = null;
1656
2306
  state.reasonMessage = null;
@@ -1837,6 +2487,93 @@ function registerSquadSharedApi(api, onFsChange) {
1837
2487
  }
1838
2488
  }
1839
2489
  );
2490
+ safeRegisterGatewayMethod(
2491
+ "squad.extensions.list",
2492
+ async ({ params, respond }) => {
2493
+ const safetySnapshot = getPluginSafetySnapshot();
2494
+ if (isPluginExecutionBlocked(safetySnapshot)) {
2495
+ respond(false, pluginBlockedPayload(safetySnapshot));
2496
+ return;
2497
+ }
2498
+ try {
2499
+ const result = await listExtensions({
2500
+ refresh: params?.refresh,
2501
+ ttlMs: params?.ttlMs
2502
+ });
2503
+ respond(true, result);
2504
+ } catch (err2) {
2505
+ if (isExtensionsManagementError(err2)) {
2506
+ respond(false, toExtensionsErrorPayload(err2));
2507
+ return;
2508
+ }
2509
+ respond(false, { errorMessage: errorMessage(err2) });
2510
+ }
2511
+ }
2512
+ );
2513
+ safeRegisterGatewayMethod(
2514
+ "squad.extensions.update",
2515
+ async ({ params, respond }) => {
2516
+ const safetySnapshot = getPluginSafetySnapshot();
2517
+ if (isPluginExecutionBlocked(safetySnapshot)) {
2518
+ respond(false, pluginBlockedPayload(safetySnapshot));
2519
+ return;
2520
+ }
2521
+ try {
2522
+ const result = startExtensionUpdate({
2523
+ pluginId: params?.pluginId
2524
+ });
2525
+ respond(true, result);
2526
+ } catch (err2) {
2527
+ if (isExtensionsManagementError(err2)) {
2528
+ respond(false, toExtensionsErrorPayload(err2));
2529
+ return;
2530
+ }
2531
+ respond(false, { errorMessage: errorMessage(err2) });
2532
+ }
2533
+ }
2534
+ );
2535
+ safeRegisterGatewayMethod(
2536
+ "squad.extensions.updateStatus",
2537
+ async ({ params, respond }) => {
2538
+ const safetySnapshot = getPluginSafetySnapshot();
2539
+ if (isPluginExecutionBlocked(safetySnapshot)) {
2540
+ respond(false, pluginBlockedPayload(safetySnapshot));
2541
+ return;
2542
+ }
2543
+ try {
2544
+ const result = getExtensionsUpdateStatus({
2545
+ pluginIds: params?.pluginIds
2546
+ });
2547
+ respond(true, result);
2548
+ } catch (err2) {
2549
+ if (isExtensionsManagementError(err2)) {
2550
+ respond(false, toExtensionsErrorPayload(err2));
2551
+ return;
2552
+ }
2553
+ respond(false, { errorMessage: errorMessage(err2) });
2554
+ }
2555
+ }
2556
+ );
2557
+ safeRegisterGatewayMethod(
2558
+ "squad.gateway.restart",
2559
+ async (payload) => {
2560
+ const safetySnapshot = getPluginSafetySnapshot();
2561
+ if (isPluginExecutionBlocked(safetySnapshot)) {
2562
+ payload.respond(false, pluginBlockedPayload(safetySnapshot));
2563
+ return;
2564
+ }
2565
+ try {
2566
+ const result = await restartGateway(payload, api);
2567
+ payload.respond(true, result);
2568
+ } catch (err2) {
2569
+ if (isExtensionsManagementError(err2)) {
2570
+ payload.respond(false, toExtensionsErrorPayload(err2));
2571
+ return;
2572
+ }
2573
+ payload.respond(false, { errorMessage: errorMessage(err2) });
2574
+ }
2575
+ }
2576
+ );
1840
2577
  };
1841
2578
  return {
1842
2579
  invokeTool,
@@ -1846,13 +2583,13 @@ function registerSquadSharedApi(api, onFsChange) {
1846
2583
  }
1847
2584
 
1848
2585
  // src/migrations/runner.ts
1849
- import fs11 from "fs";
1850
- import path11 from "path";
2586
+ import fs12 from "fs";
2587
+ import path12 from "path";
1851
2588
 
1852
2589
  // src/migrations/001-enable-main-subagent-access.ts
1853
- import fs9 from "fs";
1854
- import path9 from "path";
1855
- function asRecord2(value) {
2590
+ import fs10 from "fs";
2591
+ import path10 from "path";
2592
+ function asRecord3(value) {
1856
2593
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
1857
2594
  return value;
1858
2595
  }
@@ -1867,24 +2604,24 @@ function mergeStringArrayWithWildcard(value) {
1867
2604
  return Array.from(next);
1868
2605
  }
1869
2606
  function patchConfigOnDisk() {
1870
- const configPath = path9.join(getOpenclawStateDir(), "openclaw.json");
1871
- const raw = fs9.readFileSync(configPath, "utf-8");
2607
+ const configPath = path10.join(getOpenclawStateDir(), "openclaw.json");
2608
+ const raw = fs10.readFileSync(configPath, "utf-8");
1872
2609
  const parsed = JSON.parse(raw);
1873
- const agents = asRecord2(parsed.agents) ?? {};
1874
- const defaults = asRecord2(agents.defaults) ?? {};
1875
- const subagentsDefaults = asRecord2(defaults.subagents) ?? {};
2610
+ const agents = asRecord3(parsed.agents) ?? {};
2611
+ const defaults = asRecord3(agents.defaults) ?? {};
2612
+ const subagentsDefaults = asRecord3(defaults.subagents) ?? {};
1876
2613
  defaults.maxConcurrent = 4;
1877
2614
  defaults.subagents = {
1878
2615
  ...subagentsDefaults,
1879
2616
  maxConcurrent: 8
1880
2617
  };
1881
2618
  const listRaw = Array.isArray(agents.list) ? agents.list : [];
1882
- const list = listRaw.map((entry) => asRecord2(entry)).filter((entry) => Boolean(entry));
2619
+ const list = listRaw.map((entry) => asRecord3(entry)).filter((entry) => Boolean(entry));
1883
2620
  const mainIndex = list.findIndex((entry) => entry.id === "main");
1884
2621
  const existingMain = mainIndex >= 0 ? list[mainIndex] : {};
1885
- const existingIdentity = asRecord2(existingMain.identity) ?? {};
1886
- const existingTools = asRecord2(existingMain.tools) ?? {};
1887
- const existingSubagents = asRecord2(existingMain.subagents) ?? {};
2622
+ const existingIdentity = asRecord3(existingMain.identity) ?? {};
2623
+ const existingTools = asRecord3(existingMain.tools) ?? {};
2624
+ const existingSubagents = asRecord3(existingMain.subagents) ?? {};
1888
2625
  const nextMain = {
1889
2626
  ...existingMain,
1890
2627
  id: "main",
@@ -1908,7 +2645,7 @@ function patchConfigOnDisk() {
1908
2645
  defaults,
1909
2646
  list
1910
2647
  };
1911
- fs9.writeFileSync(configPath, `${JSON.stringify(parsed, null, 2)}
2648
+ fs10.writeFileSync(configPath, `${JSON.stringify(parsed, null, 2)}
1912
2649
  `, "utf-8");
1913
2650
  }
1914
2651
  var migration = {
@@ -1983,13 +2720,13 @@ var migration = {
1983
2720
  var enable_main_subagent_access_default = migration;
1984
2721
 
1985
2722
  // src/auth-profiles.ts
1986
- import fs10 from "fs";
1987
- import path10 from "path";
2723
+ import fs11 from "fs";
2724
+ import path11 from "path";
1988
2725
  function getMainAuthProfilesPath(stateDir) {
1989
- return path10.join(stateDir, "agents", "main", "agent", "auth-profiles.json");
2726
+ return path11.join(stateDir, "agents", "main", "agent", "auth-profiles.json");
1990
2727
  }
1991
2728
  function getAgentAuthProfilesPath(stateDir, agentId) {
1992
- return path10.join(stateDir, "agents", agentId, "agent", "auth-profiles.json");
2729
+ return path11.join(stateDir, "agents", agentId, "agent", "auth-profiles.json");
1993
2730
  }
1994
2731
  function ensureAgentAuthProfiles(agentId) {
1995
2732
  const normalizedAgentId = agentId.trim();
@@ -1997,17 +2734,17 @@ function ensureAgentAuthProfiles(agentId) {
1997
2734
  const stateDir = getOpenclawStateDir();
1998
2735
  const sourcePath = getMainAuthProfilesPath(stateDir);
1999
2736
  const targetPath = getAgentAuthProfilesPath(stateDir, normalizedAgentId);
2000
- if (!fs10.existsSync(sourcePath) || fs10.existsSync(targetPath)) return false;
2001
- fs10.mkdirSync(path10.dirname(targetPath), { recursive: true });
2002
- fs10.copyFileSync(sourcePath, targetPath);
2737
+ if (!fs11.existsSync(sourcePath) || fs11.existsSync(targetPath)) return false;
2738
+ fs11.mkdirSync(path11.dirname(targetPath), { recursive: true });
2739
+ fs11.copyFileSync(sourcePath, targetPath);
2003
2740
  return true;
2004
2741
  }
2005
2742
  function backfillAgentAuthProfiles() {
2006
2743
  const stateDir = getOpenclawStateDir();
2007
- const agentsDir = path10.join(stateDir, "agents");
2008
- if (!fs10.existsSync(agentsDir)) return [];
2744
+ const agentsDir = path11.join(stateDir, "agents");
2745
+ if (!fs11.existsSync(agentsDir)) return [];
2009
2746
  const copied = [];
2010
- for (const entry of fs10.readdirSync(agentsDir, { withFileTypes: true })) {
2747
+ for (const entry of fs11.readdirSync(agentsDir, { withFileTypes: true })) {
2011
2748
  if (!entry.isDirectory()) continue;
2012
2749
  const agentId = entry.name;
2013
2750
  if (ensureAgentAuthProfiles(agentId)) {
@@ -2038,8 +2775,8 @@ var STARTUP_MIGRATIONS = [
2038
2775
  ];
2039
2776
 
2040
2777
  // src/migrations/runner.ts
2041
- var MIGRATIONS_DIR = path11.join(getOpenclawStateDir(), "squad-ceo-data");
2042
- var MIGRATIONS_PATH = path11.join(MIGRATIONS_DIR, "migrations.json");
2778
+ var MIGRATIONS_DIR = path12.join(getOpenclawStateDir(), "squad-ceo-data");
2779
+ var MIGRATIONS_PATH = path12.join(MIGRATIONS_DIR, "migrations.json");
2043
2780
  var STARTUP_MIGRATION_TIMEOUT_MS = readTimeoutMs("SQUAD_STARTUP_MIGRATION_TIMEOUT_MS", 2e4);
2044
2781
  var STARTUP_GATEWAY_CALL_TIMEOUT_MS = readTimeoutMs("SQUAD_STARTUP_GATEWAY_CALL_TIMEOUT_MS", 5e3);
2045
2782
  function defaultState() {
@@ -2050,7 +2787,7 @@ function defaultState() {
2050
2787
  }
2051
2788
  function readState() {
2052
2789
  try {
2053
- const raw = fs11.readFileSync(MIGRATIONS_PATH, "utf-8");
2790
+ const raw = fs12.readFileSync(MIGRATIONS_PATH, "utf-8");
2054
2791
  const parsed = JSON.parse(raw);
2055
2792
  if (!Array.isArray(parsed.completed)) return defaultState();
2056
2793
  return {
@@ -2062,8 +2799,8 @@ function readState() {
2062
2799
  }
2063
2800
  }
2064
2801
  function writeState(state) {
2065
- fs11.mkdirSync(MIGRATIONS_DIR, { recursive: true });
2066
- fs11.writeFileSync(MIGRATIONS_PATH, JSON.stringify(state, null, 2), "utf-8");
2802
+ fs12.mkdirSync(MIGRATIONS_DIR, { recursive: true });
2803
+ fs12.writeFileSync(MIGRATIONS_PATH, JSON.stringify(state, null, 2), "utf-8");
2067
2804
  }
2068
2805
  function makeGatewayCaller(api) {
2069
2806
  return async (method, params = {}) => {
@@ -2130,55 +2867,6 @@ async function runStartupMigrations(api) {
2130
2867
 
2131
2868
  // src/http-routes.ts
2132
2869
  import crypto2 from "crypto";
2133
-
2134
- // src/gateway-invoke.ts
2135
- function asRecord3(value) {
2136
- return value && typeof value === "object" ? value : null;
2137
- }
2138
- function isInvoker(fn) {
2139
- return typeof fn === "function";
2140
- }
2141
- function isUnknownGatewayMethodError(message) {
2142
- return /unknown method|method .* unavailable|not found|invalid[_ ]request|does not exist/i.test(
2143
- message
2144
- );
2145
- }
2146
- async function callGatewayAny(ctx, api, method, params) {
2147
- const ctxGateway = asRecord3(ctx.gateway);
2148
- const apiGateway = asRecord3(api?.gateway);
2149
- const candidates = [
2150
- ctx.request,
2151
- ctx.callGatewayMethod,
2152
- ctx.gatewayRequest,
2153
- ctx.invokeGatewayMethod,
2154
- ctxGateway?.request,
2155
- ctxGateway?.callGatewayMethod,
2156
- api?.request,
2157
- api?.callGatewayMethod,
2158
- api?.gatewayRequest,
2159
- api?.invokeGatewayMethod,
2160
- apiGateway?.request,
2161
- apiGateway?.callGatewayMethod
2162
- ];
2163
- let lastErr = null;
2164
- for (const candidate of candidates) {
2165
- if (!isInvoker(candidate)) continue;
2166
- try {
2167
- return await candidate(method, params);
2168
- } catch (err2) {
2169
- lastErr = err2;
2170
- const msg = err2 instanceof Error ? err2.message : String(err2);
2171
- if (isUnknownGatewayMethodError(msg)) {
2172
- continue;
2173
- }
2174
- throw err2;
2175
- }
2176
- }
2177
- if (lastErr) throw lastErr;
2178
- throw new Error("Gateway method invocation API unavailable in plugin context");
2179
- }
2180
-
2181
- // src/http-routes.ts
2182
2870
  var DEFAULT_ALLOWED_ORIGINS = [
2183
2871
  "https://squad.ceo",
2184
2872
  "https://www.squad.ceo",
@@ -2677,10 +3365,10 @@ function registerTailnetInternalRoutes(api) {
2677
3365
  };
2678
3366
  const handleRequest = async (request) => {
2679
3367
  const url = getRequestUrl(request);
2680
- const path12 = url.pathname;
3368
+ const path13 = url.pathname;
2681
3369
  cleanupCaches();
2682
3370
  let pluginState = getPluginSafetySnapshot();
2683
- if (request.method === "OPTIONS" && path12.startsWith("/squad-internal/")) {
3371
+ if (request.method === "OPTIONS" && path13.startsWith("/squad-internal/")) {
2684
3372
  const origin = ensureOriginAllowed(request);
2685
3373
  if (!origin) {
2686
3374
  return new Response(null, { status: 403 });
@@ -2698,7 +3386,7 @@ function registerTailnetInternalRoutes(api) {
2698
3386
  })
2699
3387
  );
2700
3388
  }
2701
- if (request.method === "GET" && path12 === "/squad-internal/health") {
3389
+ if (request.method === "GET" && path13 === "/squad-internal/health") {
2702
3390
  if (!isTailnetContext(request)) {
2703
3391
  return jsonError(request, "TAILNET_REQUIRED", "Tailnet context required", 403);
2704
3392
  }
@@ -2715,7 +3403,7 @@ function registerTailnetInternalRoutes(api) {
2715
3403
  })
2716
3404
  );
2717
3405
  }
2718
- if (request.method === "GET" && path12 === "/squad-internal/plugin/status") {
3406
+ if (request.method === "GET" && path13 === "/squad-internal/plugin/status") {
2719
3407
  if (!isTailnetContext(request)) {
2720
3408
  return jsonError(request, "TAILNET_REQUIRED", "Tailnet context required", 403);
2721
3409
  }
@@ -2730,7 +3418,7 @@ function registerTailnetInternalRoutes(api) {
2730
3418
  })
2731
3419
  );
2732
3420
  }
2733
- if (request.method === "POST" && path12 === "/squad-internal/plugin/recover") {
3421
+ if (request.method === "POST" && path13 === "/squad-internal/plugin/recover") {
2734
3422
  if (!isTailnetContext(request)) {
2735
3423
  return jsonError(request, "TAILNET_REQUIRED", "Tailnet context required", 403);
2736
3424
  }
@@ -2763,7 +3451,7 @@ function registerTailnetInternalRoutes(api) {
2763
3451
  pluginState = result.snapshot;
2764
3452
  return withCors(request, json({ ok: true, plugin: pluginState, message: result.message }));
2765
3453
  }
2766
- if (request.method === "POST" && path12 === "/squad-internal/plugin/disable") {
3454
+ if (request.method === "POST" && path13 === "/squad-internal/plugin/disable") {
2767
3455
  if (!isTailnetContext(request)) {
2768
3456
  return jsonError(request, "TAILNET_REQUIRED", "Tailnet context required", 403);
2769
3457
  }
@@ -2789,7 +3477,7 @@ function registerTailnetInternalRoutes(api) {
2789
3477
  pluginState = setPluginManualDisabled(reasonCode, reasonMessage, remediation);
2790
3478
  return withCors(request, json({ ok: true, plugin: pluginState }));
2791
3479
  }
2792
- if (path12.startsWith("/squad-internal/") && isPluginExecutionBlocked(pluginState)) {
3480
+ if (path13.startsWith("/squad-internal/") && isPluginExecutionBlocked(pluginState)) {
2793
3481
  const code = pluginBlockedCode(pluginState);
2794
3482
  return jsonError(
2795
3483
  request,
@@ -2799,7 +3487,7 @@ function registerTailnetInternalRoutes(api) {
2799
3487
  { plugin: pluginState }
2800
3488
  );
2801
3489
  }
2802
- if (request.method === "POST" && path12 === "/squad-internal/pairing/request") {
3490
+ if (request.method === "POST" && path13 === "/squad-internal/pairing/request") {
2803
3491
  const origin = ensureOriginAllowed(request);
2804
3492
  if (!origin) {
2805
3493
  return jsonError(request, "ORIGIN_NOT_ALLOWED", "Origin is not allowed", 403);
@@ -2851,7 +3539,7 @@ function registerTailnetInternalRoutes(api) {
2851
3539
  );
2852
3540
  }
2853
3541
  }
2854
- if (request.method === "GET" && path12 === "/squad-internal/pairing/status") {
3542
+ if (request.method === "GET" && path13 === "/squad-internal/pairing/status") {
2855
3543
  const origin = ensureOriginAllowed(request);
2856
3544
  if (!origin) {
2857
3545
  return jsonError(request, "ORIGIN_NOT_ALLOWED", "Origin is not allowed", 403);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squad-openclaw",
3
- "version": "2026.2.2706",
3
+ "version": "2026.2.2707",
4
4
  "description": "Entity registry, filesystem tools, and version management plugin for OpenClaw gateway",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",