@frontmcp/plugin-skilled-openapi 1.5.1 → 1.5.2

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/esm/index.mjs CHANGED
@@ -15,12 +15,12 @@ import {
15
15
  createBundleSource
16
16
  } from "@frontmcp/adapters/skills";
17
17
  import {
18
+ buildSkillsCatalogSummary,
18
19
  DynamicPlugin,
19
20
  FrontMcpLogger,
20
21
  ListToolsHook,
21
22
  Plugin,
22
- ScopeEntry as ScopeEntry3,
23
- buildSkillsCatalogSummary
23
+ ScopeEntry as ScopeEntry3
24
24
  } from "@frontmcp/sdk";
25
25
 
26
26
  // plugins/plugin-skilled-openapi/src/executor/credential-resolver.ts
@@ -350,6 +350,78 @@ var BundleSyncService = class {
350
350
  logger;
351
351
  operationToolFactory;
352
352
  skillUnregisterByBundleId = /* @__PURE__ */ new Map();
353
+ /** The configured bundle source; wired by the DI factory via {@link attachSource}. */
354
+ source;
355
+ readyPromise;
356
+ /**
357
+ * Wired by the plugin's DI factory. Stores the bundle source so {@link
358
+ * ensureReady} can start it and await the first apply lazily, inside the
359
+ * request that needs the catalog. Does NOT start the source — see the context
360
+ * note on {@link bootstrap}.
361
+ */
362
+ attachSource(source) {
363
+ this.source = source;
364
+ }
365
+ /**
366
+ * Ensure the source has started and its FIRST bundle has been applied, so
367
+ * `scope.skills` and the hidden-op registry are populated before a meta-tool
368
+ * (search_skill / load_skill / run_workflow) reads them. Memoized — the
369
+ * underlying start + apply runs at most once per service instance.
370
+ */
371
+ async ensureReady() {
372
+ if (!this.source) return;
373
+ this.readyPromise ??= this.bootstrap(this.source);
374
+ return this.readyPromise;
375
+ }
376
+ /**
377
+ * Start the source and await its FIRST bundle apply.
378
+ *
379
+ * CRITICAL — every promise here (the `apply()`, the `firstApply` latch, and
380
+ * the `onChange` wiring) is created inside the FIRST caller's context, which
381
+ * is a request context (the meta-tools / list-tools hook call `ensureReady()`
382
+ * while handling a request). On a stateless V8-isolate runtime (Cloudflare
383
+ * Workers) this is mandatory two ways over:
384
+ * 1. workerd cancels promise continuations that cross a request boundary, so
385
+ * a `firstApply` created in the scope-init/factory context would never
386
+ * resolve when awaited from a later request — it must be created here.
387
+ * 2. There is no background event loop, so a deferred/fire-and-forget apply
388
+ * (the old `setImmediate(source.start())`) can never complete after the
389
+ * response is sent — the awaiting tool drives it to completion inline.
390
+ * A failed first sync is swallowed (logged) so it degrades to "no skills"
391
+ * instead of hanging or 500-ing every call.
392
+ */
393
+ async bootstrap(source) {
394
+ let resolveFirst;
395
+ let firstSettled = false;
396
+ const firstApply = new Promise((resolve) => {
397
+ resolveFirst = resolve;
398
+ });
399
+ let applyTail = Promise.resolve();
400
+ source.onChange((bundle) => {
401
+ applyTail = applyTail.then(async () => {
402
+ try {
403
+ const result = await this.apply(bundle);
404
+ if (!result.applied) {
405
+ this.logger.warn(`[bundle-sync] bundle ${bundle.bundleId}@${bundle.version} not applied: ${result.reason}`);
406
+ }
407
+ } catch (e) {
408
+ this.logger.error(
409
+ `[bundle-sync] bundle ${bundle.bundleId}@${bundle.version} apply threw: ${e.message}`
410
+ );
411
+ }
412
+ });
413
+ if (!firstSettled) {
414
+ firstSettled = true;
415
+ void applyTail.then(() => resolveFirst());
416
+ }
417
+ });
418
+ try {
419
+ await source.start();
420
+ if (firstSettled) await firstApply;
421
+ } catch (e) {
422
+ this.logger.error(`[skilled-openapi] initial bundle sync failed: ${e.message}`);
423
+ }
424
+ }
353
425
  /**
354
426
  * Validate signature and apply the bundle. Returns a structured result;
355
427
  * never throws on validation/registration failure (caller logs + ignores).
@@ -612,7 +684,7 @@ var loadSkillOutputSchema = {
612
684
  // plugins/plugin-skilled-openapi/src/tools/load-skill.tool.ts
613
685
  var LoadSkillTool = class extends ToolContext {
614
686
  async execute(input) {
615
- this.get(BundleSyncService);
687
+ await this.get(BundleSyncService).ensureReady();
616
688
  const scope = this.get(ScopeEntry);
617
689
  const skillRegistry = scope.skills;
618
690
  if (!skillRegistry) {
@@ -1411,7 +1483,7 @@ var MAX_TOOL_CALLS = 25;
1411
1483
  var TIMEOUT_MS = 8e3;
1412
1484
  var RunWorkflowTool = class extends ToolContext2 {
1413
1485
  async execute(input) {
1414
- this.get(BundleSyncService);
1486
+ await this.get(BundleSyncService).ensureReady();
1415
1487
  const config = this.get(SkilledOpenApiConfig);
1416
1488
  const hiddenOps = this.get(HiddenOpRegistry);
1417
1489
  const guard = this.get(AuthorityGuard);
@@ -1438,7 +1510,9 @@ var RunWorkflowTool = class extends ToolContext2 {
1438
1510
  const toolHandler = async (actionId, actionInput) => {
1439
1511
  const entry = hiddenOps.getByActionId(actionId);
1440
1512
  if (!entry) {
1441
- throw new Error(`unknown action "${actionId}" \u2014 run search_skill then load_skill to discover available actions`);
1513
+ throw new Error(
1514
+ `unknown action "${actionId}" \u2014 run search_skill then load_skill to discover available actions`
1515
+ );
1442
1516
  }
1443
1517
  const res = await executeSkillAction({ entry, input: actionInput ?? {}, authInfo: this.authInfo, deps });
1444
1518
  if (!res.ok) {
@@ -1450,7 +1524,10 @@ var RunWorkflowTool = class extends ToolContext2 {
1450
1524
  try {
1451
1525
  transformed = transformAgentScript(input.script, { transformLoops: false });
1452
1526
  } catch (error) {
1453
- return { success: false, error: `AgentScript rejected: ${error instanceof Error ? error.message : String(error)}` };
1527
+ return {
1528
+ success: false,
1529
+ error: `AgentScript rejected: ${error instanceof Error ? error.message : String(error)}`
1530
+ };
1454
1531
  }
1455
1532
  const adapter = new InterpreterAdapter({ maxSteps: MAX_STEPS });
1456
1533
  const context = {
@@ -1486,9 +1563,6 @@ RunWorkflowTool = __decorateClass([
1486
1563
  })
1487
1564
  ], RunWorkflowTool);
1488
1565
 
1489
- // plugins/plugin-skilled-openapi/src/tools/search-skill.tool.ts
1490
- import { ScopeEntry as ScopeEntry2, Tool as Tool3, ToolContext as ToolContext3 } from "@frontmcp/sdk";
1491
-
1492
1566
  // plugins/plugin-skilled-openapi/src/tools/search-skill.schema.ts
1493
1567
  import { z as z5 } from "@frontmcp/lazy-zod";
1494
1568
  var searchSkillDescription = `Search the available skills by free-form query.
@@ -1530,9 +1604,10 @@ var searchSkillOutputSchema = {
1530
1604
  };
1531
1605
 
1532
1606
  // plugins/plugin-skilled-openapi/src/tools/search-skill.tool.ts
1607
+ import { ScopeEntry as ScopeEntry2, Tool as Tool3, ToolContext as ToolContext3 } from "@frontmcp/sdk";
1533
1608
  var SearchSkillTool = class extends ToolContext3 {
1534
1609
  async execute(input) {
1535
- this.get(BundleSyncService);
1610
+ await this.get(BundleSyncService).ensureReady();
1536
1611
  const scope = this.get(ScopeEntry2);
1537
1612
  const skillRegistry = scope.skills;
1538
1613
  if (!skillRegistry || !skillRegistry.hasAny()) {
@@ -1620,7 +1695,7 @@ var SkilledOpenApiPlugin = class extends DynamicPlugin {
1620
1695
  const target = tools.find((item) => item.tool?.metadata?.name === "search_skill");
1621
1696
  if (!target) return;
1622
1697
  try {
1623
- this.get(BundleSyncService);
1698
+ await this.get(BundleSyncService).ensureReady();
1624
1699
  } catch {
1625
1700
  }
1626
1701
  const scope = this.get(ScopeEntry3);
@@ -1746,21 +1821,12 @@ ${catalog}` : searchSkillDescription;
1746
1821
  logger.warn(`runtime-deps attach threw: ${e.message}`);
1747
1822
  }
1748
1823
  }
1749
- source.onChange((bundle) => {
1750
- void sync.apply(bundle).then((result) => {
1751
- if (!result.applied) {
1752
- logger.warn(`bundle ${bundle.bundleId}@${bundle.version} not applied: ${result.reason}`);
1753
- }
1754
- }).catch((e) => {
1755
- logger.error(`bundle ${bundle.bundleId}@${bundle.version} apply threw: ${e.message}`);
1824
+ sync.attachSource(source);
1825
+ if (!runtimeDeps?.disablePolling) {
1826
+ setImmediate(() => {
1827
+ void sync.ensureReady();
1756
1828
  });
1757
- });
1758
- const startedSource = source;
1759
- setImmediate(() => {
1760
- startedSource.start().catch((e) => {
1761
- logger.error(`bundle source failed to start: ${e.message}`);
1762
- });
1763
- });
1829
+ }
1764
1830
  return sync;
1765
1831
  }
1766
1832
  }
package/esm/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/plugin-skilled-openapi",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
4
4
  "description": "FrontMCP plugin: serve a customer's OpenAPI spec as skill bundles (signed, OpenAPI-Overlay-based) with hidden per-operation tools mediated by three meta-tools (search_skill / load_skill / run_workflow — the last runs an enclave-sandboxed AgentScript that orchestrates the loaded skills' operations).",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "license": "Apache-2.0",
@@ -48,14 +48,14 @@
48
48
  }
49
49
  },
50
50
  "dependencies": {
51
- "@frontmcp/adapters": "1.5.1",
52
- "@frontmcp/auth": "1.5.1",
53
- "@frontmcp/sdk": "1.5.1"
51
+ "@frontmcp/adapters": "1.5.2",
52
+ "@frontmcp/auth": "1.5.2",
53
+ "@frontmcp/sdk": "1.5.2"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "@enclave-vm/ast": "*",
57
57
  "@enclave-vm/core": "*",
58
- "@frontmcp/lazy-zod": "1.5.1"
58
+ "@frontmcp/lazy-zod": "1.5.2"
59
59
  },
60
60
  "peerDependenciesMeta": {
61
61
  "@enclave-vm/ast": {
package/index.js CHANGED
@@ -359,6 +359,78 @@ var BundleSyncService = class {
359
359
  logger;
360
360
  operationToolFactory;
361
361
  skillUnregisterByBundleId = /* @__PURE__ */ new Map();
362
+ /** The configured bundle source; wired by the DI factory via {@link attachSource}. */
363
+ source;
364
+ readyPromise;
365
+ /**
366
+ * Wired by the plugin's DI factory. Stores the bundle source so {@link
367
+ * ensureReady} can start it and await the first apply lazily, inside the
368
+ * request that needs the catalog. Does NOT start the source — see the context
369
+ * note on {@link bootstrap}.
370
+ */
371
+ attachSource(source) {
372
+ this.source = source;
373
+ }
374
+ /**
375
+ * Ensure the source has started and its FIRST bundle has been applied, so
376
+ * `scope.skills` and the hidden-op registry are populated before a meta-tool
377
+ * (search_skill / load_skill / run_workflow) reads them. Memoized — the
378
+ * underlying start + apply runs at most once per service instance.
379
+ */
380
+ async ensureReady() {
381
+ if (!this.source) return;
382
+ this.readyPromise ??= this.bootstrap(this.source);
383
+ return this.readyPromise;
384
+ }
385
+ /**
386
+ * Start the source and await its FIRST bundle apply.
387
+ *
388
+ * CRITICAL — every promise here (the `apply()`, the `firstApply` latch, and
389
+ * the `onChange` wiring) is created inside the FIRST caller's context, which
390
+ * is a request context (the meta-tools / list-tools hook call `ensureReady()`
391
+ * while handling a request). On a stateless V8-isolate runtime (Cloudflare
392
+ * Workers) this is mandatory two ways over:
393
+ * 1. workerd cancels promise continuations that cross a request boundary, so
394
+ * a `firstApply` created in the scope-init/factory context would never
395
+ * resolve when awaited from a later request — it must be created here.
396
+ * 2. There is no background event loop, so a deferred/fire-and-forget apply
397
+ * (the old `setImmediate(source.start())`) can never complete after the
398
+ * response is sent — the awaiting tool drives it to completion inline.
399
+ * A failed first sync is swallowed (logged) so it degrades to "no skills"
400
+ * instead of hanging or 500-ing every call.
401
+ */
402
+ async bootstrap(source) {
403
+ let resolveFirst;
404
+ let firstSettled = false;
405
+ const firstApply = new Promise((resolve) => {
406
+ resolveFirst = resolve;
407
+ });
408
+ let applyTail = Promise.resolve();
409
+ source.onChange((bundle) => {
410
+ applyTail = applyTail.then(async () => {
411
+ try {
412
+ const result = await this.apply(bundle);
413
+ if (!result.applied) {
414
+ this.logger.warn(`[bundle-sync] bundle ${bundle.bundleId}@${bundle.version} not applied: ${result.reason}`);
415
+ }
416
+ } catch (e) {
417
+ this.logger.error(
418
+ `[bundle-sync] bundle ${bundle.bundleId}@${bundle.version} apply threw: ${e.message}`
419
+ );
420
+ }
421
+ });
422
+ if (!firstSettled) {
423
+ firstSettled = true;
424
+ void applyTail.then(() => resolveFirst());
425
+ }
426
+ });
427
+ try {
428
+ await source.start();
429
+ if (firstSettled) await firstApply;
430
+ } catch (e) {
431
+ this.logger.error(`[skilled-openapi] initial bundle sync failed: ${e.message}`);
432
+ }
433
+ }
362
434
  /**
363
435
  * Validate signature and apply the bundle. Returns a structured result;
364
436
  * never throws on validation/registration failure (caller logs + ignores).
@@ -621,7 +693,7 @@ var loadSkillOutputSchema = {
621
693
  // plugins/plugin-skilled-openapi/src/tools/load-skill.tool.ts
622
694
  var LoadSkillTool = class extends import_sdk.ToolContext {
623
695
  async execute(input) {
624
- this.get(BundleSyncService);
696
+ await this.get(BundleSyncService).ensureReady();
625
697
  const scope = this.get(import_sdk.ScopeEntry);
626
698
  const skillRegistry = scope.skills;
627
699
  if (!skillRegistry) {
@@ -1414,7 +1486,7 @@ var MAX_TOOL_CALLS = 25;
1414
1486
  var TIMEOUT_MS = 8e3;
1415
1487
  var RunWorkflowTool = class extends import_sdk3.ToolContext {
1416
1488
  async execute(input) {
1417
- this.get(BundleSyncService);
1489
+ await this.get(BundleSyncService).ensureReady();
1418
1490
  const config = this.get(SkilledOpenApiConfig);
1419
1491
  const hiddenOps = this.get(HiddenOpRegistry);
1420
1492
  const guard = this.get(AuthorityGuard);
@@ -1441,7 +1513,9 @@ var RunWorkflowTool = class extends import_sdk3.ToolContext {
1441
1513
  const toolHandler = async (actionId, actionInput) => {
1442
1514
  const entry = hiddenOps.getByActionId(actionId);
1443
1515
  if (!entry) {
1444
- throw new Error(`unknown action "${actionId}" \u2014 run search_skill then load_skill to discover available actions`);
1516
+ throw new Error(
1517
+ `unknown action "${actionId}" \u2014 run search_skill then load_skill to discover available actions`
1518
+ );
1445
1519
  }
1446
1520
  const res = await executeSkillAction({ entry, input: actionInput ?? {}, authInfo: this.authInfo, deps });
1447
1521
  if (!res.ok) {
@@ -1453,7 +1527,10 @@ var RunWorkflowTool = class extends import_sdk3.ToolContext {
1453
1527
  try {
1454
1528
  transformed = transformAgentScript(input.script, { transformLoops: false });
1455
1529
  } catch (error) {
1456
- return { success: false, error: `AgentScript rejected: ${error instanceof Error ? error.message : String(error)}` };
1530
+ return {
1531
+ success: false,
1532
+ error: `AgentScript rejected: ${error instanceof Error ? error.message : String(error)}`
1533
+ };
1457
1534
  }
1458
1535
  const adapter = new InterpreterAdapter({ maxSteps: MAX_STEPS });
1459
1536
  const context = {
@@ -1489,9 +1566,6 @@ RunWorkflowTool = __decorateClass([
1489
1566
  })
1490
1567
  ], RunWorkflowTool);
1491
1568
 
1492
- // plugins/plugin-skilled-openapi/src/tools/search-skill.tool.ts
1493
- var import_sdk4 = require("@frontmcp/sdk");
1494
-
1495
1569
  // plugins/plugin-skilled-openapi/src/tools/search-skill.schema.ts
1496
1570
  var import_lazy_zod5 = require("@frontmcp/lazy-zod");
1497
1571
  var searchSkillDescription = `Search the available skills by free-form query.
@@ -1533,9 +1607,10 @@ var searchSkillOutputSchema = {
1533
1607
  };
1534
1608
 
1535
1609
  // plugins/plugin-skilled-openapi/src/tools/search-skill.tool.ts
1610
+ var import_sdk4 = require("@frontmcp/sdk");
1536
1611
  var SearchSkillTool = class extends import_sdk4.ToolContext {
1537
1612
  async execute(input) {
1538
- this.get(BundleSyncService);
1613
+ await this.get(BundleSyncService).ensureReady();
1539
1614
  const scope = this.get(import_sdk4.ScopeEntry);
1540
1615
  const skillRegistry = scope.skills;
1541
1616
  if (!skillRegistry || !skillRegistry.hasAny()) {
@@ -1623,7 +1698,7 @@ var SkilledOpenApiPlugin = class extends import_sdk5.DynamicPlugin {
1623
1698
  const target = tools.find((item) => item.tool?.metadata?.name === "search_skill");
1624
1699
  if (!target) return;
1625
1700
  try {
1626
- this.get(BundleSyncService);
1701
+ await this.get(BundleSyncService).ensureReady();
1627
1702
  } catch {
1628
1703
  }
1629
1704
  const scope = this.get(import_sdk5.ScopeEntry);
@@ -1749,21 +1824,12 @@ ${catalog}` : searchSkillDescription;
1749
1824
  logger.warn(`runtime-deps attach threw: ${e.message}`);
1750
1825
  }
1751
1826
  }
1752
- source.onChange((bundle) => {
1753
- void sync.apply(bundle).then((result) => {
1754
- if (!result.applied) {
1755
- logger.warn(`bundle ${bundle.bundleId}@${bundle.version} not applied: ${result.reason}`);
1756
- }
1757
- }).catch((e) => {
1758
- logger.error(`bundle ${bundle.bundleId}@${bundle.version} apply threw: ${e.message}`);
1827
+ sync.attachSource(source);
1828
+ if (!runtimeDeps?.disablePolling) {
1829
+ setImmediate(() => {
1830
+ void sync.ensureReady();
1759
1831
  });
1760
- });
1761
- const startedSource = source;
1762
- setImmediate(() => {
1763
- startedSource.start().catch((e) => {
1764
- logger.error(`bundle source failed to start: ${e.message}`);
1765
- });
1766
- });
1832
+ }
1767
1833
  return sync;
1768
1834
  }
1769
1835
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/plugin-skilled-openapi",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
4
4
  "description": "FrontMCP plugin: serve a customer's OpenAPI spec as skill bundles (signed, OpenAPI-Overlay-based) with hidden per-operation tools mediated by three meta-tools (search_skill / load_skill / run_workflow — the last runs an enclave-sandboxed AgentScript that orchestrates the loaded skills' operations).",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "license": "Apache-2.0",
@@ -48,14 +48,14 @@
48
48
  }
49
49
  },
50
50
  "dependencies": {
51
- "@frontmcp/adapters": "1.5.1",
52
- "@frontmcp/auth": "1.5.1",
53
- "@frontmcp/sdk": "1.5.1"
51
+ "@frontmcp/adapters": "1.5.2",
52
+ "@frontmcp/auth": "1.5.2",
53
+ "@frontmcp/sdk": "1.5.2"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "@enclave-vm/ast": "*",
57
57
  "@enclave-vm/core": "*",
58
- "@frontmcp/lazy-zod": "1.5.1"
58
+ "@frontmcp/lazy-zod": "1.5.2"
59
59
  },
60
60
  "peerDependenciesMeta": {
61
61
  "@enclave-vm/ast": {
@@ -1 +1 @@
1
- {"version":3,"file":"skilled-openapi.plugin.d.ts","sourceRoot":"","sources":["../src/skilled-openapi.plugin.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,gBAAgB,EAIrB,KAAK,iBAAiB,EACvB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,aAAa,EAMb,KAAK,SAAS,EAEd,KAAK,YAAY,EAClB,MAAM,eAAe,CAAC;AAMvB,OAAO,EAEL,KAAK,2BAA2B,EAChC,KAAK,gCAAgC,EACtC,MAAM,yBAAyB,CAAC;AAmBjC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kCAAkC,eAAsD,CAAC;AAEtG;;;;;GAKG;AACH,MAAM,WAAW,yBAA0B,SAAQ,gBAAgB;IACjE;;;;OAIG;IACH,MAAM,CAAC,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC;CAC1C;AAqDD;;;;;;;;;;;;GAYG;AAQH,MAAM,CAAC,OAAO,OAAO,oBAAqB,SAAQ,aAAa,CAC7D,2BAA2B,EAC3B,gCAAgC,CACjC;IACC,OAAO,EAAE,2BAA2B,CAAC;IACrC,OAAO,CAAC,YAAY,CAAC,CAAiB;gBAE1B,OAAO,EAAE,gCAAgC;IAMrD,OAAO,CAAC,SAAS;IAOjB;;;;;;;;;;OAUG;IAEG,gCAAgC,CAAC,OAAO,EAAE,SAAS,CAAC,kBAAkB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAqB7F,OAAO,CAAC,oBAAoB;WAaZ,gBAAgB,CAAC,OAAO,EAAE,gCAAgC,GAAG,YAAY,EAAE;CAsJ5F"}
1
+ {"version":3,"file":"skilled-openapi.plugin.d.ts","sourceRoot":"","sources":["../src/skilled-openapi.plugin.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,gBAAgB,EAIrB,KAAK,iBAAiB,EACvB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAEL,aAAa,EAKb,KAAK,SAAS,EAEd,KAAK,YAAY,EAClB,MAAM,eAAe,CAAC;AAMvB,OAAO,EAEL,KAAK,2BAA2B,EAChC,KAAK,gCAAgC,EACtC,MAAM,yBAAyB,CAAC;AAmBjC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kCAAkC,eAAsD,CAAC;AAEtG;;;;;GAKG;AACH,MAAM,WAAW,yBAA0B,SAAQ,gBAAgB;IACjE;;;;OAIG;IACH,MAAM,CAAC,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC;CAC1C;AAqDD;;;;;;;;;;;;GAYG;AAQH,MAAM,CAAC,OAAO,OAAO,oBAAqB,SAAQ,aAAa,CAC7D,2BAA2B,EAC3B,gCAAgC,CACjC;IACC,OAAO,EAAE,2BAA2B,CAAC;IACrC,OAAO,CAAC,YAAY,CAAC,CAAiB;gBAE1B,OAAO,EAAE,gCAAgC;IAMrD,OAAO,CAAC,SAAS;IAOjB;;;;;;;;;;OAUG;IAEG,gCAAgC,CAAC,OAAO,EAAE,SAAS,CAAC,kBAAkB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAuB7F,OAAO,CAAC,oBAAoB;WAaZ,gBAAgB,CAAC,OAAO,EAAE,gCAAgC,GAAG,YAAY,EAAE;CA0J5F"}
@@ -46,6 +46,11 @@ export interface BundleApplyResult {
46
46
  * Listeners can subscribe via the underlying BundleStore for downstream
47
47
  * effects (audit log, OTel attributes, /healthz updates).
48
48
  */
49
+ /** Minimal slice of a bundle source that {@link BundleSyncService} drives. */
50
+ interface StartableBundleSource {
51
+ start(): Promise<void>;
52
+ onChange(listener: (bundle: ResolvedBundle) => void): () => void;
53
+ }
49
54
  export declare class BundleSyncService {
50
55
  private readonly skillRegistry;
51
56
  private readonly hiddenOps;
@@ -61,6 +66,9 @@ export declare class BundleSyncService {
61
66
  */
62
67
  private readonly operationToolFactory?;
63
68
  private skillUnregisterByBundleId;
69
+ /** The configured bundle source; wired by the DI factory via {@link attachSource}. */
70
+ private source?;
71
+ private readyPromise?;
64
72
  constructor(skillRegistry: SkillRegistryInterface, hiddenOps: HiddenOpRegistry, bundleStore: BundleStore, options: BundleSyncOptions, logger: FrontMcpLogger,
65
73
  /**
66
74
  * Optional factory that registers each operation as an internal SDK tool.
@@ -70,6 +78,38 @@ export declare class BundleSyncService {
70
78
  * tool registry (e.g. unit tests for the sync service in isolation).
71
79
  */
72
80
  operationToolFactory?: OperationToolFactory | undefined);
81
+ /**
82
+ * Wired by the plugin's DI factory. Stores the bundle source so {@link
83
+ * ensureReady} can start it and await the first apply lazily, inside the
84
+ * request that needs the catalog. Does NOT start the source — see the context
85
+ * note on {@link bootstrap}.
86
+ */
87
+ attachSource(source: StartableBundleSource): void;
88
+ /**
89
+ * Ensure the source has started and its FIRST bundle has been applied, so
90
+ * `scope.skills` and the hidden-op registry are populated before a meta-tool
91
+ * (search_skill / load_skill / run_workflow) reads them. Memoized — the
92
+ * underlying start + apply runs at most once per service instance.
93
+ */
94
+ ensureReady(): Promise<void>;
95
+ /**
96
+ * Start the source and await its FIRST bundle apply.
97
+ *
98
+ * CRITICAL — every promise here (the `apply()`, the `firstApply` latch, and
99
+ * the `onChange` wiring) is created inside the FIRST caller's context, which
100
+ * is a request context (the meta-tools / list-tools hook call `ensureReady()`
101
+ * while handling a request). On a stateless V8-isolate runtime (Cloudflare
102
+ * Workers) this is mandatory two ways over:
103
+ * 1. workerd cancels promise continuations that cross a request boundary, so
104
+ * a `firstApply` created in the scope-init/factory context would never
105
+ * resolve when awaited from a later request — it must be created here.
106
+ * 2. There is no background event loop, so a deferred/fire-and-forget apply
107
+ * (the old `setImmediate(source.start())`) can never complete after the
108
+ * response is sent — the awaiting tool drives it to completion inline.
109
+ * A failed first sync is swallowed (logged) so it degrades to "no skills"
110
+ * instead of hanging or 500-ing every call.
111
+ */
112
+ private bootstrap;
73
113
  /**
74
114
  * Validate signature and apply the bundle. Returns a structured result;
75
115
  * never throws on validation/registration failure (caller logs + ignores).
@@ -84,4 +124,5 @@ export declare class BundleSyncService {
84
124
  private toSkillContent;
85
125
  private rebuildHiddenOps;
86
126
  }
127
+ export {};
87
128
  //# sourceMappingURL=bundle-sync.service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bundle-sync.service.d.ts","sourceRoot":"","sources":["../../src/sync/bundle-sync.service.ts"],"names":[],"mappings":"AAEA,OAAO,EASL,KAAK,UAAU,EACf,KAAK,WAAW,EAEhB,KAAK,cAAc,EAEnB,KAAK,wBAAwB,EAC9B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,KAAK,cAAc,EAAqB,KAAK,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAEpG,OAAO,EAAsB,KAAK,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAC3F,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,KAAK,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAE5E,MAAM,WAAW,iBAAiB;IAChC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,WAAW,EAAE,YAAY,EAAE,CAAC;IAC5B;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,wBAAwB,CAAC;IACrC;;;;;;;;OAQG;IACH,+BAA+B,EAAE,OAAO,CAAC;CAC1C;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,iBAAiB;IAI1B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAC;IAfxC,OAAO,CAAC,yBAAyB,CAA0C;gBAGxD,aAAa,EAAE,sBAAsB,EACrC,SAAS,EAAE,gBAAgB,EAC3B,WAAW,EAAE,WAAW,EACxB,OAAO,EAAE,iBAAiB,EAC1B,MAAM,EAAE,cAAc;IACvC;;;;;;OAMG;IACc,oBAAoB,CAAC,EAAE,oBAAoB,YAAA;IAG9D;;;;OAIG;IACG,KAAK,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA0O/D;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,gBAAgB;CAwCzB"}
1
+ {"version":3,"file":"bundle-sync.service.d.ts","sourceRoot":"","sources":["../../src/sync/bundle-sync.service.ts"],"names":[],"mappings":"AAEA,OAAO,EASL,KAAK,UAAU,EACf,KAAK,WAAW,EAEhB,KAAK,cAAc,EAEnB,KAAK,wBAAwB,EAC9B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,KAAK,cAAc,EAAqB,KAAK,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAEpG,OAAO,EAAsB,KAAK,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAC3F,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,KAAK,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAE5E,MAAM,WAAW,iBAAiB;IAChC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,WAAW,EAAE,YAAY,EAAE,CAAC;IAC5B;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,wBAAwB,CAAC;IACrC;;;;;;;;OAQG;IACH,+BAA+B,EAAE,OAAO,CAAC;CAC1C;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AAED;;;;;;;;;;;;GAYG;AACH,8EAA8E;AAC9E,UAAU,qBAAqB;IAC7B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CAClE;AAED,qBAAa,iBAAiB;IAQ1B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAC;IAnBxC,OAAO,CAAC,yBAAyB,CAA0C;IAE3E,sFAAsF;IACtF,OAAO,CAAC,MAAM,CAAC,CAAwB;IACvC,OAAO,CAAC,YAAY,CAAC,CAAgB;gBAGlB,aAAa,EAAE,sBAAsB,EACrC,SAAS,EAAE,gBAAgB,EAC3B,WAAW,EAAE,WAAW,EACxB,OAAO,EAAE,iBAAiB,EAC1B,MAAM,EAAE,cAAc;IACvC;;;;;;OAMG;IACc,oBAAoB,CAAC,EAAE,oBAAoB,YAAA;IAG9D;;;;;OAKG;IACH,YAAY,CAAC,MAAM,EAAE,qBAAqB,GAAG,IAAI;IAIjD;;;;;OAKG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAMlC;;;;;;;;;;;;;;;;OAgBG;YACW,SAAS;IA+CvB;;;;OAIG;IACG,KAAK,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA0O/D;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,gBAAgB;CAwCzB"}
@@ -1 +1 @@
1
- {"version":3,"file":"load-skill.tool.d.ts","sourceRoot":"","sources":["../../src/tools/load-skill.tool.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsD,WAAW,EAAE,MAAM,eAAe,CAAC;AAGhG,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,eAAe,EACrB,MAAM,qBAAqB,CAAC;AAY7B,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,WAAW;IAC9C,OAAO,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;CA8B/D"}
1
+ {"version":3,"file":"load-skill.tool.d.ts","sourceRoot":"","sources":["../../src/tools/load-skill.tool.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsD,WAAW,EAAE,MAAM,eAAe,CAAC;AAGhG,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,eAAe,EACrB,MAAM,qBAAqB,CAAC;AAY7B,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,WAAW;IAC9C,OAAO,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;CAgC/D"}
@@ -1 +1 @@
1
- {"version":3,"file":"run-workflow.tool.d.ts","sourceRoot":"","sources":["../../src/tools/run-workflow.tool.ts"],"names":[],"mappings":"AAUA,OAAO,EAAQ,WAAW,EAAE,MAAM,eAAe,CAAC;AAOlD,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACvB,MAAM,uBAAuB,CAAC;AAkB/B,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,WAAW;IAChD,OAAO,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAmFnE"}
1
+ {"version":3,"file":"run-workflow.tool.d.ts","sourceRoot":"","sources":["../../src/tools/run-workflow.tool.ts"],"names":[],"mappings":"AAUA,OAAO,EAAQ,WAAW,EAAE,MAAM,eAAe,CAAC;AAOlD,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACvB,MAAM,uBAAuB,CAAC;AAkB/B,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,WAAW;IAChD,OAAO,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CA0FnE"}
@@ -1 +1 @@
1
- {"version":3,"file":"search-skill.tool.d.ts","sourceRoot":"","sources":["../../src/tools/search-skill.tool.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoB,WAAW,EAAE,MAAM,eAAe,CAAC;AAG9D,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACvB,MAAM,uBAAuB,CAAC;AAY/B,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,WAAW;IAChD,OAAO,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAiCnE"}
1
+ {"version":3,"file":"search-skill.tool.d.ts","sourceRoot":"","sources":["../../src/tools/search-skill.tool.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoB,WAAW,EAAE,MAAM,eAAe,CAAC;AAG9D,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACvB,MAAM,uBAAuB,CAAC;AAY/B,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,WAAW;IAChD,OAAO,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAkCnE"}