@alfe.ai/integrations 0.1.4 → 0.1.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
@@ -8,6 +8,14 @@ import { Manager } from "@alfe.ai/mcp-bundler";
8
8
  *
9
9
  * Calls GET /integrations/registry (public, no auth required) and caches the result.
10
10
  * The API URL can be passed explicitly or set via ALFE_API_URL env var.
11
+ *
12
+ * The cache is time-bounded by a TTL (default 60s). Long-running consumers — chiefly
13
+ * the agent daemon, which constructs the Registry once at startup and never restarts —
14
+ * would otherwise be pinned to the registry snapshot taken at process boot, and could
15
+ * never resolve a version published after the daemon came up (the "stale cache" bug).
16
+ * With a TTL, every consumer self-heals within a bounded window. The resolve-for-install
17
+ * path additionally forces a fresh read (see `load({ fresh: true })`) because installing
18
+ * a specific version is a rare, correctness-critical action that must never race the TTL.
11
19
  */
12
20
  interface RegistryEntry {
13
21
  /** Human-readable display name (e.g. 'Alfe Voice') */
@@ -69,26 +77,51 @@ interface RegistryIndex {
69
77
  type RegistryFetcher = () => Promise<(RegistryEntry & {
70
78
  id: string;
71
79
  })[]>;
80
+ /** Default cache TTL — refetch the registry index after this many ms. */
81
+ declare const DEFAULT_REGISTRY_TTL_MS = 60000;
82
+ interface RegistryOptions {
83
+ /**
84
+ * How long (ms) a loaded index is considered fresh before `load()` refetches.
85
+ * Defaults to {@link DEFAULT_REGISTRY_TTL_MS} (60s). A non-positive value
86
+ * disables time-based caching (every `load()` refetches).
87
+ */
88
+ ttlMs?: number;
89
+ }
90
+ /** Options for a single `load()` call. */
91
+ interface LoadOptions {
92
+ /** Bypass the cache and refetch the index even if it's still within TTL. */
93
+ fresh?: boolean;
94
+ }
72
95
  declare class Registry {
73
96
  private index;
97
+ private loadedAt;
74
98
  private fetcher;
99
+ private ttlMs;
75
100
  /**
76
101
  * @param fetcher - Function that fetches the integrations array from the registry API.
77
102
  * Typically backed by api-client's IntegrationsService.getRegistry().
103
+ * @param options - Optional cache configuration (TTL).
78
104
  */
79
- constructor(fetcher: RegistryFetcher);
105
+ constructor(fetcher: RegistryFetcher, options?: RegistryOptions);
106
+ /** True when the cached index is absent or older than the TTL. */
107
+ private isStale;
80
108
  /**
81
- * Load the registry index. Uses cache if already loaded.
109
+ * Load the registry index. Returns the cached index while it's still within
110
+ * TTL; refetches once the cache is stale (or when `{ fresh: true }` is passed).
82
111
  */
83
- load(): Promise<RegistryIndex>;
112
+ load(options?: LoadOptions): Promise<RegistryIndex>;
84
113
  /**
85
- * Force reload the index (bypass cache).
114
+ * Force reload the index (bypass cache). Equivalent to `load({ fresh: true })`.
86
115
  */
87
116
  reload(): Promise<RegistryIndex>;
88
117
  /**
89
118
  * Get a specific integration entry by name.
119
+ *
120
+ * @param fresh - When true, force a fresh fetch before reading (used by the
121
+ * resolve-for-install path so a stale version list never blocks
122
+ * a just-published version).
90
123
  */
91
- get(id: string): Promise<RegistryEntry | undefined>;
124
+ get(id: string, fresh?: boolean): Promise<RegistryEntry | undefined>;
92
125
  /**
93
126
  * List all integrations in the registry.
94
127
  */
@@ -115,6 +148,20 @@ interface ResolvedIntegration {
115
148
  subdir?: string;
116
149
  description: string;
117
150
  }
151
+ /** Options for a single `resolve()` call. */
152
+ interface ResolveOptions {
153
+ /**
154
+ * Bypass the registry cache and read a fresh index before resolving.
155
+ *
156
+ * The install/reconcile path sets this: installing a specific version is a
157
+ * rare, user-triggered, correctness-critical action that must never resolve
158
+ * against a stale cached version list (the cause of the
159
+ * `Version "x" not found … Available versions: …` install failure on a
160
+ * long-running daemon). High-frequency read-only uses (marketplace
161
+ * listing/search) leave it unset and keep the TTL cache.
162
+ */
163
+ fresh?: boolean;
164
+ }
118
165
  declare class RegistryResolveError extends Error {
119
166
  constructor(message: string);
120
167
  }
@@ -126,9 +173,11 @@ declare class Resolver {
126
173
  *
127
174
  * @param name - Integration name (e.g. "discord")
128
175
  * @param version - Specific version (e.g. "1.0.0") or undefined for latest
176
+ * @param options - Pass `{ fresh: true }` to bypass the registry cache
177
+ * (used by the install/reconcile path).
129
178
  * @returns Resolved integration with repo URL and commit hash
130
179
  */
131
- resolve(id: string, version?: string): Promise<ResolvedIntegration>;
180
+ resolve(id: string, version?: string, options?: ResolveOptions): Promise<ResolvedIntegration>;
132
181
  /**
133
182
  * Check if an integration exists in the registry.
134
183
  */
@@ -875,4 +924,4 @@ declare class IntegrationManagerAdapter implements IIntegrationManager {
875
924
  resetReinstallAttempts(integrationId: string): void;
876
925
  }
877
926
  //#endregion
878
- export { type CredentialsResolver, type HookEnvOptions, type HookResult, type IIntegrationManager, type InstalledInfo, Installer, InstallerError, type IntegrationConfigureParams, type IntegrationHealthParams, type IntegrationInfo, type IntegrationInstallParams, IntegrationManager, IntegrationManagerAdapter, type IntegrationManagerOptions, type IntegrationRemoveParams, LockManager, McpApplier, type McpApplierOptions, OpenClawApplier, type OpenClawApplierOptions, type PlatformContext, Registry, type RegistryEntry, type RegistryFetcher, type RegistryIndex, RegistryResolveError, type ResolvedIntegration, Resolver, type RuntimeApplier, type RuntimeDesiredState, type RuntimeLockFile, type RuntimePluginEntry, type RuntimeSkillEntry, StateManager, buildHookEnv, resolveInstallsForRuntime, runHook, runHookWithContext };
927
+ export { type CredentialsResolver, DEFAULT_REGISTRY_TTL_MS, type HookEnvOptions, type HookResult, type IIntegrationManager, type InstalledInfo, Installer, InstallerError, type IntegrationConfigureParams, type IntegrationHealthParams, type IntegrationInfo, type IntegrationInstallParams, IntegrationManager, IntegrationManagerAdapter, type IntegrationManagerOptions, type IntegrationRemoveParams, type LoadOptions, LockManager, McpApplier, type McpApplierOptions, OpenClawApplier, type OpenClawApplierOptions, type PlatformContext, Registry, type RegistryEntry, type RegistryFetcher, type RegistryIndex, type RegistryOptions, RegistryResolveError, type ResolveOptions, type ResolvedIntegration, Resolver, type RuntimeApplier, type RuntimeDesiredState, type RuntimeLockFile, type RuntimePluginEntry, type RuntimeSkillEntry, StateManager, buildHookEnv, resolveInstallsForRuntime, runHook, runHookWithContext };
package/dist/index.js CHANGED
@@ -6,21 +6,34 @@ import { closeSync, copyFileSync, cpSync, existsSync, mkdirSync, mkdtempSync, op
6
6
  import { buildConfigValidationSchema, parseManifestFile } from "@alfe.ai/integration-manifest";
7
7
  import { createLogger } from "@auriclabs/logger";
8
8
  //#region src/registry.ts
9
+ /** Default cache TTL — refetch the registry index after this many ms. */
10
+ const DEFAULT_REGISTRY_TTL_MS = 6e4;
9
11
  var Registry = class {
10
12
  index = null;
13
+ loadedAt = 0;
11
14
  fetcher;
15
+ ttlMs;
12
16
  /**
13
17
  * @param fetcher - Function that fetches the integrations array from the registry API.
14
18
  * Typically backed by api-client's IntegrationsService.getRegistry().
19
+ * @param options - Optional cache configuration (TTL).
15
20
  */
16
- constructor(fetcher) {
21
+ constructor(fetcher, options = {}) {
17
22
  this.fetcher = fetcher;
23
+ this.ttlMs = options.ttlMs ?? 6e4;
24
+ }
25
+ /** True when the cached index is absent or older than the TTL. */
26
+ isStale() {
27
+ if (!this.index) return true;
28
+ if (this.ttlMs <= 0) return true;
29
+ return Date.now() - this.loadedAt >= this.ttlMs;
18
30
  }
19
31
  /**
20
- * Load the registry index. Uses cache if already loaded.
32
+ * Load the registry index. Returns the cached index while it's still within
33
+ * TTL; refetches once the cache is stale (or when `{ fresh: true }` is passed).
21
34
  */
22
- async load() {
23
- if (this.index) return this.index;
35
+ async load(options = {}) {
36
+ if (!options.fresh && this.index && !this.isStale()) return this.index;
24
37
  const raw = await this.fetcher();
25
38
  const integrations = {};
26
39
  for (const entry of raw) {
@@ -31,20 +44,25 @@ var Registry = class {
31
44
  version: 1,
32
45
  integrations
33
46
  };
47
+ this.loadedAt = Date.now();
34
48
  return this.index;
35
49
  }
36
50
  /**
37
- * Force reload the index (bypass cache).
51
+ * Force reload the index (bypass cache). Equivalent to `load({ fresh: true })`.
38
52
  */
39
53
  async reload() {
40
54
  this.index = null;
41
- return this.load();
55
+ return this.load({ fresh: true });
42
56
  }
43
57
  /**
44
58
  * Get a specific integration entry by name.
59
+ *
60
+ * @param fresh - When true, force a fresh fetch before reading (used by the
61
+ * resolve-for-install path so a stale version list never blocks
62
+ * a just-published version).
45
63
  */
46
- async get(id) {
47
- return (await this.load()).integrations[id];
64
+ async get(id, fresh = false) {
65
+ return (await this.load({ fresh })).integrations[id];
48
66
  }
49
67
  /**
50
68
  * List all integrations in the registry.
@@ -83,10 +101,12 @@ var Resolver = class {
83
101
  *
84
102
  * @param name - Integration name (e.g. "discord")
85
103
  * @param version - Specific version (e.g. "1.0.0") or undefined for latest
104
+ * @param options - Pass `{ fresh: true }` to bypass the registry cache
105
+ * (used by the install/reconcile path).
86
106
  * @returns Resolved integration with repo URL and commit hash
87
107
  */
88
- async resolve(id, version) {
89
- const entry = await this.registry.get(id);
108
+ async resolve(id, version, options = {}) {
109
+ const entry = await this.registry.get(id, options.fresh);
90
110
  if (!entry) throw new RegistryResolveError(`Integration "${id}" not found in registry`);
91
111
  const resolvedVersion = version && version.length > 0 ? version : entry.latest;
92
112
  if (!entry.versions.includes(resolvedVersion)) throw new RegistryResolveError(`Version "${resolvedVersion}" not found for integration "${id}". Available versions: ${entry.versions.join(", ")}`);
@@ -966,7 +986,7 @@ var IntegrationManager = class {
966
986
  resolved = buildCustomResolved(name, customSource);
967
987
  this.log.info(`Custom Connection install: ${name} from ${resolved.repository}@${resolved.commit}`);
968
988
  } else {
969
- resolved = await this.resolver.resolve(name, version);
989
+ resolved = await this.resolver.resolve(name, version, { fresh: true });
970
990
  this.log.info(`Resolved ${name}@${resolved.version} from ${resolved.repository}`);
971
991
  }
972
992
  const installPath = await this.installer.install(resolved);
@@ -2157,4 +2177,4 @@ var IntegrationManagerAdapter = class {
2157
2177
  }
2158
2178
  };
2159
2179
  //#endregion
2160
- export { Installer, InstallerError, IntegrationManager, IntegrationManagerAdapter, LockManager, McpApplier, OpenClawApplier, Registry, RegistryResolveError, Resolver, StateManager, buildHookEnv, resolveInstallsForRuntime, runHook, runHookWithContext };
2180
+ export { DEFAULT_REGISTRY_TTL_MS, Installer, InstallerError, IntegrationManager, IntegrationManagerAdapter, LockManager, McpApplier, OpenClawApplier, Registry, RegistryResolveError, Resolver, StateManager, buildHookEnv, resolveInstallsForRuntime, runHook, runHookWithContext };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/integrations",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Integration lifecycle management for Alfe — registry, resolution, installation, and state",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",