@alfe.ai/integrations 0.2.8 → 0.2.10

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 CHANGED
@@ -610,6 +610,15 @@ declare class IntegrationManager {
610
610
  clear(): void;
611
611
  private checkHealth;
612
612
  private err;
613
+ /**
614
+ * True when the manifest's `supported_agents` (if declared) intersects the
615
+ * registered runtime appliers. Hooks and MCP registration run once per
616
+ * integration (not per runtime), so they must use this aggregate check —
617
+ * the per-runtime loop's check only protects plugin/skill/config apply.
618
+ * The daemon registers exactly the agent's runtime, so checking the
619
+ * registered appliers' keys is sufficient.
620
+ */
621
+ private manifestSupportsRegisteredRuntime;
613
622
  }
614
623
  //#endregion
615
624
  //#region src/state.d.ts
@@ -699,6 +708,13 @@ interface HookEnvOptions {
699
708
  config?: Record<string, unknown>;
700
709
  /** Secret key-value pairs (in-memory only, injected as env vars) */
701
710
  secrets?: Map<string, unknown>;
711
+ /**
712
+ * Registered runtime name(s) on this agent (e.g. ["openclaw"]). Injected as
713
+ * ALFE_AGENT_RUNTIME so hooks can branch on runtime instead of sniffing for
714
+ * runtime binaries. The daemon registers exactly one runtime today; the
715
+ * value is comma-joined if that ever changes.
716
+ */
717
+ runtimes?: string[];
702
718
  }
703
719
  /**
704
720
  * Build the environment variables for a hook script execution.
@@ -706,6 +722,7 @@ interface HookEnvOptions {
706
722
  * Injects:
707
723
  * - ALFE_INTEGRATION_DIR=~/.alfe/integrations/{name}/
708
724
  * - ALFE_STATE_DIR=~/.alfe/state/{name}/ (creates dir if needed)
725
+ * - ALFE_AGENT_RUNTIME=openclaw (comma-joined registered runtime names)
709
726
  * - ALFE_<NAME_UPPER>_<KEY_UPPER>=value for each config entry
710
727
  * - ALFE_<NAME_UPPER>_<KEY_UPPER>=value for each secret entry
711
728
  */
package/dist/index.js CHANGED
@@ -671,11 +671,12 @@ const STATE_BASE_DIR = join(homedir(), ".alfe", "state");
671
671
  * Injects:
672
672
  * - ALFE_INTEGRATION_DIR=~/.alfe/integrations/{name}/
673
673
  * - ALFE_STATE_DIR=~/.alfe/state/{name}/ (creates dir if needed)
674
+ * - ALFE_AGENT_RUNTIME=openclaw (comma-joined registered runtime names)
674
675
  * - ALFE_<NAME_UPPER>_<KEY_UPPER>=value for each config entry
675
676
  * - ALFE_<NAME_UPPER>_<KEY_UPPER>=value for each secret entry
676
677
  */
