@alfe.ai/integrations 0.3.1 → 0.4.0
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 +59 -22
- package/dist/index.js +128 -22
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -359,6 +359,22 @@ interface RuntimeApplier {
|
|
|
359
359
|
* its absence and degrade gracefully.
|
|
360
360
|
*/
|
|
361
361
|
setConfigRaw?(key: string, value: string): Promise<void>;
|
|
362
|
+
/**
|
|
363
|
+
* Optional: read a single raw config key back from the runtime, returning the
|
|
364
|
+
* scalar value as a string (or `undefined` when the key is unset / can't be
|
|
365
|
+
* read). Used by the daemon's config-reconcile pass (plan A4) to value-diff
|
|
366
|
+
* before writing (skip when equal) and to verify a write landed.
|
|
367
|
+
*
|
|
368
|
+
* MUST normalize the runtime's `config get` output to the same scalar string
|
|
369
|
+
* that `setConfigRaw` was called with (e.g. unwrap the JSON quoting OpenClaw's
|
|
370
|
+
* `config get` adds), so a `getConfigRaw(k) === want` compare is exact.
|
|
371
|
+
*
|
|
372
|
+
* Read-back can be a false negative under load (a 2-vCPU box running a
|
|
373
|
+
* `config get` while the runtime hot-reloads) — callers MUST retry with the
|
|
374
|
+
* same cadence as a write verify, never one-shot. Appliers that can't read
|
|
375
|
+
* config omit this method; callers degrade gracefully (blind set, no diff).
|
|
376
|
+
*/
|
|
377
|
+
getConfigRaw?(key: string): Promise<string | undefined>;
|
|
362
378
|
/** Check if this runtime is available (e.g. workspace directory exists) */
|
|
363
379
|
isAvailable(): Promise<boolean>;
|
|
364
380
|
}
|
|
@@ -697,6 +713,13 @@ declare class IntegrationManager {
|
|
|
697
713
|
clear(): void;
|
|
698
714
|
private checkHealth;
|
|
699
715
|
private err;
|
|
716
|
+
/**
|
|
717
|
+
* Build a hook-failure message. A hook we SIGKILLed at its timeout is rendered
|
|
718
|
+
* as `(timed out after <ms>ms)` so it is visually distinguishable in Sentry
|
|
719
|
+
* from a genuine non-zero exit `(exit <code>)` — the two have very different
|
|
720
|
+
* root causes (Sentry AGENT-DAEMON-6).
|
|
721
|
+
*/
|
|
722
|
+
private hookFailureMessage;
|
|
700
723
|
/**
|
|
701
724
|
* Compute the plugin (bare package) names and skill names still claimed by
|
|
702
725
|
* SOME integration in the CURRENT lock state, keyed by runtime. Both
|
|
@@ -786,31 +809,16 @@ declare class StateManager {
|
|
|
786
809
|
}
|
|
787
810
|
//#endregion
|
|
788
811
|
//#region src/hooks.d.ts
|
|
789
|
-
/**
|
|
790
|
-
* Hook Runner — executes integration lifecycle hook scripts.
|
|
791
|
-
*
|
|
792
|
-
* Hooks are scripts defined in the integration manifest. The runner
|
|
793
|
-
* auto-detects the interpreter from the script's shebang line or file
|
|
794
|
-
* extension (.js/.mjs → node, .py → python3, default → bash).
|
|
795
|
-
*
|
|
796
|
-
* Scripts run as child processes with a 30-second timeout.
|
|
797
|
-
* stdout/stderr are captured and returned.
|
|
798
|
-
*
|
|
799
|
-
* The runner injects standard environment variables:
|
|
800
|
-
* - ALFE_INTEGRATION_DIR — path to the integration install directory
|
|
801
|
-
* - ALFE_STATE_DIR — path to the integration state directory (created if needed)
|
|
802
|
-
* - ALFE_<NAME>_<KEY> — config values (non-secret AND secret, both injected as env vars)
|
|
803
|
-
*
|
|
804
|
-
* Secrets are acceptable as env vars because:
|
|
805
|
-
* - Child process env is ephemeral (dies with the process)
|
|
806
|
-
* - Same security model as Docker secrets / systemd credentials
|
|
807
|
-
* - Never written to disk
|
|
808
|
-
*/
|
|
809
812
|
interface HookResult {
|
|
810
813
|
exitCode: number;
|
|
811
814
|
stdout: string;
|
|
812
815
|
stderr: string;
|
|
813
816
|
timedOut: boolean;
|
|
817
|
+
/**
|
|
818
|
+
* When `timedOut` is true, the timeout ceiling (ms) that fired the SIGKILL.
|
|
819
|
+
* Lets callers render a timeout distinctly from a genuine non-zero exit.
|
|
820
|
+
*/
|
|
821
|
+
timedOutAfterMs?: number;
|
|
814
822
|
}
|
|
815
823
|
interface HookEnvOptions {
|
|
816
824
|
/** Integration name (e.g. "voice") */
|
|
@@ -857,9 +865,12 @@ declare function buildHookEnv(options: HookEnvOptions, additionalEnv?: Record<st
|
|
|
857
865
|
* @param integrationPath - Base path of the integration (where alfe-integration.yaml lives)
|
|
858
866
|
* @param hookScript - Relative path to the hook script (e.g. "scripts/activate.sh")
|
|
859
867
|
* @param env - Additional environment variables to pass to the script
|
|
868
|
+
* @param timeoutMs - Timeout before the hook is SIGKILLed. Defaults to the fast
|
|
869
|
+
* lifecycle ceiling ({@link HOOK_TIMEOUT_MS}); install-phase callers pass
|
|
870
|
+
* {@link INSTALL_HOOK_TIMEOUT_MS}.
|
|
860
871
|
* @returns Hook execution result
|
|
861
872
|
*/
|
|
862
|
-
declare function runHook(integrationPath: string, hookScript: string, env?: Record<string, string
|
|
873
|
+
declare function runHook(integrationPath: string, hookScript: string, env?: Record<string, string>, timeoutMs?: number): Promise<HookResult>;
|
|
863
874
|
/**
|
|
864
875
|
* Run a hook script with full integration context (config + secrets as env vars).
|
|
865
876
|
*
|
|
@@ -870,9 +881,12 @@ declare function runHook(integrationPath: string, hookScript: string, env?: Reco
|
|
|
870
881
|
* @param integrationPath - Base path of the integration
|
|
871
882
|
* @param hookScript - Relative path to the hook script
|
|
872
883
|
* @param options - Integration name, config, and secrets
|
|
884
|
+
* @param timeoutMs - Timeout before the hook is SIGKILLed. Defaults to the fast
|
|
885
|
+
* lifecycle ceiling ({@link HOOK_TIMEOUT_MS}); install-phase callers pass
|
|
886
|
+
* {@link INSTALL_HOOK_TIMEOUT_MS}.
|
|
873
887
|
* @returns Hook execution result
|
|
874
888
|
*/
|
|
875
|
-
declare function runHookWithContext(integrationPath: string, hookScript: string, options: HookEnvOptions): Promise<HookResult>;
|
|
889
|
+
declare function runHookWithContext(integrationPath: string, hookScript: string, options: HookEnvOptions, timeoutMs?: number): Promise<HookResult>;
|
|
876
890
|
//#endregion
|
|
877
891
|
//#region src/openclaw-cli-lock.d.ts
|
|
878
892
|
/**
|
|
@@ -1220,6 +1234,21 @@ declare class OpenClawApplier implements RuntimeApplier {
|
|
|
1220
1234
|
* config writes trigger.
|
|
1221
1235
|
*/
|
|
1222
1236
|
setConfigRaw(key: string, value: string): Promise<void>;
|
|
1237
|
+
/**
|
|
1238
|
+
* Read a single config key back — `openclaw config get <key>` — and normalize
|
|
1239
|
+
* to the scalar string that `setConfigRaw` would have written. `config get`
|
|
1240
|
+
* emits JSON, so a scalar comes back quoted (`"claude-sonnet-4-6"`); parse it
|
|
1241
|
+
* to the raw value so a `=== want` compare in the daemon's config-reconcile
|
|
1242
|
+
* pass is exact.
|
|
1243
|
+
*
|
|
1244
|
+
* Returns `undefined` when the key is unset, the command fails, or the runtime
|
|
1245
|
+
* REDACTS the value (a sensitive key we can't compare — treat as "unknown" so
|
|
1246
|
+
* the caller doesn't spuriously diff). Under the shared CLI lock so it can't
|
|
1247
|
+
* interleave with a concurrent mutation. NOT self-healing on a malformed state
|
|
1248
|
+
* DB — a read failure returns `undefined`, and the caller's set path (which IS
|
|
1249
|
+
* healing) recovers.
|
|
1250
|
+
*/
|
|
1251
|
+
getConfigRaw(key: string): Promise<string | undefined>;
|
|
1223
1252
|
isAvailable(): Promise<boolean>;
|
|
1224
1253
|
private readTracking;
|
|
1225
1254
|
private writeTracking;
|
|
@@ -1281,6 +1310,14 @@ declare class HermesApplier implements RuntimeApplier {
|
|
|
1281
1310
|
* per-integration removal accounting). Reuses the serialized queue.
|
|
1282
1311
|
*/
|
|
1283
1312
|
setConfigRaw(key: string, value: string): Promise<void>;
|
|
1313
|
+
/**
|
|
1314
|
+
* Read a single config key back — `hermes config get <key>` — normalized to
|
|
1315
|
+
* the scalar string `setConfigRaw` would have written (used by the daemon's
|
|
1316
|
+
* config-reconcile value-diff + verify, plan A4). Returns `undefined` when the
|
|
1317
|
+
* key is unset or the command fails. Serialized onto the same config queue so
|
|
1318
|
+
* it can't interleave with an in-flight `hermes config set`.
|
|
1319
|
+
*/
|
|
1320
|
+
getConfigRaw(key: string): Promise<string | undefined>;
|
|
1284
1321
|
/**
|
|
1285
1322
|
* Apply a plugin. The `@alfe.ai/openclaw-*` npm plugins are OpenClaw-specific
|
|
1286
1323
|
* (they carry the `openclaw` peer dep) and do NOT apply to Hermes — log + skip
|
package/dist/index.js
CHANGED
|
@@ -746,7 +746,19 @@ var LockManager = class {
|
|
|
746
746
|
* - Same security model as Docker secrets / systemd credentials
|
|
747
747
|
* - Never written to disk
|
|
748
748
|
*/
|
|
749
|
+
/**
|
|
750
|
+
* Default timeout for lifecycle hooks (activate/health_check/uninstall). These
|
|
751
|
+
* are expected to be fast; a long-running one is a bug and should be killed.
|
|
752
|
+
*/
|
|
749
753
|
const HOOK_TIMEOUT_MS = 3e4;
|
|
754
|
+
/**
|
|
755
|
+
* Timeout for install-phase hooks (pre_install/post_install). These legitimately
|
|
756
|
+
* take minutes — they clone repos, run `npm install`, download binaries — so the
|
|
757
|
+
* fast 30s ceiling would SIGKILL a healthy install mid-flight (Sentry
|
|
758
|
+
* AGENT-DAEMON-6). Callers opt into this longer ceiling explicitly per hook type;
|
|
759
|
+
* it is NOT the global default so lifecycle hooks stay fast.
|
|
760
|
+
*/
|
|
761
|
+
const INSTALL_HOOK_TIMEOUT_MS = 6e5;
|
|
750
762
|
const INTEGRATIONS_BASE_DIR = join(homedir(), ".alfe", "integrations");
|
|
751
763
|
const STATE_BASE_DIR = join(homedir(), ".alfe", "state");
|
|
752
764
|
/**
|
|
@@ -848,9 +860,12 @@ function resolveInterpreter(scriptPath) {
|
|
|
848
860
|
* @param integrationPath - Base path of the integration (where alfe-integration.yaml lives)
|
|
849
861
|
* @param hookScript - Relative path to the hook script (e.g. "scripts/activate.sh")
|
|
850
862
|
* @param env - Additional environment variables to pass to the script
|
|
863
|
+
* @param timeoutMs - Timeout before the hook is SIGKILLed. Defaults to the fast
|
|
864
|
+
* lifecycle ceiling ({@link HOOK_TIMEOUT_MS}); install-phase callers pass
|
|
865
|
+
* {@link INSTALL_HOOK_TIMEOUT_MS}.
|
|
851
866
|
* @returns Hook execution result
|
|
852
867
|
*/
|
|
853
|
-
async function runHook(integrationPath, hookScript, env) {
|
|
868
|
+
async function runHook(integrationPath, hookScript, env, timeoutMs = HOOK_TIMEOUT_MS) {
|
|
854
869
|
const scriptPath = join(integrationPath, hookScript);
|
|
855
870
|
if (!existsSync(scriptPath)) return {
|
|
856
871
|
exitCode: 0,
|
|
@@ -878,7 +893,7 @@ async function runHook(integrationPath, hookScript, env) {
|
|
|
878
893
|
const timer = setTimeout(() => {
|
|
879
894
|
timedOut = true;
|
|
880
895
|
proc.kill("SIGKILL");
|
|
881
|
-
},
|
|
896
|
+
}, timeoutMs);
|
|
882
897
|
proc.stdout.on("data", (data) => {
|
|
883
898
|
stdout += data.toString();
|
|
884
899
|
if (stdout.length > 1e5) stdout = stdout.slice(0, 1e5) + "\n[truncated]";
|
|
@@ -893,7 +908,8 @@ async function runHook(integrationPath, hookScript, env) {
|
|
|
893
908
|
exitCode: code ?? 1,
|
|
894
909
|
stdout: stdout.trim(),
|
|
895
910
|
stderr: stderr.trim(),
|
|
896
|
-
timedOut
|
|
911
|
+
timedOut,
|
|
912
|
+
timedOutAfterMs: timedOut ? timeoutMs : void 0
|
|
897
913
|
});
|
|
898
914
|
});
|
|
899
915
|
proc.on("error", (err) => {
|
|
@@ -917,10 +933,13 @@ async function runHook(integrationPath, hookScript, env) {
|
|
|
917
933
|
* @param integrationPath - Base path of the integration
|
|
918
934
|
* @param hookScript - Relative path to the hook script
|
|
919
935
|
* @param options - Integration name, config, and secrets
|
|
936
|
+
* @param timeoutMs - Timeout before the hook is SIGKILLed. Defaults to the fast
|
|
937
|
+
* lifecycle ceiling ({@link HOOK_TIMEOUT_MS}); install-phase callers pass
|
|
938
|
+
* {@link INSTALL_HOOK_TIMEOUT_MS}.
|
|
920
939
|
* @returns Hook execution result
|
|
921
940
|
*/
|
|
922
|
-
async function runHookWithContext(integrationPath, hookScript, options) {
|
|
923
|
-
return runHook(integrationPath, hookScript, buildHookEnv(options));
|
|
941
|
+
async function runHookWithContext(integrationPath, hookScript, options, timeoutMs = HOOK_TIMEOUT_MS) {
|
|
942
|
+
return runHook(integrationPath, hookScript, buildHookEnv(options), timeoutMs);
|
|
924
943
|
}
|
|
925
944
|
//#endregion
|
|
926
945
|
//#region src/plugin-spec.ts
|
|
@@ -1127,8 +1146,8 @@ var IntegrationManager = class {
|
|
|
1127
1146
|
if (!installHooksSupported && (manifest.hooks.pre_install || manifest.hooks.post_install)) this.log.warn(`Integration "${name}" install hooks skipped — no registered runtime (${[...this.runtimeAppliers.keys()].join(", ")}) is in supported_agents (${(manifest.supported_agents ?? []).join(", ")})`);
|
|
1128
1147
|
if (installHooksSupported && manifest.hooks.pre_install) {
|
|
1129
1148
|
this.log.info(`Running pre_install hook: ${manifest.hooks.pre_install}`);
|
|
1130
|
-
const hookResult = await runHook(installPath, manifest.hooks.pre_install);
|
|
1131
|
-
if (hookResult.exitCode !== 0) throw new Error(
|
|
1149
|
+
const hookResult = await runHook(installPath, manifest.hooks.pre_install, void 0, INSTALL_HOOK_TIMEOUT_MS);
|
|
1150
|
+
if (hookResult.exitCode !== 0) throw new Error(this.hookFailureMessage("pre_install hook failed", hookResult));
|
|
1132
1151
|
}
|
|
1133
1152
|
if (installHooksSupported && manifest.hooks.post_install) {
|
|
1134
1153
|
this.log.info(`Running post_install hook: ${manifest.hooks.post_install}`);
|
|
@@ -1137,8 +1156,8 @@ var IntegrationManager = class {
|
|
|
1137
1156
|
config: config ?? {},
|
|
1138
1157
|
secrets: this.secrets.get(name),
|
|
1139
1158
|
runtimes: [...this.runtimeAppliers.keys()]
|
|
1140
|
-
});
|
|
1141
|
-
if (hookResult.exitCode !== 0) throw new Error(
|
|
1159
|
+
}, INSTALL_HOOK_TIMEOUT_MS);
|
|
1160
|
+
if (hookResult.exitCode !== 0) throw new Error(this.hookFailureMessage("post_install hook failed", hookResult));
|
|
1142
1161
|
}
|
|
1143
1162
|
this.state.set(name, {
|
|
1144
1163
|
status: "installed",
|
|
@@ -1328,8 +1347,9 @@ var IntegrationManager = class {
|
|
|
1328
1347
|
runtimes: [...this.runtimeAppliers.keys()]
|
|
1329
1348
|
});
|
|
1330
1349
|
if (hookResult.exitCode !== 0) {
|
|
1331
|
-
this.
|
|
1332
|
-
|
|
1350
|
+
const message = this.hookFailureMessage("post_activate hook failed", hookResult);
|
|
1351
|
+
this.state.setStatus(integrationId, "error", message);
|
|
1352
|
+
return this.err("POST_ACTIVATE_FAILED", message);
|
|
1333
1353
|
}
|
|
1334
1354
|
}
|
|
1335
1355
|
if (manifest.hooks.health_check && !runtimeSupported) this.log.warn(`Integration "${integrationId}" health_check hook skipped — no registered runtime (${[...this.runtimeAppliers.keys()].join(", ")}) is in supported_agents (${(supportedAgents ?? []).join(", ")})`);
|
|
@@ -1342,8 +1362,9 @@ var IntegrationManager = class {
|
|
|
1342
1362
|
runtimes: [...this.runtimeAppliers.keys()]
|
|
1343
1363
|
});
|
|
1344
1364
|
if (hookResult.exitCode !== 0) {
|
|
1345
|
-
this.
|
|
1346
|
-
|
|
1365
|
+
const message = this.hookFailureMessage("Health check failed", hookResult);
|
|
1366
|
+
this.state.setStatus(integrationId, "error", message);
|
|
1367
|
+
return this.err("HEALTH_CHECK_FAILED", message);
|
|
1347
1368
|
}
|
|
1348
1369
|
}
|
|
1349
1370
|
this.log.info(`Integration "${integrationId}" activated`);
|
|
@@ -1485,8 +1506,8 @@ var IntegrationManager = class {
|
|
|
1485
1506
|
config: entry.config,
|
|
1486
1507
|
secrets: this.secrets.get(name),
|
|
1487
1508
|
runtimes: [...this.runtimeAppliers.keys()]
|
|
1488
|
-
});
|
|
1489
|
-
if (hookResult.exitCode !== 0) this.log.warn(
|
|
1509
|
+
}, INSTALL_HOOK_TIMEOUT_MS);
|
|
1510
|
+
if (hookResult.exitCode !== 0) this.log.warn(this.hookFailureMessage("pre_uninstall hook failed (continuing)", hookResult));
|
|
1490
1511
|
}
|
|
1491
1512
|
if (uninstallHooksSupported && manifest?.hooks.post_uninstall) {
|
|
1492
1513
|
this.log.info(`Running post_uninstall hook: ${manifest.hooks.post_uninstall}`);
|
|
@@ -1495,8 +1516,8 @@ var IntegrationManager = class {
|
|
|
1495
1516
|
config: entry.config,
|
|
1496
1517
|
secrets: this.secrets.get(name),
|
|
1497
1518
|
runtimes: [...this.runtimeAppliers.keys()]
|
|
1498
|
-
});
|
|
1499
|
-
if (hookResult.exitCode !== 0) this.log.warn(
|
|
1519
|
+
}, INSTALL_HOOK_TIMEOUT_MS);
|
|
1520
|
+
if (hookResult.exitCode !== 0) this.log.warn(this.hookFailureMessage("post_uninstall hook failed (non-fatal)", hookResult));
|
|
1500
1521
|
}
|
|
1501
1522
|
if (this.installer.isInstalled(name)) {
|
|
1502
1523
|
await this.installer.remove(name);
|
|
@@ -1647,8 +1668,8 @@ var IntegrationManager = class {
|
|
|
1647
1668
|
if (!installHooksSupported && (newManifest.hooks.pre_install || newManifest.hooks.post_install)) this.log.warn(`Integration "${name}" upgrade install hooks skipped — no registered runtime (${[...this.runtimeAppliers.keys()].join(", ")}) is in supported_agents (${(newManifest.supported_agents ?? []).join(", ")})`);
|
|
1648
1669
|
if (installHooksSupported && newManifest.hooks.pre_install) {
|
|
1649
1670
|
this.log.info(`Running pre_install hook: ${newManifest.hooks.pre_install}`);
|
|
1650
|
-
const hookResult = await runHook(this.installer.getInstallPath(name), newManifest.hooks.pre_install);
|
|
1651
|
-
if (hookResult.exitCode !== 0) throw new Error(
|
|
1671
|
+
const hookResult = await runHook(this.installer.getInstallPath(name), newManifest.hooks.pre_install, void 0, INSTALL_HOOK_TIMEOUT_MS);
|
|
1672
|
+
if (hookResult.exitCode !== 0) throw new Error(this.hookFailureMessage("pre_install hook failed", hookResult));
|
|
1652
1673
|
}
|
|
1653
1674
|
if (installHooksSupported && newManifest.hooks.post_install) {
|
|
1654
1675
|
this.log.info(`Running post_install hook: ${newManifest.hooks.post_install}`);
|
|
@@ -1657,8 +1678,8 @@ var IntegrationManager = class {
|
|
|
1657
1678
|
config: config ?? existing.config,
|
|
1658
1679
|
secrets: this.secrets.get(name),
|
|
1659
1680
|
runtimes: [...this.runtimeAppliers.keys()]
|
|
1660
|
-
});
|
|
1661
|
-
if (hookResult.exitCode !== 0) throw new Error(
|
|
1681
|
+
}, INSTALL_HOOK_TIMEOUT_MS);
|
|
1682
|
+
if (hookResult.exitCode !== 0) throw new Error(this.hookFailureMessage("post_install hook failed", hookResult));
|
|
1662
1683
|
}
|
|
1663
1684
|
await this.applyUpgradeDiffRemovals(name, oldManifest, newManifest);
|
|
1664
1685
|
return await this.activate(name, { forcePlugins: true });
|
|
@@ -1837,7 +1858,7 @@ var IntegrationManager = class {
|
|
|
1837
1858
|
healthy: hookResult.exitCode === 0,
|
|
1838
1859
|
status: entry.status,
|
|
1839
1860
|
version: manifest.version,
|
|
1840
|
-
message: hookResult.exitCode === 0 ? "Healthy" : `Health check failed (exit ${String(hookResult.exitCode)})`,
|
|
1861
|
+
message: hookResult.exitCode === 0 ? "Healthy" : hookResult.timedOut ? `Health check failed (timed out after ${String(hookResult.timedOutAfterMs ?? "?")}ms)` : `Health check failed (exit ${String(hookResult.exitCode)})`,
|
|
1841
1862
|
stdout: hookResult.stdout || void 0,
|
|
1842
1863
|
stderr: hookResult.stderr || void 0
|
|
1843
1864
|
};
|
|
@@ -1861,6 +1882,17 @@ var IntegrationManager = class {
|
|
|
1861
1882
|
};
|
|
1862
1883
|
}
|
|
1863
1884
|
/**
|
|
1885
|
+
* Build a hook-failure message. A hook we SIGKILLed at its timeout is rendered
|
|
1886
|
+
* as `(timed out after <ms>ms)` so it is visually distinguishable in Sentry
|
|
1887
|
+
* from a genuine non-zero exit `(exit <code>)` — the two have very different
|
|
1888
|
+
* root causes (Sentry AGENT-DAEMON-6).
|
|
1889
|
+
*/
|
|
1890
|
+
hookFailureMessage(label, hookResult) {
|
|
1891
|
+
const reason = hookResult.timedOut ? `timed out after ${String(hookResult.timedOutAfterMs ?? "?")}ms` : `exit ${String(hookResult.exitCode)}`;
|
|
1892
|
+
const output = hookResult.stderr || hookResult.stdout;
|
|
1893
|
+
return output ? `${label} (${reason}): ${output}` : `${label} (${reason})`;
|
|
1894
|
+
}
|
|
1895
|
+
/**
|
|
1864
1896
|
* Compute the plugin (bare package) names and skill names still claimed by
|
|
1865
1897
|
* SOME integration in the CURRENT lock state, keyed by runtime. Both
|
|
1866
1898
|
* `deactivate` and `upgrade`'s diff-removal call this AFTER
|
|
@@ -2948,6 +2980,46 @@ var OpenClawApplier = class {
|
|
|
2948
2980
|
setConfigRaw(key, value) {
|
|
2949
2981
|
return this.cliLock.run(() => this.runConfigSetUnlocked([key, value]));
|
|
2950
2982
|
}
|
|
2983
|
+
/**
|
|
2984
|
+
* Read a single config key back — `openclaw config get <key>` — and normalize
|
|
2985
|
+
* to the scalar string that `setConfigRaw` would have written. `config get`
|
|
2986
|
+
* emits JSON, so a scalar comes back quoted (`"claude-sonnet-4-6"`); parse it
|
|
2987
|
+
* to the raw value so a `=== want` compare in the daemon's config-reconcile
|
|
2988
|
+
* pass is exact.
|
|
2989
|
+
*
|
|
2990
|
+
* Returns `undefined` when the key is unset, the command fails, or the runtime
|
|
2991
|
+
* REDACTS the value (a sensitive key we can't compare — treat as "unknown" so
|
|
2992
|
+
* the caller doesn't spuriously diff). Under the shared CLI lock so it can't
|
|
2993
|
+
* interleave with a concurrent mutation. NOT self-healing on a malformed state
|
|
2994
|
+
* DB — a read failure returns `undefined`, and the caller's set path (which IS
|
|
2995
|
+
* healing) recovers.
|
|
2996
|
+
*/
|
|
2997
|
+
getConfigRaw(key) {
|
|
2998
|
+
return this.cliLock.run(async () => {
|
|
2999
|
+
try {
|
|
3000
|
+
const { stdout } = await execFileAsync$1("openclaw", [
|
|
3001
|
+
"config",
|
|
3002
|
+
"get",
|
|
3003
|
+
key
|
|
3004
|
+
], { timeout: 1e4 });
|
|
3005
|
+
const trimmed = stdout.trim();
|
|
3006
|
+
if (!trimmed) return void 0;
|
|
3007
|
+
let parsed;
|
|
3008
|
+
try {
|
|
3009
|
+
parsed = JSON.parse(trimmed);
|
|
3010
|
+
} catch {
|
|
3011
|
+
return trimmed;
|
|
3012
|
+
}
|
|
3013
|
+
if (parsed === null || parsed === void 0) return void 0;
|
|
3014
|
+
if (parsed === OPENCLAW_REDACTED) return void 0;
|
|
3015
|
+
if (typeof parsed === "string") return parsed;
|
|
3016
|
+
if (typeof parsed === "number" || typeof parsed === "boolean") return String(parsed);
|
|
3017
|
+
return JSON.stringify(parsed);
|
|
3018
|
+
} catch {
|
|
3019
|
+
return;
|
|
3020
|
+
}
|
|
3021
|
+
});
|
|
3022
|
+
}
|
|
2951
3023
|
isAvailable() {
|
|
2952
3024
|
return Promise.resolve(existsSync(this.home));
|
|
2953
3025
|
}
|
|
@@ -3153,6 +3225,40 @@ var HermesApplier = class {
|
|
|
3153
3225
|
return this.runConfigSet([key, value]);
|
|
3154
3226
|
}
|
|
3155
3227
|
/**
|
|
3228
|
+
* Read a single config key back — `hermes config get <key>` — normalized to
|
|
3229
|
+
* the scalar string `setConfigRaw` would have written (used by the daemon's
|
|
3230
|
+
* config-reconcile value-diff + verify, plan A4). Returns `undefined` when the
|
|
3231
|
+
* key is unset or the command fails. Serialized onto the same config queue so
|
|
3232
|
+
* it can't interleave with an in-flight `hermes config set`.
|
|
3233
|
+
*/
|
|
3234
|
+
getConfigRaw(key) {
|
|
3235
|
+
const run = async () => {
|
|
3236
|
+
try {
|
|
3237
|
+
const { stdout } = await execFileAsync("hermes", [
|
|
3238
|
+
"config",
|
|
3239
|
+
"get",
|
|
3240
|
+
key
|
|
3241
|
+
], { timeout: 1e4 });
|
|
3242
|
+
const trimmed = stdout.trim();
|
|
3243
|
+
if (!trimmed) return void 0;
|
|
3244
|
+
try {
|
|
3245
|
+
const parsed = JSON.parse(trimmed);
|
|
3246
|
+
if (parsed === null || parsed === void 0) return void 0;
|
|
3247
|
+
if (typeof parsed === "string") return parsed;
|
|
3248
|
+
if (typeof parsed === "number" || typeof parsed === "boolean") return String(parsed);
|
|
3249
|
+
return JSON.stringify(parsed);
|
|
3250
|
+
} catch {
|
|
3251
|
+
return trimmed;
|
|
3252
|
+
}
|
|
3253
|
+
} catch {
|
|
3254
|
+
return;
|
|
3255
|
+
}
|
|
3256
|
+
};
|
|
3257
|
+
const result = this.configSetQueue.then(run, run);
|
|
3258
|
+
this.configSetQueue = result.catch(() => void 0);
|
|
3259
|
+
return result;
|
|
3260
|
+
}
|
|
3261
|
+
/**
|
|
3156
3262
|
* Apply a plugin. The `@alfe.ai/openclaw-*` npm plugins are OpenClaw-specific
|
|
3157
3263
|
* (they carry the `openclaw` peer dep) and do NOT apply to Hermes — log + skip
|
|
3158
3264
|
* them. The manager catches per-plugin, so a skip here is a correct no-op.
|
package/package.json
CHANGED