@alfe.ai/integrations 0.2.6 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +10 -0
- package/dist/index.js +136 -6
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -951,6 +951,16 @@ declare class OpenClawApplier implements RuntimeApplier {
|
|
|
951
951
|
* plugins under 2026.5+, which made force-mode skip its uninstall step.
|
|
952
952
|
*/
|
|
953
953
|
private isPluginInstalled;
|
|
954
|
+
/**
|
|
955
|
+
* Best-effort read of the installed version of a plugin, or `undefined` when
|
|
956
|
+
* it can't be determined. Reads the version from the installed package's
|
|
957
|
+
* package.json under the npm registry path OpenClaw 2026.5+ installs to
|
|
958
|
+
* (`~/.openclaw/npm/node_modules/<pkg>/package.json`) — the same root
|
|
959
|
+
* `isPluginInstalled` checks. 2026.4 extensions-dir installs have no
|
|
960
|
+
* stable package.json version here and return `undefined` (callers fail
|
|
961
|
+
* open to the safe reinstall path).
|
|
962
|
+
*/
|
|
963
|
+
private installedPluginVersion;
|
|
954
964
|
/**
|
|
955
965
|
* Remove an extensions/ install that has no matching record in
|
|
956
966
|
* plugins/installs.json. OpenClaw 2026.5+ tracks installs via the records
|
package/dist/index.js
CHANGED
|
@@ -856,6 +856,22 @@ function stripPluginVersion(spec) {
|
|
|
856
856
|
if (at <= 0) return spec;
|
|
857
857
|
return spec.slice(0, at);
|
|
858
858
|
}
|
|
859
|
+
/**
|
|
860
|
+
* Return the pinned version from a plugin spec, or `undefined` when the spec
|
|
861
|
+
* carries no version (bare package name). Mirror of `stripPluginVersion`: the
|
|
862
|
+
* version is everything after the LAST '@' when that '@' sits at index > 0
|
|
863
|
+
* (i.e. it's a real version delimiter, not the scope's leading '@').
|
|
864
|
+
*
|
|
865
|
+
* Used by the force-mode reinstall path to compare the pinned version against
|
|
866
|
+
* the installed one so an already-current plugin can skip a ~45s
|
|
867
|
+
* uninstall+reinstall cycle.
|
|
868
|
+
*/
|
|
869
|
+
function pluginSpecVersion(spec) {
|
|
870
|
+
const at = spec.lastIndexOf("@");
|
|
871
|
+
if (at <= 0) return void 0;
|
|
872
|
+
const version = spec.slice(at + 1);
|
|
873
|
+
return version.length > 0 ? version : void 0;
|
|
874
|
+
}
|
|
859
875
|
//#endregion
|
|
860
876
|
//#region src/integration-manager.ts
|
|
861
877
|
/**
|
|
@@ -1729,6 +1745,15 @@ const BUNDLED_BASELINE_ALLOW = ["browser"];
|
|
|
1729
1745
|
const CONFIG_SET_RETRIES$1 = 3;
|
|
1730
1746
|
const CONFIG_SET_RETRY_DELAY_MS$1 = 750;
|
|
1731
1747
|
/**
|
|
1748
|
+
* Max `openclaw config get` spawns in flight at once during the batch
|
|
1749
|
+
* verify-after-write. A naive `Promise.all(leaves.map(...))` fires one CLI
|
|
1750
|
+
* process per leaf simultaneously — a 36-leaf model-provider batch on a 2-vCPU
|
|
1751
|
+
* box means 36 concurrent `config get`s, each needing ~1.9s of CPU, so every
|
|
1752
|
+
* process blows its own 10s timeout and the verify reports false even when the
|
|
1753
|
+
* write actually landed. A small pool keeps the read-backs honest under load.
|
|
1754
|
+
*/
|
|
1755
|
+
const VERIFY_GET_CONCURRENCY = 4;
|
|
1756
|
+
/**
|
|
1732
1757
|
* Sentinel OpenClaw returns from `config get` in place of a sensitive value —
|
|
1733
1758
|
* the key IS set, OpenClaw is just hiding it. Verify-after-write must treat a
|
|
1734
1759
|
* read-back of this as "present/matches" rather than a mismatch (see
|
|
@@ -1739,6 +1764,29 @@ const delay$1 = (ms) => new Promise((resolve) => {
|
|
|
1739
1764
|
setTimeout(resolve, ms);
|
|
1740
1765
|
});
|
|
1741
1766
|
/**
|
|
1767
|
+
* Run `fn` over `items` with at most `limit` invocations in flight at once,
|
|
1768
|
+
* preserving result order. A tiny local pool (no new dependency) so the batch
|
|
1769
|
+
* verify-after-write doesn't stampede one `openclaw config get` per leaf — see
|
|
1770
|
+
* VERIFY_GET_CONCURRENCY.
|
|
1771
|
+
*
|
|
1772
|
+
* NOTE: relies on `fn` never rejecting (configValueMatches catches internally).
|
|
1773
|
+
* A throwing `fn` would reject the pool while sibling workers keep draining
|
|
1774
|
+
* items in the background — don't wire a throwing `fn` in here.
|
|
1775
|
+
*/
|
|
1776
|
+
async function mapWithConcurrency(items, limit, fn) {
|
|
1777
|
+
const results = new Array(items.length);
|
|
1778
|
+
let cursor = 0;
|
|
1779
|
+
const worker = async () => {
|
|
1780
|
+
while (cursor < items.length) {
|
|
1781
|
+
const index = cursor++;
|
|
1782
|
+
results[index] = await fn(items[index]);
|
|
1783
|
+
}
|
|
1784
|
+
};
|
|
1785
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());
|
|
1786
|
+
await Promise.all(workers);
|
|
1787
|
+
return results;
|
|
1788
|
+
}
|
|
1789
|
+
/**
|
|
1742
1790
|
* Asymmetric structural containment for verify-after-write: is every field we
|
|
1743
1791
|
* INTENDED present in the ACTUAL read-back with our value? `actual` may carry
|
|
1744
1792
|
* EXTRA keys that `intended` does not.
|
|
@@ -1781,19 +1829,70 @@ function deepSubset(intended, actual) {
|
|
|
1781
1829
|
function redactConfigSetTarget(args) {
|
|
1782
1830
|
return `openclaw config ${args.slice(0, 2).join(" ")}`.trim();
|
|
1783
1831
|
}
|
|
1832
|
+
/** Cap a captured stream so a runaway OpenClaw dump can't bloat errorMessage. */
|
|
1833
|
+
const ERROR_STREAM_MAX = 400;
|
|
1834
|
+
function truncateForError(s) {
|
|
1835
|
+
return s.length > ERROR_STREAM_MAX ? `${s.slice(0, ERROR_STREAM_MAX)}… (truncated)` : s;
|
|
1836
|
+
}
|
|
1837
|
+
/**
|
|
1838
|
+
* Collect the value strings a `config set` argv carries so they can be scrubbed
|
|
1839
|
+
* out of OpenClaw's own output. OpenClaw validation errors can echo the
|
|
1840
|
+
* offending VALUE back in their diagnostic text, so stdout/stderr must be
|
|
1841
|
+
* treated as value-bearing, not just the argv. For `{path, value}` batch
|
|
1842
|
+
* entries only the `value` side is collected — redacting the path out of
|
|
1843
|
+
* "invalid path models.providers…" would gut the diagnostic.
|
|
1844
|
+
*/
|
|
1845
|
+
function collectArgvValues(args) {
|
|
1846
|
+
const out = [];
|
|
1847
|
+
const collect = (v) => {
|
|
1848
|
+
if (typeof v === "string") {
|
|
1849
|
+
if (v.length >= 4) out.push(v);
|
|
1850
|
+
} else if (Array.isArray(v)) v.forEach(collect);
|
|
1851
|
+
else if (v !== null && typeof v === "object") {
|
|
1852
|
+
const o = v;
|
|
1853
|
+
if (typeof o.path === "string" && "value" in o) collect(o.value);
|
|
1854
|
+
else Object.values(o).forEach(collect);
|
|
1855
|
+
}
|
|
1856
|
+
};
|
|
1857
|
+
for (const token of args.slice(2)) {
|
|
1858
|
+
if (token.startsWith("--")) continue;
|
|
1859
|
+
try {
|
|
1860
|
+
collect(JSON.parse(token));
|
|
1861
|
+
} catch {
|
|
1862
|
+
collect(token);
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
return out.sort((a, b) => b.length - a.length);
|
|
1866
|
+
}
|
|
1867
|
+
/** Replace every occurrence of the argv-derived values in a captured stream. */
|
|
1868
|
+
function scrubStream(s, values) {
|
|
1869
|
+
let out = s;
|
|
1870
|
+
for (const v of values) if (out.includes(v)) out = out.split(v).join("[REDACTED]");
|
|
1871
|
+
return out;
|
|
1872
|
+
}
|
|
1784
1873
|
/**
|
|
1785
1874
|
* Build a diagnosable BUT scrubbed message from an execFile rejection. Node's
|
|
1786
1875
|
* execFile error `message` is "Command failed: <full argv>" — which for
|
|
1787
1876
|
* `config set` embeds the value payload — so we never surface it. We keep the
|
|
1788
|
-
* redacted target, the exit code,
|
|
1877
|
+
* redacted target, the exit code, `stderr`, and `stdout`.
|
|
1878
|
+
*
|
|
1879
|
+
* OpenClaw prints its own error text to STDOUT (not stderr), so on a genuine
|
|
1880
|
+
* failure `stderr` is often empty and the real cause is in stdout — surfacing
|
|
1881
|
+
* only stderr left days of bare "(exit 1)" in the logs. We include a truncated
|
|
1882
|
+
* stdout too — but scrubbed first: OpenClaw's error text can echo the offending
|
|
1883
|
+
* value, so both streams are run through `scrubStream` with the argv values
|
|
1884
|
+
* before anything reaches the dashboard-visible errorMessage.
|
|
1789
1885
|
*/
|
|
1790
1886
|
function configSetErrorMessage(err, args) {
|
|
1791
1887
|
const target = redactConfigSetTarget(args);
|
|
1792
1888
|
if (err instanceof Error) {
|
|
1793
1889
|
const e = err;
|
|
1794
|
-
const
|
|
1890
|
+
const values = collectArgvValues(args);
|
|
1891
|
+
const stderr = typeof e.stderr === "string" ? scrubStream(e.stderr.trim(), values) : "";
|
|
1892
|
+
const stdout = typeof e.stdout === "string" ? scrubStream(e.stdout.trim(), values) : "";
|
|
1795
1893
|
const code = e.code !== void 0 ? ` (exit ${e.code})` : "";
|
|
1796
|
-
|
|
1894
|
+
const details = [stderr, stdout ? `stdout: ${truncateForError(stdout)}` : ""].filter(Boolean).join("; ");
|
|
1895
|
+
return details ? `${target} failed${code}: ${details}` : `${target} failed${code}`;
|
|
1797
1896
|
}
|
|
1798
1897
|
return `${target} failed: ${String(err)}`;
|
|
1799
1898
|
}
|
|
@@ -1919,9 +2018,21 @@ var OpenClawApplier = class {
|
|
|
1919
2018
|
await this.ensurePluginsAllowUnlocked(pkg);
|
|
1920
2019
|
this.cleanupUntrackedExtensionInstall(pkg);
|
|
1921
2020
|
if (opts?.force && this.isPluginInstalled(pkg)) {
|
|
2021
|
+
const pinnedVersion = pluginSpecVersion(spec);
|
|
2022
|
+
const installedVersion = this.installedPluginVersion(pkg);
|
|
2023
|
+
if (pinnedVersion && installedVersion && installedVersion === pinnedVersion) {
|
|
2024
|
+
log$3.info({
|
|
2025
|
+
pkg,
|
|
2026
|
+
spec,
|
|
2027
|
+
version: installedVersion
|
|
2028
|
+
}, "Force mode — installed version already matches pinned spec, skipping uninstall+reinstall");
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
1922
2031
|
log$3.info({
|
|
1923
2032
|
pkg,
|
|
1924
|
-
spec
|
|
2033
|
+
spec,
|
|
2034
|
+
installedVersion,
|
|
2035
|
+
pinnedVersion
|
|
1925
2036
|
}, "Force mode — uninstalling plugin before reinstall");
|
|
1926
2037
|
try {
|
|
1927
2038
|
await this.removePluginUnlocked(pkg);
|
|
@@ -2039,6 +2150,25 @@ var OpenClawApplier = class {
|
|
|
2039
2150
|
return readdirSync(extensionsDir).some((dir) => dir.startsWith(prefix) && /^[0-9a-f]+$/i.test(dir.slice(prefix.length)));
|
|
2040
2151
|
}
|
|
2041
2152
|
/**
|
|
2153
|
+
* Best-effort read of the installed version of a plugin, or `undefined` when
|
|
2154
|
+
* it can't be determined. Reads the version from the installed package's
|
|
2155
|
+
* package.json under the npm registry path OpenClaw 2026.5+ installs to
|
|
2156
|
+
* (`~/.openclaw/npm/node_modules/<pkg>/package.json`) — the same root
|
|
2157
|
+
* `isPluginInstalled` checks. 2026.4 extensions-dir installs have no
|
|
2158
|
+
* stable package.json version here and return `undefined` (callers fail
|
|
2159
|
+
* open to the safe reinstall path).
|
|
2160
|
+
*/
|
|
2161
|
+
installedPluginVersion(pkg) {
|
|
2162
|
+
const pkgJsonPath = join(this.home, "npm", "node_modules", ...pkg.split("/"), "package.json");
|
|
2163
|
+
if (!existsSync(pkgJsonPath)) return void 0;
|
|
2164
|
+
try {
|
|
2165
|
+
const data = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
|
|
2166
|
+
return typeof data.version === "string" ? data.version : void 0;
|
|
2167
|
+
} catch {
|
|
2168
|
+
return;
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
/**
|
|
2042
2172
|
* Remove an extensions/ install that has no matching record in
|
|
2043
2173
|
* plugins/installs.json. OpenClaw 2026.5+ tracks installs via the records
|
|
2044
2174
|
* file — a leftover dir from 2026.4 is invisible to `openclaw plugins
|
|
@@ -2192,10 +2322,10 @@ var OpenClawApplier = class {
|
|
|
2192
2322
|
"--batch-json",
|
|
2193
2323
|
JSON.stringify(leaves),
|
|
2194
2324
|
"--replace"
|
|
2195
|
-
]);
|
|
2325
|
+
], { timeout: Math.min(Math.max(3e4, leaves.length * 2e3), 12e4) });
|
|
2196
2326
|
} catch (err) {
|
|
2197
2327
|
if (await this.verifyApplied(async () => {
|
|
2198
|
-
return (await
|
|
2328
|
+
return (await mapWithConcurrency(leaves, VERIFY_GET_CONCURRENCY, (l) => this.configValueMatches(l.path, l.value))).every(Boolean);
|
|
2199
2329
|
})) log$3.warn({ count: leaves.length }, "openclaw config set --batch-json exited non-zero but config landed — continuing");
|
|
2200
2330
|
else {
|
|
2201
2331
|
log$3.error({
|
package/package.json
CHANGED