@alfe.ai/integrations 0.5.4 → 0.6.1

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/README.md CHANGED
@@ -45,7 +45,8 @@ import {
45
45
  } from '@alfe.ai/integrations';
46
46
 
47
47
  // ── Registry + Resolution ──────────────────────────────────
48
- const registry = new Registry();
48
+ const registryFetcher = () => apiClient.integrations.getRegistry();
49
+ const registry = new Registry(registryFetcher);
49
50
  const results = await registry.search('discord');
50
51
 
51
52
  const resolver = new Resolver(registry);
@@ -56,8 +57,9 @@ const installPath = await installer.install(resolved);
56
57
 
57
58
  // ── Lifecycle Management ───────────────────────────────────
58
59
  const manager = new IntegrationManager({
59
- logger,
60
- skillsDir: '~/.alfe/skills/', // default
60
+ registryFetcher,
61
+ runtimeAppliers: new Map([['openclaw', openClawApplier]]),
62
+ mcpApplier,
61
63
  });
62
64
 
63
65
  await manager.install({ name: 'discord', version: '1.0.0' });
@@ -73,20 +75,20 @@ cloudClient.setIntegrationManager(adapter);
73
75
 
74
76
  ## Configuration
75
77
 
76
- The `Registry` class resolves the API URL in this order:
77
-
78
- 1. Explicit `apiUrl` constructor argument
79
- 2. `ALFE_API_URL` environment variable
80
- 3. `https://api.alfe.ai` (production default)
78
+ `Registry` is transport-independent. Callers provide a `RegistryFetcher`,
79
+ normally backed by `@alfe.ai/api-client`; the package does not read an API URL
80
+ or perform HTTP directly.
81
81
 
82
82
  The `IntegrationManager` accepts an options object:
83
83
 
84
84
  ```typescript
85
85
  interface IntegrationManagerOptions {
86
- logger?: Logger;
87
- statePath?: string; // default: ~/.alfe/integrations.json
86
+ registryFetcher: RegistryFetcher;
87
+ statePath?: string; // default: ~/.alfe/integrations.json
88
88
  integrationsDir?: string; // default: ~/.alfe/integrations/
89
- skillsDir?: string; // default: ~/.alfe/skills/
89
+ lockPath?: string; // default: ~/.alfe/runtime-lock.json
90
+ runtimeAppliers?: Map<string, RuntimeApplier>;
91
+ mcpApplier?: McpApplier; // required when manifests declare mcp_servers
90
92
  }
91
93
  ```
92
94
 
