@alfe.ai/integrations 0.3.1 → 0.3.2
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 +20 -22
- package/dist/index.js +54 -22
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -697,6 +697,13 @@ declare class IntegrationManager {
|
|
|
697
697
|
clear(): void;
|
|
698
698
|
private checkHealth;
|
|
699
699
|
private err;
|
|
700
|
+
/**
|
|
701
|
+
* Build a hook-failure message. A hook we SIGKILLed at its timeout is rendered
|
|
702
|
+
* as `(timed out after <ms>ms)` so it is visually distinguishable in Sentry
|
|
703
|
+
* from a genuine non-zero exit `(exit <code>)` — the two have very different
|
|
704
|
+
* root causes (Sentry AGENT-DAEMON-6).
|
|
705
|
+
*/
|
|
706
|
+
private hookFailureMessage;
|
|
700
707
|
/**
|
|
701
708
|
* Compute the plugin (bare package) names and skill names still claimed by
|
|
702
709
|
* SOME integration in the CURRENT lock state, keyed by runtime. Both
|
|
@@ -786,31 +793,16 @@ declare class StateManager {
|
|
|
786
793
|
}
|
|
787
794
|
//#endregion
|
|
788
795
|
//#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
796
|
interface HookResult {
|
|
810
797
|
exitCode: number;
|
|
811
798
|
stdout: string;
|
|
812
799
|
stderr: string;
|
|
813
800
|
timedOut: boolean;
|
|
801
|
+
/**
|
|
802
|
+
* When `timedOut` is true, the timeout ceiling (ms) that fired the SIGKILL.
|
|
803
|
+
* Lets callers render a timeout distinctly from a genuine non-zero exit.
|
|
804
|
+
*/
|
|
805
|
+
timedOutAfterMs?: number;
|
|
814
806
|
}
|
|
815
807
|
interface HookEnvOptions {
|
|
816
808
|
/** Integration name (e.g. "voice") */
|
|
@@ -857,9 +849,12 @@ declare function buildHookEnv(options: HookEnvOptions, additionalEnv?: Record<st
|
|
|
857
849
|
* @param integrationPath - Base path of the integration (where alfe-integration.yaml lives)
|
|
858
850
|
* @param hookScript - Relative path to the hook script (e.g. "scripts/activate.sh")
|
|
859
851
|
* @param env - Additional environment variables to pass to the script
|
|
852
|
+
* @param timeoutMs - Timeout before the hook is SIGKILLed. Defaults to the fast
|
|
853
|
+
* lifecycle ceiling ({@link HOOK_TIMEOUT_MS}); install-phase callers pass
|
|
854
|
+
* {@link INSTALL_HOOK_TIMEOUT_MS}.
|
|
860
855
|
* @returns Hook execution result
|
|
861
856
|
*/
|
|
862
|
-
declare function runHook(integrationPath: string, hookScript: string, env?: Record<string, string
|
|
857
|
+
declare function runHook(integrationPath: string, hookScript: string, env?: Record<string, string>, timeoutMs?: number): Promise<HookResult>;
|
|
863
858
|
/**
|
|
864
859
|
* Run a hook script with full integration context (config + secrets as env vars).
|
|
865
860
|
*
|
|
@@ -870,9 +865,12 @@ declare function runHook(integrationPath: string, hookScript: string, env?: Reco
|
|
|
870
865
|
* @param integrationPath - Base path of the integration
|
|
871
866
|
* @param hookScript - Relative path to the hook script
|
|
872
867
|
* @param options - Integration name, config, and secrets
|
|
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}.
|
|
873
871
|
* @returns Hook execution result
|
|
874
872
|
*/
|
|
875
|
-
declare function runHookWithContext(integrationPath: string, hookScript: string, options: HookEnvOptions): Promise<HookResult>;
|
|
873
|
+
declare function runHookWithContext(integrationPath: string, hookScript: string, options: HookEnvOptions, timeoutMs?: number): Promise<HookResult>;
|
|
876
874
|
//#endregion
|
|
877
875
|
//#region src/openclaw-cli-lock.d.ts
|
|
878
876
|
/**
|
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
|
package/package.json
CHANGED