677
678
  function buildHookEnv(options, additionalEnv) {
678
- const { integrationName, config, secrets } = options;
679
+ const { integrationName, config, secrets, runtimes } = options;
679
680
  const nameUpper = integrationName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
680
681
  const integrationDir = join(INTEGRATIONS_BASE_DIR, integrationName);
681
682
  const stateDir = join(STATE_BASE_DIR, integrationName);
@@ -688,6 +689,7 @@ function buildHookEnv(options, additionalEnv) {
688
689
  ALFE_INTEGRATION_DIR: integrationDir,
689
690
  ALFE_STATE_DIR: stateDir
690
691
  };
692
+ if (runtimes && runtimes.length > 0) env.ALFE_AGENT_RUNTIME = runtimes.join(",");
691
693
  if (config) {
692
694
  for (const [key, value] of Object.entries(config)) if (value !== void 0 && value !== null) {
693
695
  const envKey = `ALFE_${nameUpper}_${key.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
@@ -1037,17 +1039,20 @@ var IntegrationManager = class {
1037
1039
  const depState = this.state.get(dep);
1038
1040
  if (!depState || depState.status === "error") throw new Error(`Dependency "${dep}" is not installed. Install it first: alfe integration install ${dep}`);
1039
1041
  }
1040
- if (manifest.hooks.pre_install) {
1042
+ const installHooksSupported = this.manifestSupportsRegisteredRuntime(manifest);
1043
+ 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(", ")})`);
1044
+ if (installHooksSupported && manifest.hooks.pre_install) {
1041
1045
  this.log.info(`Running pre_install hook: ${manifest.hooks.pre_install}`);
1042
1046
  const hookResult = await runHook(installPath, manifest.hooks.pre_install);
1043
1047
  if (hookResult.exitCode !== 0) throw new Error(`pre_install hook failed (exit ${String(hookResult.exitCode)}): ${hookResult.stderr || hookResult.stdout}`);
1044
1048
  }
1045
- if (manifest.hooks.post_install) {
1049
+ if (installHooksSupported && manifest.hooks.post_install) {
1046
1050
  this.log.info(`Running post_install hook: ${manifest.hooks.post_install}`);
1047
1051
  const hookResult = await runHookWithContext(installPath, manifest.hooks.post_install, {
1048
1052
  integrationName: name,
1049
1053
  config: config ?? {},
1050
- secrets: this.secrets.get(name)
1054
+ secrets: this.secrets.get(name),
1055
+ runtimes: [...this.runtimeAppliers.keys()]
1051
1056
  });
1052
1057
  if (hookResult.exitCode !== 0) throw new Error(`post_install hook failed (exit ${String(hookResult.exitCode)}): ${hookResult.stderr || hookResult.stdout}`);
1053
1058
  }
@@ -1213,8 +1218,8 @@ var IntegrationManager = class {
1213
1218
  if (appliedPlugins.length > 0 || skills.length > 0 || runtimeConfigApplied) this.lockManager.addEntries(runtimeName, integrationId, manifest.version, appliedPlugins, skills, installPath, { configApplied: runtimeConfigApplied });
1214
1219
  }
1215
1220
  const mcpServers = manifest.mcp_servers ?? [];
1216
- const runtimeSupportsMcp = !supportedAgents || supportedAgents.length === 0 || [...this.runtimeAppliers.keys()].some((r) => supportedAgents.includes(r));
1217
- if (mcpServers.length > 0 && !runtimeSupportsMcp) this.log.warn(`Integration "${integrationId}" declares ${String(mcpServers.length)} mcp_server(s) but no registered runtime (${[...this.runtimeAppliers.keys()].join(", ")}) is in supported_agents (${supportedAgents.join(", ")}) — skipping MCP registration`);
1221
+ const runtimeSupported = this.manifestSupportsRegisteredRuntime(manifest);
1222
+ if (mcpServers.length > 0 && !runtimeSupported) this.log.warn(`Integration "${integrationId}" declares ${String(mcpServers.length)} mcp_server(s) but no registered runtime (${[...this.runtimeAppliers.keys()].join(", ")}) is in supported_agents (${(supportedAgents ?? []).join(", ")}) — skipping MCP registration`);
1218
1223
  else if (mcpServers.length > 0) if (!this.mcpApplier) this.log.warn(`Integration "${integrationId}" declares ${String(mcpServers.length)} mcp_server(s) but no mcpApplier is wired — skipping MCP registration`);
1219
1224
  else {
1220
1225
  const secretEntries = this.secrets.get(integrationId);
@@ -1226,24 +1231,28 @@ var IntegrationManager = class {
1226
1231
  await this.mcpApplier.applyForIntegration(integrationId, mcpServers, mergedConfig, { connectionId: entry.customConnectionId });
1227
1232
  }
1228
1233
  this.state.setStatus(integrationId, "active");
1229
- if (manifest.hooks.post_activate) {
1234
+ if (manifest.hooks.post_activate && !runtimeSupported) this.log.warn(`Integration "${integrationId}" post_activate hook skipped — no registered runtime (${[...this.runtimeAppliers.keys()].join(", ")}) is in supported_agents (${(supportedAgents ?? []).join(", ")})`);
1235
+ else if (manifest.hooks.post_activate) {
1230
1236
  this.log.info(`Running post_activate hook: ${manifest.hooks.post_activate}`);
1231
1237
  const hookResult = await runHookWithContext(installPath, manifest.hooks.post_activate, {
1232
1238
  integrationName: integrationId,
1233
1239
  config: entry.config,
1234
- secrets: this.secrets.get(integrationId)
1240
+ secrets: this.secrets.get(integrationId),
1241
+ runtimes: [...this.runtimeAppliers.keys()]
1235
1242
  });
1236
1243
  if (hookResult.exitCode !== 0) {
1237
1244
  this.state.setStatus(integrationId, "error", `post_activate hook failed: ${hookResult.stderr || hookResult.stdout}`);
1238
1245
  return this.err("POST_ACTIVATE_FAILED", `post_activate hook failed (exit ${String(hookResult.exitCode)}): ${hookResult.stderr || hookResult.stdout}`);
1239
1246
  }
1240
1247
  }
1241
- if (manifest.hooks.health_check) {
1248
+ 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(", ")})`);
1249
+ else if (manifest.hooks.health_check) {
1242
1250
  this.log.info(`Running health check: ${manifest.hooks.health_check}`);
1243
1251
  const hookResult = await runHookWithContext(installPath, manifest.hooks.health_check, {
1244
1252
  integrationName: integrationId,
1245
1253
  config: entry.config,
1246
- secrets: this.secrets.get(integrationId)
1254
+ secrets: this.secrets.get(integrationId),
1255
+ runtimes: [...this.runtimeAppliers.keys()]
1247
1256
  });
1248
1257
  if (hookResult.exitCode !== 0) {
1249
1258
  this.state.setStatus(integrationId, "error", `Health check failed: ${hookResult.stderr || hookResult.stdout}`);
@@ -1386,21 +1395,25 @@ var IntegrationManager = class {
1386
1395
  } catch {
1387
1396
  this.log.warn(`Could not parse manifest for "${name}" — proceeding with basic cleanup`);
1388
1397
  }
1389
- if (manifest?.hooks.pre_uninstall) {
1398
+ const uninstallHooksSupported = manifest ? this.manifestSupportsRegisteredRuntime(manifest) : true;
1399
+ if (manifest && !uninstallHooksSupported && (manifest.hooks.pre_uninstall || manifest.hooks.post_uninstall)) this.log.warn(`Integration "${name}" uninstall hooks skipped — no registered runtime (${[...this.runtimeAppliers.keys()].join(", ")}) is in supported_agents (${(manifest.supported_agents ?? []).join(", ")})`);
1400
+ if (uninstallHooksSupported && manifest?.hooks.pre_uninstall) {
1390
1401
  this.log.info(`Running pre_uninstall hook: ${manifest.hooks.pre_uninstall}`);
1391
1402
  const hookResult = await runHookWithContext(installPath, manifest.hooks.pre_uninstall, {
1392
1403
  integrationName: name,
1393
1404
  config: entry.config,
1394
- secrets: this.secrets.get(name)
1405
+ secrets: this.secrets.get(name),
1406
+ runtimes: [...this.runtimeAppliers.keys()]
1395
1407
  });
1396
1408
  if (hookResult.exitCode !== 0) this.log.warn(`pre_uninstall hook failed (continuing): ${hookResult.stderr}`);
1397
1409
  }
1398
- if (manifest?.hooks.post_uninstall) {
1410
+ if (uninstallHooksSupported && manifest?.hooks.post_uninstall) {
1399
1411
  this.log.info(`Running post_uninstall hook: ${manifest.hooks.post_uninstall}`);
1400
1412
  const hookResult = await runHookWithContext(installPath, manifest.hooks.post_uninstall, {
1401
1413
  integrationName: name,
1402
1414
  config: entry.config,
1403
- secrets: this.secrets.get(name)
1415
+ secrets: this.secrets.get(name),
1416
+ runtimes: [...this.runtimeAppliers.keys()]
1404
1417
  });
1405
1418
  if (hookResult.exitCode !== 0) this.log.warn(`post_uninstall hook failed (non-fatal): ${hookResult.stderr}`);
1406
1419
  }
@@ -1597,10 +1610,19 @@ var IntegrationManager = class {
1597
1610
  version: manifest.version,
1598
1611
  message: "No health check hook defined"
1599
1612
  };
1613
+ if (!this.manifestSupportsRegisteredRuntime(manifest)) return {
1614
+ id,
1615
+ name: manifest.name,
1616
+ healthy: entry.status === "active" || entry.status === "configured" || entry.status === "installed",
1617
+ status: entry.status,
1618
+ version: manifest.version,
1619
+ message: "Health check hook skipped — integration does not support this agent runtime"
1620
+ };
1600
1621
  const hookResult = await runHookWithContext(installPath, manifest.hooks.health_check, {
1601
1622
  integrationName: id,
1602
1623
  config: entry.config,
1603
- secrets: this.secrets.get(id)
1624
+ secrets: this.secrets.get(id),
1625
+ runtimes: [...this.runtimeAppliers.keys()]
1604
1626
  });
1605
1627
  return {
1606
1628
  id,
@@ -1631,6 +1653,18 @@ var IntegrationManager = class {
1631
1653
  }
1632
1654
  };
1633
1655
  }
1656
+ /**
1657
+ * True when the manifest's `supported_agents` (if declared) intersects the
1658
+ * registered runtime appliers. Hooks and MCP registration run once per
1659
+ * integration (not per runtime), so they must use this aggregate check —
1660
+ * the per-runtime loop's check only protects plugin/skill/config apply.
1661
+ * The daemon registers exactly the agent's runtime, so checking the
1662
+ * registered appliers' keys is sufficient.
1663
+ */
1664
+ manifestSupportsRegisteredRuntime(manifest) {
1665
+ const supported = manifest.supported_agents;
1666
+ return !supported || supported.length === 0 || [...this.runtimeAppliers.keys()].some((r) => supported.includes(r));
1667
+ }
1634
1668
  };
1635
1669
  //#endregion
1636
1670
  //#region src/openclaw-cli-lock.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/integrations",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "description": "Integration lifecycle management for Alfe — registry, resolution, installation, and state",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -15,7 +15,7 @@
15
15
  "@auriclabs/logger": "^0.1.1",
16
16
  "yaml": ">=2.8.3",
17
17
  "@alfe.ai/integration-manifest": "^0.3.1",
18
- "@alfe.ai/mcp-bundler": "^0.2.2"
18
+ "@alfe.ai/mcp-bundler": "^0.3.0"
19
19
  },
20
20
  "files": [
21
21
  "dist"