package/dist/index.d.ts CHANGED
@@ -95,6 +95,7 @@ interface LoadOptions {
95
95
  declare class Registry {
96
96
  private index;
97
97
  private loadedAt;
98
+ private loadPromise;
98
99
  private fetcher;
99
100
  private ttlMs;
100
101
  /**
@@ -249,12 +250,14 @@ declare class Installer {
249
250
  */
250
251
  stage(resolved: ResolvedIntegration): Promise<string>;
251
252
  /**
252
- * Commit a previously-staged clone: remove the live install dir and rename
253
- * the staged dir into its place. Same-filesystem `renameSync` makes the swap
254
- * near-atomic (no window where the install dir is half-populated). Called
255
- * inside the runtime-suspension window during a diff-based upgrade.
253
+ * Commit a previously-staged clone with a recoverable same-filesystem swap.
254
+ * The live dir first moves to a deterministic backup, then the staged dir
255
+ * moves into place. A failed second rename restores the prior install; a
256
+ * daemon crash is recovered on the next `getInstallPath()` call.
256
257
  */
257
258
  commitStaged(name: string, stagedPath: string): void;
259
+ /** Restore a live install left in the deterministic backup by a killed swap. */
260
+ private recoverInterruptedSwap;
258
261
  /**
259
262
  * Remove any orphaned `.staging-*` directories under the base path. Called at
260
263
  * the start of `stage()` so a daemon killed mid-upgrade doesn't accumulate
@@ -290,6 +293,21 @@ declare class Installer {
290
293
  */
291
294
  private installLocalDependencies;
292
295
  private runNpmInstall;
296
+ /**
297
+ * Pre-fetch every npx-executed MCP server package the manifest declares
298
+ * (e.g. `command: npx, args: ["-y", "@alfe.ai/xero-mcp@0.3.8"]`) into the
299
+ * npx cache, WITHOUT executing the server. The first bundler connect then
300
+ * spawns from a warm cache instead of paying a 30-90s npm download during
301
+ * the MCP handshake — where it used to blow the connect budget and strand
302
+ * the server's tools until a daemon restart.
303
+ *
304
+ * Strictly best-effort: a missing/unparseable manifest, a non-npx command,
305
+ * or a failed download logs a warning and moves on — an install must never
306
+ * fail because a pre-warm did.
307
+ */
308
+ prewarmMcpNpxPackages(installPath: string): Promise<void>;
309
+ /** Populate the npx cache for a package spec via a no-op command. */
310
+ private runNpx;
293
311
  /**
294
312
  * List all locally installed integrations.
295
313
  */
@@ -298,6 +316,7 @@ declare class Installer {
298
316
  * Check if an integration is installed.
299
317
  */
300
318
  isInstalled(name: string): boolean;
319
+ private validateResolved;
301
320
  }
302
321
  //#endregion
303
322
  //#region src/runtime-applier.d.ts
@@ -510,6 +529,7 @@ interface IntegrationManagerOptions {
510
529
  logger?: unknown;
511
530
  statePath?: string;
512
531
  integrationsDir?: string;
532
+ /** @deprecated Skills directories are owned by each RuntimeApplier. */
513
533
  skillsDir?: string;
514
534
  /** Runtime appliers keyed by runtime name */
515
535
  runtimeAppliers?: Map<string, RuntimeApplier>;
@@ -521,7 +541,8 @@ interface IntegrationManagerOptions {
521
541
  * Runtime-agnostic MCP server applier. The manager routes
522
542
  * `mcp_servers` declarations through it once per integration (the
523
543
  * applier writes to the bundler store + openclaw.json mirror).
524
- * Required there is no fallback path; integrations with
544
+ * Operationally required when manifests declare MCP servers. There is no
545
+ * fallback path; integrations with
525
546
  * `mcp_servers` will skip MCP registration silently if this is
526
547
  * omitted (the warn log makes this visible).
527
548
  */
@@ -716,8 +737,10 @@ declare class IntegrationManager {
716
737
  /**
717
738
  * Build a hook-failure message. A hook we SIGKILLed at its timeout is rendered
718
739
  * as `(timed out after <ms>ms)` so it is visually distinguishable in Sentry
719
- * from a genuine non-zero exit `(exit <code>)` the two have very different
720
- * root causes (Sentry AGENT-DAEMON-6).
740
+ * from a genuine non-zero exit `(exit <code>)`. Captured output is deliberately
741
+ * excluded: hooks inherit daemon credentials and receive integration secrets,
742
+ * so stdout/stderr is a secret-bearing channel that must not enter durable
743
+ * state, logs, or user-facing error messages.
721
744
  */
722
745
  private hookFailureMessage;
723
746
  /**
@@ -739,9 +762,9 @@ declare class IntegrationManager {
739
762
  * (and everything a sibling integration still claims) in place. Phase 4's
740
763
  * `activate` re-applies and re-locks the new set immediately after.
741
764
  *
742
- * `removeEntries` clears this integration's lock rows and returns what it had;
743
- * a candidate removal is skipped when it is EITHER (a) still declared by the
744
- * new manifest, or (b) still claimed by another integration.
765
+ * The existing lock remains intact until every physical removal and MCP prune
766
+ * succeeds. This makes partial cleanup retryable; only then are the old rows
767
+ * cleared so activate() can record the new manifest's contributions.
745
768
  */
746
769
  private applyUpgradeDiffRemovals;
747
770
  /**
@@ -758,7 +781,6 @@ declare class IntegrationManager {
758
781
  //#region src/state.d.ts
759
782
  declare class StateManager {
760
783
  private filePath;
761
- private lockHeld;
762
784
  constructor(filePath?: string);
763
785
  /**
764
786
  * Read the current state file. Returns empty state if file doesn't exist.
@@ -1215,8 +1237,9 @@ declare class OpenClawApplier implements RuntimeApplier {
1215
1237
  * Drop a set of dotted keys from a dot-free parent object via read-drop-write,
1216
1238
  * UNLOCKED. If the parent becomes empty, `config unset` it; otherwise
1217
1239
  * `--replace` the shrunk map (siblings survive because they remain in
1218
- * `remaining`). Warn-tolerant a failed drop of an already-gone key must not
1219
- * fail the caller. Shared by `removeConfig` (whole-integration teardown) and
1240
+ * `remaining`). A proven already-absent path is idempotent success; every
1241
+ * other write failure propagates so callers retain the ownership record for a
1242
+ * later retry. Shared by `removeConfig` (whole-integration teardown) and
1220
1243
  * `applyConfig`'s stale-key diff (per-key removal between manifest versions).
1221
1244
  *
1222
1245
  * Assumes the shared CLI lock is already held by the calling public method —
@@ -1240,10 +1263,11 @@ declare class OpenClawApplier implements RuntimeApplier {
1240
1263
  * the provider subtree is unset for `models.providers.zhipu.baseUrl`, the
1241
1264
  * follow-up `.apiKey` / `.models` leaves skip re-issuing the parent unset.
1242
1265
  *
1243
- * Warn-tolerant (a failed unset of an already-gone key must never fail the
1244
- * caller) and assumes the shared CLI lock is held stays an `*Unlocked`
1245
- * internal since the lock is NOT re-entrant. Shared by `removeConfig`
1246
- * (whole-integration teardown) and `applyConfig`'s stale-leaf diff.
1266
+ * A proven already-absent path is idempotent success; every other failure
1267
+ * propagates so the ownership ledger remains a retry record. Assumes the
1268
+ * shared CLI lock is held stays an `*Unlocked` internal since the lock is
1269
+ * NOT re-entrant. Shared by `removeConfig` (whole-integration teardown) and
1270
+ * `applyConfig`'s stale-leaf diff.
1247
1271
  */
1248
1272
  private unsetLeafPathUnlocked;
1249
1273
  /**
@@ -1753,6 +1777,11 @@ interface RuntimeLockFile {
1753
1777
  runtimes: Record<string, RuntimeDesiredState>;
1754
1778
  updatedAt: string;
1755
1779
  }
1780
+ interface IntegrationRuntimeEntries {
1781
+ plugins: RuntimePluginEntry[];
1782
+ skills: RuntimeSkillEntry[];
1783
+ config: RuntimeConfigEntry[];
1784
+ }
1756
1785
  declare class LockManager {
1757
1786
  private filePath;
1758
1787
  constructor(filePath?: string);
@@ -1774,6 +1803,12 @@ declare class LockManager {
1774
1803
  addEntries(runtime: string, integrationId: string, version: string, plugins: PluginInstall[], skills: SkillInstall[], installPath: string, opts?: {
1775
1804
  configApplied?: boolean;
1776
1805
  }): void;
1806
+ /**
1807
+ * Snapshot an integration's entries without changing the lock. Teardown uses
1808
+ * this first and only clears the entries after every physical removal
1809
+ * succeeds, making a failed cleanup retryable.
1810
+ */
1811
+ getEntriesForIntegration(integrationId: string): Record<string, IntegrationRuntimeEntries>;
1777
1812
  /**
1778
1813
  * Remove all entries for a given integration across all runtimes.
1779
1814
  * Returns what was removed, keyed by runtime.
@@ -1782,11 +1817,7 @@ declare class LockManager {
1782
1817
  * plugins, skills, OR config there — so `deactivate` drives
1783
1818
  * `applier.removeConfig` even for a config-only integration.
1784
1819
  */
1785
- removeEntries(integrationId: string): Record<string, {
1786
- plugins: RuntimePluginEntry[];
1787
- skills: RuntimeSkillEntry[];
1788
- config: RuntimeConfigEntry[];
1789
- }>;
1820
+ removeEntries(integrationId: string): Record<string, IntegrationRuntimeEntries>;
1790
1821
  /**
1791
1822
  * Get the full desired state for a specific runtime.
1792
1823
  */
@@ -1874,4 +1905,4 @@ declare class IntegrationManagerAdapter implements IIntegrationManager {
1874
1905
  resetReinstallAttempts(integrationId: string): void;
1875
1906
  }
1876
1907
  //#endregion
1877
- export { ClaudeCodeApplier, type ClaudeCodeApplierOptions, ClaudeCodeMcpSync, type ClaudeCodeMcpSyncOptions, type CredentialsResolver, DEFAULT_REGISTRY_TTL_MS, HermesApplier, type HermesApplierOptions, HermesMcpSync, type HermesMcpSyncOptions, 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, type McpStoreReader, NoopOpenClawCliLock, OpenClawApplier, type OpenClawApplierOptions, type OpenClawCliLock, type PlatformContext, Registry, type RegistryEntry, type RegistryFetcher, type RegistryIndex, type RegistryOptions, RegistryResolveError, type ResolveOptions, type ResolvedIntegration, Resolver, type RuntimeApplier, type RuntimeConfigEntry, type RuntimeDesiredState, type RuntimeLockFile, type RuntimePluginEntry, type RuntimeSkillEntry, SerialOpenClawCliLock, StateManager, buildHookEnv, resolveInstallsForRuntime, runHook, runHookWithContext, upsertEnvVar };
1908
+ export { ClaudeCodeApplier, type ClaudeCodeApplierOptions, ClaudeCodeMcpSync, type ClaudeCodeMcpSyncOptions, type CredentialsResolver, DEFAULT_REGISTRY_TTL_MS, HermesApplier, type HermesApplierOptions, HermesMcpSync, type HermesMcpSyncOptions, 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 IntegrationRuntimeEntries, type LoadOptions, LockManager, McpApplier, type McpApplierOptions, type McpStoreReader, NoopOpenClawCliLock, OpenClawApplier, type OpenClawApplierOptions, type OpenClawCliLock, type PlatformContext, Registry, type RegistryEntry, type RegistryFetcher, type RegistryIndex, type RegistryOptions, RegistryResolveError, type ResolveOptions, type ResolvedIntegration, Resolver, type RuntimeApplier, type RuntimeConfigEntry, type RuntimeDesiredState, type RuntimeLockFile, type RuntimePluginEntry, type RuntimeSkillEntry, SerialOpenClawCliLock, StateManager, buildHookEnv, resolveInstallsForRuntime, runHook, runHookWithContext, upsertEnvVar };