@alfe.ai/integrations 0.6.4 → 0.6.5

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
@@ -450,6 +450,8 @@ declare class McpApplier {
450
450
  */
451
451
  applyForIntegration(integrationId: string, servers: McpServerDeclaration[], mergedConfig: Record<string, unknown>, opts?: {
452
452
  connectionId?: string;
453
+ connectionAuthorityVersion?: string;
454
+ strictReconcile?: boolean;
453
455
  }): Promise<string[]>;
454
456
  /**
455
457
  * Drop every entry owned by this integration. Store writes are
@@ -562,6 +564,8 @@ interface IntegrationInfo {
562
564
  version?: string;
563
565
  status: IntegrationStatus;
564
566
  installedAt?: string;
567
+ connectionAuthorityVersion?: string;
568
+ connectionAuthorityPendingVersion?: string;
565
569
  config?: Record<string, unknown>;
566
570
  error?: string;
567
571
  }
@@ -620,7 +624,12 @@ declare class IntegrationManager {
620
624
  */
621
625
  activate(integrationId: string, opts?: {
622
626
  forcePlugins?: boolean;
627
+ refresh?: boolean;
623
628
  }): Promise<ManagerResponse>;
629
+ /** Refresh only the consumers whose manifest depends on changed account authority. */
630
+ refreshConnections(integrationId: string, connectionAuthorityVersion: string, opts?: {
631
+ onBeforeRuntimeMutation?: () => Promise<void>;
632
+ }): Promise<void>;
624
633
  /**
625
634
  * Deactivate a running integration.
626
635
  *
@@ -1836,7 +1845,13 @@ interface IIntegrationManager {
1836
1845
  version: string;
1837
1846
  status: string;
1838
1847
  installedAt?: string;
1848
+ connectionAuthorityVersion?: string;
1849
+ connectionAuthorityPendingVersion?: string;
1839
1850
  }[]>;
1851
+ /** Reconcile account-bearing MCP processes after their effective account set changes. */
1852
+ refreshConnections(integrationId: string, connectionAuthorityVersion: string, opts?: {
1853
+ onBeforeRuntimeMutation?: () => Promise<void>;
1854
+ }): Promise<void>;
1840
1855
  /** Install an integration at a specific version with config */
1841
1856
  install(integrationId: string, version: string, config: unknown, customSource?: CustomInstallSource): Promise<void>;
1842
1857
  /** Activate an installed integration. `opts.forcePlugins` forces version-drift reinstall of already-installed plugins. */
@@ -1877,12 +1892,10 @@ interface IIntegrationManager {
1877
1892
  declare class IntegrationManagerAdapter implements IIntegrationManager {
1878
1893
  private readonly manager;
1879
1894
  constructor(manager: IntegrationManager);
1880
- getInstalledIntegrations(): Promise<{
1881
- id: string;
1882
- version: string;
1883
- status: string;
1884
- installedAt?: string;
1885
- }[]>;
1895
+ getInstalledIntegrations(): ReturnType<IIntegrationManager['getInstalledIntegrations']>;
1896
+ refreshConnections(integrationId: string, connectionAuthorityVersion: string, opts?: {
1897
+ onBeforeRuntimeMutation?: () => Promise<void>;
1898
+ }): Promise<void>;
1886
1899
  install(integrationId: string, version: string, config: unknown, customSource?: CustomInstallSource): Promise<void>;
1887
1900
  activate(integrationId: string, opts?: {
1888
1901
  forcePlugins?: boolean;
package/dist/index.js CHANGED
@@ -907,7 +907,7 @@ function validateStateFile(value) {
907
907
  const integrations = Object.create(null);
908
908
  for (const [id, rawEntry] of Object.entries(value.integrations)) {
909
909
  assertSafePathSegment(id, "Persisted integration id");
910
- if (!isRecord$1(rawEntry) || typeof rawEntry.status !== "string" || !VALID_STATUSES.has(rawEntry.status) || typeof rawEntry.version !== "string" || typeof rawEntry.installedAt !== "string" || !isRecord$1(rawEntry.config) || rawEntry.error !== void 0 && typeof rawEntry.error !== "string" || rawEntry.reinstallAttempts !== void 0 && (typeof rawEntry.reinstallAttempts !== "number" || !Number.isSafeInteger(rawEntry.reinstallAttempts) || rawEntry.reinstallAttempts < 0) || rawEntry.customConnectionId !== void 0 && typeof rawEntry.customConnectionId !== "string") throw new Error(`Integration state entry "${id}" has an invalid shape`);
910
+ if (!isRecord$1(rawEntry) || typeof rawEntry.status !== "string" || !VALID_STATUSES.has(rawEntry.status) || typeof rawEntry.version !== "string" || typeof rawEntry.installedAt !== "string" || !isRecord$1(rawEntry.config) || rawEntry.error !== void 0 && typeof rawEntry.error !== "string" || rawEntry.reinstallAttempts !== void 0 && (typeof rawEntry.reinstallAttempts !== "number" || !Number.isSafeInteger(rawEntry.reinstallAttempts) || rawEntry.reinstallAttempts < 0) || rawEntry.customConnectionId !== void 0 && typeof rawEntry.customConnectionId !== "string" || rawEntry.connectionAuthorityVersion !== void 0 && (typeof rawEntry.connectionAuthorityVersion !== "string" || !/^[a-f0-9]{64}$/.test(rawEntry.connectionAuthorityVersion)) || rawEntry.connectionAuthorityPendingVersion !== void 0 && (typeof rawEntry.connectionAuthorityPendingVersion !== "string" || !/^[a-f0-9]{64}$/.test(rawEntry.connectionAuthorityPendingVersion))) throw new Error(`Integration state entry "${id}" has an invalid shape`);
911
911
  integrations[id] = rawEntry;
912
912
  }
913
913
  return {
@@ -1697,7 +1697,7 @@ var IntegrationManager = class {
1697
1697
  async activate(integrationId, opts) {
1698
1698
  const entry = this.state.get(integrationId);
1699
1699
  if (!entry) return this.err("NOT_FOUND", `Integration "${integrationId}" is not installed`);
1700
- if (entry.status === "active") return {
1700
+ if (entry.status === "active" && !opts?.refresh) return {
1701
1701
  ok: true,
1702
1702
  payload: {
1703
1703
  name: integrationId,
@@ -1705,7 +1705,7 @@ var IntegrationManager = class {
1705
1705
  message: "Already active"
1706
1706
  }
1707
1707
  };
1708
- if (entry.status !== "configured" && entry.status !== "installed" && entry.status !== "error") return this.err("INVALID_STATE", `Cannot activate integration in "${entry.status}" state`);
1708
+ if (entry.status !== "configured" && entry.status !== "installed" && entry.status !== "error" && !(entry.status === "active" && opts?.refresh)) return this.err("INVALID_STATE", `Cannot activate integration in "${entry.status}" state`);
1709
1709
  this.log.info(`Activating integration: ${integrationId}`);
1710
1710
  try {
1711
1711
  const installPath = this.installer.getInstallPath(integrationId);
@@ -1844,6 +1844,41 @@ var IntegrationManager = class {
1844
1844
  return this.err("ACTIVATE_FAILED", message);
1845
1845
  }
1846
1846
  }
1847
+ /** Refresh only the consumers whose manifest depends on changed account authority. */
1848
+ async refreshConnections(integrationId, connectionAuthorityVersion, opts) {
1849
+ if (!/^[a-f0-9]{64}$/.test(connectionAuthorityVersion)) throw new Error("Invalid connection authority version");
1850
+ const entry = this.state.get(integrationId);
1851
+ if (!entry || entry.status !== "active" && !(entry.status === "error" && entry.connectionAuthorityPendingVersion)) throw new Error("Connection refresh requires an active integration or a pending authority retry");
1852
+ if (entry.connectionAuthorityVersion === connectionAuthorityVersion && !entry.connectionAuthorityPendingVersion) return;
1853
+ const manifest = parseManifestFile(join(this.installer.getInstallPath(integrationId), "alfe-integration.yaml"));
1854
+ this.state.update(integrationId, { connectionAuthorityPendingVersion: connectionAuthorityVersion });
1855
+ const supported = this.manifestSupportsRegisteredRuntime(manifest);
1856
+ if (supported && (Boolean(manifest.hooks.post_activate) || [...this.runtimeAppliers.keys()].some((runtime) => {
1857
+ if (manifest.supported_agents?.length && !manifest.supported_agents.includes(runtime)) return false;
1858
+ const installs = resolveInstallsForRuntime(manifest, runtime);
1859
+ return installs.plugins.length > 0 || installs.skills.length > 0 || Object.keys(installs.config ?? {}).length > 0;
1860
+ }))) {
1861
+ await opts?.onBeforeRuntimeMutation?.();
1862
+ const refreshed = await this.activate(integrationId, { refresh: true });
1863
+ if (!refreshed.ok) throw new Error(refreshed.error?.message ?? "Connection consumer refresh failed");
1864
+ }
1865
+ if (supported && manifest.mcp_servers.length > 0) {
1866
+ if (!this.mcpApplier) throw new Error("Connection refresh requires an MCP applier");
1867
+ const secretEntries = this.secrets.get(integrationId);
1868
+ if ((await this.mcpApplier.applyForIntegration(integrationId, manifest.mcp_servers, {
1869
+ ...entry.config,
1870
+ ...secretEntries ? Object.fromEntries(secretEntries) : {}
1871
+ }, {
1872
+ connectionId: entry.customConnectionId,
1873
+ connectionAuthorityVersion,
1874
+ strictReconcile: true
1875
+ })).length !== manifest.mcp_servers.length) throw new Error("Connection refresh could not resolve every MCP credential");
1876
+ }
1877
+ this.state.update(integrationId, {
1878
+ connectionAuthorityVersion,
1879
+ connectionAuthorityPendingVersion: void 0
1880
+ });
1881
+ }
1847
1882
  /**
1848
1883
  * Deactivate a running integration.
1849
1884
  *
@@ -1982,7 +2017,7 @@ var IntegrationManager = class {
1982
2017
  secrets: this.secrets.get(name),
1983
2018
  runtimes: [...this.runtimeAppliers.keys()]
1984
2019
  }, INSTALL_HOOK_TIMEOUT_MS);
1985
- if (hookResult.exitCode !== 0) this.log.warn(this.hookFailureMessage("pre_uninstall hook failed (continuing)", hookResult));
2020
+ if (hookResult.exitCode !== 0) throw new Error(this.hookFailureMessage("pre_uninstall hook failed", hookResult));
1986
2021
  }
1987
2022
  if (uninstallHooksSupported && manifest?.hooks.post_uninstall) {
1988
2023
  this.log.info(`Running post_uninstall hook: ${manifest.hooks.post_uninstall}`);
@@ -1992,7 +2027,7 @@ var IntegrationManager = class {
1992
2027
  secrets: this.secrets.get(name),
1993
2028
  runtimes: [...this.runtimeAppliers.keys()]
1994
2029
  }, INSTALL_HOOK_TIMEOUT_MS);
1995
- if (hookResult.exitCode !== 0) this.log.warn(this.hookFailureMessage("post_uninstall hook failed (non-fatal)", hookResult));
2030
+ if (hookResult.exitCode !== 0) throw new Error(this.hookFailureMessage("post_uninstall hook failed", hookResult));
1996
2031
  }
1997
2032
  if (this.installer.isInstalled(name)) {
1998
2033
  await this.installer.remove(name);
@@ -2238,6 +2273,8 @@ var IntegrationManager = class {
2238
2273
  version: entry.version,
2239
2274
  status: entry.status,
2240
2275
  installedAt: entry.installedAt,
2276
+ connectionAuthorityVersion: entry.connectionAuthorityVersion,
2277
+ connectionAuthorityPendingVersion: entry.connectionAuthorityPendingVersion,
2241
2278
  config: entry.config,
2242
2279
  error: entry.error
2243
2280
  }));
@@ -2278,6 +2315,8 @@ var IntegrationManager = class {
2278
2315
  version: entry.version,
2279
2316
  status: entry.status,
2280
2317
  installedAt: entry.installedAt,
2318
+ connectionAuthorityVersion: entry.connectionAuthorityVersion,
2319
+ connectionAuthorityPendingVersion: entry.connectionAuthorityPendingVersion,
2281
2320
  config: entry.config,
2282
2321
  error: entry.error
2283
2322
  };
@@ -4771,7 +4810,10 @@ var McpApplier = class {
4771
4810
  const id = `${integrationId}-${server.id}`;
4772
4811
  const envResolved = await this.resolveEnv(server, mergedConfig, opts?.connectionId);
4773
4812
  if (envResolved == null) {
4774
- await this.manager.removeServer(id, { expectedOwner: owner });
4813
+ await this.manager.removeServer(id, {
4814
+ expectedOwner: owner,
4815
+ ...opts?.strictReconcile ? { strictReconcile: true } : {}
4816
+ });
4775
4817
  log.info({
4776
4818
  integrationId,
4777
4819
  server: server.id,
@@ -4779,13 +4821,20 @@ var McpApplier = class {
4779
4821
  }, "Skipping MCP server registration — required credentials missing or fetch failed");
4780
4822
  continue;
4781
4823
  }
4782
- await this.manager.addServer(toBundlerConfig(server, envResolved), {
4824
+ await this.manager.addServer(toBundlerConfig(server, {
4825
+ ...envResolved,
4826
+ ...opts?.connectionAuthorityVersion ? { ALFE_CONNECTION_AUTHORITY_VERSION: opts.connectionAuthorityVersion } : {}
4827
+ }), {
4783
4828
  id,
4784
- owner
4829
+ owner,
4830
+ ...opts?.strictReconcile ? { strictReconcile: true } : {}
4785
4831
  });
4786
4832
  applied.push(id);
4787
4833
  }
4788
- for (const { id, entry } of this.manager.listServers()) if (entry.owner === owner && !declaredIds.has(id)) await this.manager.removeServer(id, { expectedOwner: owner });
4834
+ for (const { id, entry } of this.manager.listServers()) if (entry.owner === owner && !declaredIds.has(id)) await this.manager.removeServer(id, {
4835
+ expectedOwner: owner,
4836
+ ...opts?.strictReconcile ? { strictReconcile: true } : {}
4837
+ });
4789
4838
  for (const id of applied) this.manager.warmServer(id, MCP_ACTIVATION_WARM_TIMEOUT_MS).then((status) => {
4790
4839
  if (!status) return;
4791
4840
  if (status.connected) log.info({
@@ -4919,9 +4968,14 @@ var IntegrationManagerAdapter = class {
4919
4968
  id: i.id,
4920
4969
  version: i.version ?? "unknown",
4921
4970
  status: i.status,
4922
- installedAt: i.installedAt
4971
+ installedAt: i.installedAt,
4972
+ connectionAuthorityVersion: i.connectionAuthorityVersion,
4973
+ connectionAuthorityPendingVersion: i.connectionAuthorityPendingVersion
4923
4974
  })));
4924
4975
  }
4976
+ async refreshConnections(integrationId, connectionAuthorityVersion, opts) {
4977
+ await this.manager.refreshConnections(integrationId, connectionAuthorityVersion, opts);
4978
+ }
4925
4979
  async install(integrationId, version, config, customSource) {
4926
4980
  const result = await this.manager.install({
4927
4981
  name: integrationId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/integrations",
3
- "version": "0.6.4",
3
+ "version": "0.6.5",
4
4
  "description": "Integration lifecycle management for Alfe — registry, resolution, installation, and state",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -14,8 +14,8 @@
14
14
  "dependencies": {
15
15
  "@auriclabs/logger": "^0.1.1",
16
16
  "yaml": ">=2.8.3",
17
- "@alfe.ai/integration-manifest": "^0.4.0",
18
- "@alfe.ai/mcp-bundler": "^0.4.1"
17
+ "@alfe.ai/integration-manifest": "^0.4.1",
18
+ "@alfe.ai/mcp-bundler": "^0.4.2"
19
19
  },
20
20
  "files": [
21
21
  "dist"