@alfe.ai/integrations 0.1.3 → 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 +56 -7
- package/dist/index.js +48 -14
- package/package.json +2 -2
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.
|
|
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.
|
|
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);
|
|
@@ -1580,6 +1600,19 @@ var IntegrationManager = class {
|
|
|
1580
1600
|
const execFileAsync = promisify(execFile);
|
|
1581
1601
|
const log$1 = createLogger("OpenClawApplier");
|
|
1582
1602
|
const DEFAULT_SKILLS_DIR = join(homedir(), ".alfe", "skills");
|
|
1603
|
+
/**
|
|
1604
|
+
* Bundled OpenClaw plugins that ship inside the runtime (not installed as npm
|
|
1605
|
+
* `@alfe.ai/openclaw-*` packages) and must always be present in `plugins.allow`.
|
|
1606
|
+
*
|
|
1607
|
+
* OpenClaw 2026.6.8+ treats a non-empty `plugins.allow` as an EXCLUSIVE allowlist:
|
|
1608
|
+
* any plugin absent from it is gated off even when its manifest is
|
|
1609
|
+
* `enabledByDefault`. Because we build `plugins.allow` incrementally from the npm
|
|
1610
|
+
* plugins we `applyPlugin`, bundled plugins never land in it and are silently
|
|
1611
|
+
* disabled. Keep this list minimal — only bundled plugins Alfe actually relies on.
|
|
1612
|
+
* Providers (anthropic/openai/elevenlabs/…) are intentionally excluded: Alfe routes
|
|
1613
|
+
* LLM/voice traffic through its own AI proxy, so leaving them gated off is correct.
|
|
1614
|
+
*/
|
|
1615
|
+
const BUNDLED_BASELINE_ALLOW = ["browser"];
|
|
1583
1616
|
function flattenConfig(obj, prefix = "") {
|
|
1584
1617
|
const entries = [];
|
|
1585
1618
|
for (const [key, val] of Object.entries(obj)) {
|
|
@@ -1721,8 +1754,9 @@ var OpenClawApplier = class {
|
|
|
1721
1754
|
const parsed = JSON.parse(stdout.trim());
|
|
1722
1755
|
if (Array.isArray(parsed)) currentAllow = parsed;
|
|
1723
1756
|
} catch {}
|
|
1724
|
-
|
|
1725
|
-
|
|
1757
|
+
const missing = [...new Set([pkg, ...BUNDLED_BASELINE_ALLOW])].filter((id) => !currentAllow.includes(id));
|
|
1758
|
+
if (missing.length === 0) return;
|
|
1759
|
+
const updated = [...currentAllow, ...missing];
|
|
1726
1760
|
try {
|
|
1727
1761
|
await execFileAsync("openclaw", [
|
|
1728
1762
|
"config",
|
|
@@ -2143,4 +2177,4 @@ var IntegrationManagerAdapter = class {
|
|
|
2143
2177
|
}
|
|
2144
2178
|
};
|
|
2145
2179
|
//#endregion
|
|
2146
|
-
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.
|
|
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",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@auriclabs/logger": "^0.1.1",
|
|
16
16
|
"@alfe.ai/integration-manifest": "^0.2.1",
|
|
17
|
-
"@alfe.ai/mcp-bundler": "^0.2.
|
|
17
|
+
"@alfe.ai/mcp-bundler": "^0.2.1"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
20
|
"dist"
|