@dbx-tools/appkit-mastra 0.9.120 → 0.9.122

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/package.json CHANGED
@@ -19,6 +19,7 @@
19
19
  "projen": "projen"
20
20
  },
21
21
  "devDependencies": {
22
+ "@mastra/isolated-vm": "0.1.2",
22
23
  "@mlflow/core": "0.4.0",
23
24
  "@types/express": "^5.0.5",
24
25
  "@types/json-schema": "^7",
@@ -34,26 +35,25 @@
34
35
  },
35
36
  "dependencies": {
36
37
  "@databricks/appkit": "0.81.0",
37
- "@dbx-tools/appkit": "^0.9.120",
38
- "@dbx-tools/cache": "^0.9.120",
39
- "@dbx-tools/core": "^0.9.120",
40
- "@dbx-tools/databricks": "^0.9.120",
41
- "@dbx-tools/fs": "^0.9.120",
42
- "@dbx-tools/genie": "^0.9.120",
43
- "@dbx-tools/model": "^0.9.120",
44
- "@dbx-tools/model-protocol": "^0.9.120",
45
- "@dbx-tools/path": "^0.9.120",
46
- "@dbx-tools/postgres": "^0.9.120",
47
- "@dbx-tools/shared-core": "^0.9.120",
48
- "@dbx-tools/shared-fs": "^0.9.120",
49
- "@dbx-tools/shared-genie": "^0.9.120",
50
- "@dbx-tools/shared-mastra": "^0.9.120",
51
- "@dbx-tools/shared-model": "^0.9.120",
38
+ "@dbx-tools/appkit": "^0.9.122",
39
+ "@dbx-tools/cache": "^0.9.122",
40
+ "@dbx-tools/core": "^0.9.122",
41
+ "@dbx-tools/databricks": "^0.9.122",
42
+ "@dbx-tools/fs": "^0.9.122",
43
+ "@dbx-tools/genie": "^0.9.122",
44
+ "@dbx-tools/model": "^0.9.122",
45
+ "@dbx-tools/model-protocol": "^0.9.122",
46
+ "@dbx-tools/path": "^0.9.122",
47
+ "@dbx-tools/postgres": "^0.9.122",
48
+ "@dbx-tools/shared-core": "^0.9.122",
49
+ "@dbx-tools/shared-fs": "^0.9.122",
50
+ "@dbx-tools/shared-genie": "^0.9.122",
51
+ "@dbx-tools/shared-mastra": "^0.9.122",
52
+ "@dbx-tools/shared-model": "^0.9.122",
52
53
  "@mastra/ai-sdk": "1.10.5",
53
54
  "@mastra/core": "1.71.0",
54
55
  "@mastra/express": "1.5.15",
55
56
  "@mastra/fastembed": "1.3.2",
56
- "@mastra/isolated-vm": "0.1.2",
57
57
  "@mastra/mcp": "2.1.0",
58
58
  "@mastra/memory": "1.32.1",
59
59
  "@mastra/observability": "1.18.1",
@@ -219,7 +219,7 @@
219
219
  "./package.json": "./package.json"
220
220
  }
221
221
  },
222
- "version": "0.9.120",
222
+ "version": "0.9.122",
223
223
  "types": "./lib/index.d.ts",
224
224
  "dbxToolsConfig": {
225
225
  "tags": [
@@ -372,6 +372,9 @@
372
372
  "exports.ts",
373
373
  "lib"
374
374
  ],
375
+ "optionalDependencies": {
376
+ "@mastra/isolated-vm": "0.1.2"
377
+ },
375
378
  "peerDependenciesMeta": {
376
379
  "skills": {
377
380
  "optional": true
package/src/agents.ts CHANGED
@@ -39,10 +39,11 @@ import { SkillSearchProcessor, ToolCallFilter } from "@mastra/core/processors";
39
39
  import type { OutputProcessor } from "@mastra/core/processors";
40
40
  import { MASTRA_RESOURCE_ID_KEY } from "@mastra/core/request-context";
41
41
  import type { RequestContext } from "@mastra/core/request-context";
42
+ import { resolveAgentSkills, type InlineSkill } from "@mastra/core/skills";
42
43
  import type { CodeModeTransport, Tool } from "@mastra/core/tools";
43
44
  import { createCodeMode, createTool } from "@mastra/core/tools";
44
45
  import type { Workspace } from "@mastra/core/workspace";
45
- import { createWorkspaceTools } from "@mastra/core/workspace";
46
+ import { createSkillTools, createWorkspaceTools } from "@mastra/core/workspace";
46
47
  import type { PgVectorConfig, PostgresStoreConfig } from "@mastra/pg";
47
48
  import { QuickJsCodeModeTransport } from "@mastra/quickjs";
48
49
 
@@ -706,13 +707,24 @@ export async function buildAgents(opts: {
706
707
  memoryBuilder?: MemoryBuilder;
707
708
  log: log.Logger;
708
709
  /**
709
- * Local skill scan paths (remote skills provisioned to a temp dir at
710
- * startup) folded into every agent that uses the auto-created default
711
- * workspace.
710
+ * Local skill filesystems mounted into every auto-created default workspace.
711
+ * Skill activation comes from {@link standardSkills}, not workspace search.
712
712
  */
713
713
  extraSkillMounts?: DatabricksWorkspaceOptions["extraSkillMounts"];
714
+ /** Fast in-memory skills passed through Mastra's standard agent skill tools. */
715
+ standardSkills?: readonly InlineSkill[];
716
+ /** Standard skill names omitted from slower Databricks workspace search. */
717
+ excludedWorkspaceSkillNames?: readonly string[];
714
718
  }): Promise<BuiltAgents> {
715
- const { config, context, memoryBuilder, log, extraSkillMounts } = opts;
719
+ const {
720
+ config,
721
+ context,
722
+ memoryBuilder,
723
+ log,
724
+ extraSkillMounts,
725
+ standardSkills = [],
726
+ excludedWorkspaceSkillNames = [],
727
+ } = opts;
716
728
  const definitions = resolveDefinitions(config);
717
729
  const ids = Object.keys(definitions);
718
730
  const defaultAgentId = config.defaultAgent ?? ids[0] ?? FALLBACK_AGENT_ID;
@@ -723,13 +735,16 @@ export async function buildAgents(opts: {
723
735
  // offloading text condensing to the fast / small chat tier. The
724
736
  // user can shadow either by including a same-named tool in their own
725
737
  // `config.tools` or per-agent `tools`. Merge order is
726
- // `system -> user-ambient -> per-agent`, last write wins.
738
+ // `system -> standard skills -> user-ambient -> per-agent`, last write wins.
727
739
  const systemTools: MastraTools = {
728
740
  render_data: buildRenderDataTool(config),
729
741
  summarize: buildSummarizeTool(config),
730
742
  };
743
+ const standardSkillTools =
744
+ standardSkills.length > 0 ? createSkillTools(resolveAgentSkills([...standardSkills])) : {};
731
745
  const ambientTools = {
732
746
  ...systemTools,
747
+ ...standardSkillTools,
733
748
  ...(config.tools ?? {}),
734
749
  };
735
750
  const style = resolveStyleInstructions(config);
@@ -764,6 +779,7 @@ export async function buildAgents(opts: {
764
779
  ) {
765
780
  workspace = databricksWorkspace({
766
781
  extraSkillMounts,
782
+ excludeSkillNames: excludedWorkspaceSkillNames,
767
783
  fallbackToTmp: config.workspaceFallbackToTmp,
768
784
  ...(config.workspaceTools !== undefined ? { tools: config.workspaceTools } : {}),
769
785
  pluginContext: context,
@@ -771,7 +787,7 @@ export async function buildAgents(opts: {
771
787
  });
772
788
  markDefaultWorkspace(workspace);
773
789
  } else if (workspace) {
774
- workspace = bindDatabricksWorkspaceContext(workspace, context);
790
+ workspace = bindDatabricksWorkspaceContext(workspace, context, excludedWorkspaceSkillNames);
775
791
  }
776
792
  const gated = approvalGatedToolIds(tools);
777
793
  if (gated.length > 0) approvalGatedByAgent.push({ agentId: id, toolIds: gated });
@@ -1249,7 +1265,7 @@ function toolkitEntryToMastraTool(entry: ToolkitEntry, plugin: ContextualToolPro
1249
1265
  abortSignal?: AbortSignal;
1250
1266
  requestContext?: { get(key: string): unknown };
1251
1267
  writer?: {
1252
- custom(part: { type: string; data: unknown }): Promise<void>;
1268
+ custom(part: { type: string; data: unknown; transient?: boolean }): Promise<void>;
1253
1269
  };
1254
1270
  }
1255
1271
  | undefined;
@@ -1262,12 +1278,13 @@ function toolkitEntryToMastraTool(entry: ToolkitEntry, plugin: ContextualToolPro
1262
1278
  ...(typeof model === "string" ? { model } : {}),
1263
1279
  ...(writer && toolCallId
1264
1280
  ? {
1265
- writeProgress: async (event: unknown) => {
1281
+ writeProgress: async (event, options) => {
1266
1282
  const parsed = ToolProgressEventSchema.parse(event);
1267
1283
  await writer
1268
1284
  .custom({
1269
1285
  type: TOOL_PROGRESS_PART_TYPE,
1270
1286
  data: { toolCallId, event: parsed },
1287
+ ...(options?.transient ? { transient: true } : {}),
1271
1288
  })
1272
1289
  .catch(() => undefined);
1273
1290
  },
package/src/config.ts CHANGED
@@ -658,7 +658,7 @@ export interface MastraPluginConfig extends BasePluginConfig {
658
658
  * ```
659
659
  */
660
660
  remoteSkills?: RemoteSkillsOption;
661
- /** Read-only local skill trees mounted into every default agent workspace. */
661
+ /** Read-only local skill trees mounted for files and passed as standard agent-level skills. */
662
662
  localSkillMounts?: LocalSkillMount[];
663
663
  }
664
664
 
@@ -686,7 +686,7 @@ export const MASTRA_CONFIG_SCHEMA: ConfigSchema = {
686
686
  remoteSkills: {
687
687
  type: ["string", "array", "object"],
688
688
  description:
689
- 'Remote Agent-Skill sources provisioned at startup: "aitools" for Databricks\' own skill set (daily @dbx-tools/cache snapshot served from the shared in-memory /.databricks/skills mount), a GitHub owner/repo, a git/GitLab URL, or a direct SKILL.md/archive URL. Non-aitools sources prefer the optional `skills` npm CLI, else a direct fetch; fails startup on error unless failOnError:false. Other sources write to the Databricks user Assistant skills folder or a read-only local-memory fallback.',
689
+ 'Remote Agent-Skill sources provisioned at startup and passed through Mastra standard agent-level skill tools: "aitools" for Databricks\' own skill set, a GitHub owner/repo, a git/GitLab URL, or a direct SKILL.md/archive URL. Sources retain a process-memory copy even when persisted to the Databricks user Assistant skills folder.',
690
690
  },
691
691
  storage: {
692
692
  type: ["boolean", "object"],
package/src/mlflow.ts CHANGED
@@ -27,7 +27,7 @@
27
27
 
28
28
  import { ConfigurationError, createWorkspaceClient } from "@databricks/appkit";
29
29
  import { appkit } from "@dbx-tools/appkit";
30
- import { asyncUtils, errorUtils, log, object } from "@dbx-tools/shared-core";
30
+ import { asyncUtils, environmentUtils, errorUtils, log, object } from "@dbx-tools/shared-core";
31
31
  import { feedback } from "@dbx-tools/shared-mastra";
32
32
  import { TraceLocationType, TraceMetadataKey } from "@mlflow/core";
33
33
  import { TraceInfo } from "@mlflow/core/dist/core/entities/trace_info.js";
@@ -298,7 +298,7 @@ function configuredExperiment(): { id?: string; name?: string } {
298
298
 
299
299
  /** Tracking URI for direct local MLflow tracing, with CLI-profile auth injected. */
300
300
  export function directMlflowTrackingUri(): string | undefined {
301
- if (appkit.isDatabricksAppEnv()) return undefined;
301
+ if (environmentUtils.isDatabricksAppEnv()) return undefined;
302
302
  const configured = process.env.MLFLOW_TRACKING_URI?.trim();
303
303
  if (configured && configured !== "databricks") return configured;
304
304
  const profile = process.env.DATABRICKS_CONFIG_PROFILE?.trim();
@@ -331,7 +331,7 @@ export function directMlflowTraceLocation():
331
331
  /** Whether the local direct-to-experiment MLflow SDK has enough configuration. */
332
332
  export function directMlflowTracingConfigured(): boolean {
333
333
  return Boolean(
334
- !appkit.isDatabricksAppEnv() &&
334
+ !environmentUtils.isDatabricksAppEnv() &&
335
335
  process.env.MLFLOW_EXPERIMENT_ID?.trim() &&
336
336
  directMlflowTrackingUri(),
337
337
  );
@@ -340,7 +340,7 @@ export function directMlflowTracingConfigured(): boolean {
340
340
  /** Whether a Databricks App can promote OTLP spans into full MLflow trace info. */
341
341
  export function appMlflowTraceInfoConfigured(): boolean {
342
342
  return Boolean(
343
- appkit.isDatabricksAppEnv() &&
343
+ environmentUtils.isDatabricksAppEnv() &&
344
344
  (process.env.MLFLOW_EXPERIMENT_ID?.trim() || process.env.MLFLOW_EXPERIMENT_NAME?.trim()) &&
345
345
  (process.env.OTEL_EXPORTER_OTLP_ENDPOINT?.trim() ||
346
346
  process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT?.trim()),
package/src/plugin.ts CHANGED
@@ -120,6 +120,7 @@ import { resolveServingConfig } from "./serving.ts";
120
120
  import { fetchStatementData, STATEMENT_ROW_CAP } from "./statement.ts";
121
121
  import { attachChatTurnTelemetry } from "./telemetry.ts";
122
122
  import { invalidFields } from "./validation.ts";
123
+ import { inlineSkillsFromLocalMounts } from "./workspaces.ts";
123
124
 
124
125
  const GENIE_MANIFEST = pluginRegistry.data(genie).plugin.manifest;
125
126
  const LAKEBASE_MANIFEST = pluginRegistry.data(lakebase).plugin.manifest;
@@ -1055,23 +1056,34 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
1055
1056
  // Materialize any remote Agent-Skill sources ONCE at startup. Runs
1056
1057
  // outside a request scope, so it uses the app service principal's client
1057
1058
  // and defaults to the shared workspace Assistant skills tree; a source
1058
- // backed by local temp storage (no writable workspace) is folded into
1059
- // every default-workspace agent via `extraSkillMounts`.
1059
+ // backed by local or memory storage is converted into native agent-level
1060
+ // skills; Databricks workspace directories remain search-discovered.
1060
1061
  const provisioned = await provisionRemoteSkills(this.config.remoteSkills);
1061
1062
  if (provisioned.skillNames.length > 0) {
1062
1063
  this.logger.info("remote skills configured", {
1063
1064
  skills: provisioned.skillNames,
1064
1065
  databricksBasePath: provisioned.databricksBasePath,
1065
1066
  localSkillMounts: provisioned.localSkillMounts.length,
1067
+ standardSkillMounts: provisioned.standardSkillMounts.length,
1066
1068
  });
1067
1069
  }
1068
1070
 
1071
+ const localSkillMounts = [
1072
+ ...(this.config.localSkillMounts ?? []),
1073
+ ...provisioned.localSkillMounts,
1074
+ ];
1075
+ const standardSkills = await inlineSkillsFromLocalMounts([
1076
+ ...(this.config.localSkillMounts ?? []),
1077
+ ...provisioned.standardSkillMounts,
1078
+ ]);
1069
1079
  this.built = await buildAgents({
1070
1080
  config: this.config,
1071
1081
  context: this.context,
1072
1082
  memoryBuilder,
1073
1083
  log: this.logger,
1074
- extraSkillMounts: [...(this.config.localSkillMounts ?? []), ...provisioned.localSkillMounts],
1084
+ extraSkillMounts: localSkillMounts,
1085
+ standardSkills,
1086
+ excludedWorkspaceSkillNames: provisioned.skillNames,
1075
1087
  });
1076
1088
 
1077
1089
  // `mastra.server.apiRoutes` is only honored by Mastra's standalone
@@ -26,11 +26,11 @@
26
26
  * source (or the top-level call) sets `failOnError: false`, in which case it is
27
27
  * logged and skipped so one bad source never takes the app down.
28
28
  *
29
- * AI Tools uses the shared `@dbx-tools/cache` project cache with a one-day TTL,
30
- * then hydrates one process-memory filesystem mounted at
31
- * {@link AITOOLS_SKILLS_MOUNT} for every default workspace. Other sources keep
32
- * a {@link METADATA_FILE} beside their provisioned tree and default to a
33
- * seven-day refresh window. Pass `refreshTtlMs: 0` to download on every boot.
29
+ * AI Tools uses the shared `@dbx-tools/cache` project cache with a one-day TTL.
30
+ * Every source hydrates a process-memory filesystem that the plugin converts to
31
+ * native agent-level skills; persisted workspace copies are not used for
32
+ * runtime discovery. Other sources keep a {@link METADATA_FILE} beside their
33
+ * provisioned tree and default to a seven-day refresh window.
34
34
  *
35
35
  * For non-AI-Tools sources, the default destination is the Databricks workspace
36
36
  * Assistant skills tree (`/Workspace/.assistant/skills`). Pass `userEmail` or
@@ -253,12 +253,11 @@ export interface ProvisionRemoteSkillsOptions {
253
253
  /** What {@link provisionRemoteSkills} resolved. */
254
254
  export interface ProvisionedRemoteSkills {
255
255
  /**
256
- * Extra read-only local or process-memory skill mounts to hand Mastra. AI
257
- * Tools always uses process memory; other sources use this fallback when
258
- * they could not be written to Databricks. Physical paths remain hidden
259
- * behind stable virtual workspace paths.
256
+ * Extra read-only filesystem mounts retained for direct workspace file access.
260
257
  */
261
258
  localSkillMounts: LocalSkillMount[];
259
+ /** Process-memory trees converted into standard agent-level skills. */
260
+ standardSkillMounts: LocalSkillMount[];
262
261
  /** Absolute Databricks destination each source was written to, if any. */
263
262
  databricksBasePath?: string;
264
263
  /**
@@ -491,7 +490,11 @@ export async function provisionRemoteSkills(
491
490
  option: RemoteSkillsOption | undefined,
492
491
  ): Promise<ProvisionedRemoteSkills> {
493
492
  const options = normalizeRemoteSkillsOption(option);
494
- const empty: ProvisionedRemoteSkills = { localSkillMounts: [], skillNames: [] };
493
+ const empty: ProvisionedRemoteSkills = {
494
+ localSkillMounts: [],
495
+ standardSkillMounts: [],
496
+ skillNames: [],
497
+ };
495
498
  if (!options) return empty;
496
499
 
497
500
  const failDefault = options.failOnError !== false;
@@ -512,6 +515,7 @@ export async function provisionRemoteSkills(
512
515
  const databricksBasePath = destination ? requestedBasePath : undefined;
513
516
 
514
517
  const localSkillMounts: LocalSkillMount[] = [];
518
+ const standardSkillMounts: LocalSkillMount[] = [];
515
519
  const skillNames: string[] = [];
516
520
  let staging: LocalFileSystem | undefined;
517
521
 
@@ -526,10 +530,12 @@ export async function provisionRemoteSkills(
526
530
  if (isAiToolsSource(sourceOptions.source)) {
527
531
  staging ??= await initializedScratch("mastra-remote-skills");
528
532
  const tree = await cachedAiToolsTree(sourceOptions, staging.root, options, key);
529
- localSkillMounts.push({
533
+ const mount = {
530
534
  filesystem: await memorySkillTree(tree.files, `aitools-${key}`),
531
535
  mountPath,
532
- });
536
+ };
537
+ localSkillMounts.push(mount);
538
+ standardSkillMounts.push(mount);
533
539
  skillNames.push(...tree.record.skills);
534
540
  logger.info("remote skill ready", {
535
541
  source: sourceOptions.source,
@@ -545,6 +551,15 @@ export async function provisionRemoteSkills(
545
551
  const cached = metadata?.sources[key];
546
552
  if (cached && isFresh(cached, resolveRefreshTtl(sourceOptions, options))) {
547
553
  skillNames.push(...cached.skills);
554
+ const standardMount = {
555
+ filesystem: await memorySkillTreeFromFileSystem(
556
+ cacheFS,
557
+ cached.skills,
558
+ `remote-${key}`,
559
+ ),
560
+ mountPath,
561
+ };
562
+ standardSkillMounts.push(standardMount);
548
563
  if (!destination) localSkillMounts.push({ root: cacheFS.root, mountPath });
549
564
  logger.info("remote skill ready", {
550
565
  source: sourceOptions.source,
@@ -584,6 +599,10 @@ export async function provisionRemoteSkills(
584
599
  mountPath,
585
600
  });
586
601
  }
602
+ standardSkillMounts.push({
603
+ filesystem: await memorySkillTree(await serializeSkillDirs(staged), `remote-${key}`),
604
+ mountPath,
605
+ });
587
606
  skillNames.push(...record.skills);
588
607
  logger.info("remote skill installed", {
589
608
  source: sourceOptions.source,
@@ -610,7 +629,7 @@ export async function provisionRemoteSkills(
610
629
  }
611
630
 
612
631
  assertUniqueLocalSkillMounts(localSkillMounts);
613
- return { localSkillMounts, databricksBasePath, skillNames };
632
+ return { localSkillMounts, standardSkillMounts, databricksBasePath, skillNames };
614
633
  }
615
634
 
616
635
  async function cachedAiToolsTree(
@@ -685,6 +704,27 @@ async function memorySkillTree(
685
704
  return filesystem;
686
705
  }
687
706
 
707
+ async function memorySkillTreeFromFileSystem(
708
+ source: FileSystem,
709
+ skillNames: readonly string[],
710
+ id: string,
711
+ ): Promise<MemoryFileSystem> {
712
+ await source.init();
713
+ const files: CachedSkillFile[] = [];
714
+ for (const skill of skillNames) {
715
+ for (const entry of await source.readdir(skill, { recursive: true })) {
716
+ if (entry.type !== "file") continue;
717
+ const path = posix.join(skill, entry.name);
718
+ const content = await source.readFile(path);
719
+ files.push({
720
+ path,
721
+ content: Buffer.from(content).toString("base64"),
722
+ });
723
+ }
724
+ }
725
+ return memorySkillTree(files, id);
726
+ }
727
+
688
728
  /** Fail before workspace composition when two sources claim one virtual mount. */
689
729
  function assertUniqueLocalSkillMounts(mounts: readonly LocalSkillMount[]): void {
690
730
  const rootsByMount = new Map<string, string>();
package/src/telemetry.ts CHANGED
@@ -20,9 +20,8 @@
20
20
 
21
21
  import { StringDecoder } from "node:string_decoder";
22
22
 
23
- import { appkit } from "@dbx-tools/appkit";
24
23
  import { getRequestTags } from "@dbx-tools/appkit/request-tags";
25
- import { json, log, object, stringUtils } from "@dbx-tools/shared-core";
24
+ import { environmentUtils, json, log, object, stringUtils } from "@dbx-tools/shared-core";
26
25
  import { feedback, thread } from "@dbx-tools/shared-mastra";
27
26
  import { context, createContextKey, SpanKind, trace, type Span } from "@opentelemetry/api";
28
27
  import { getRPCMetadata, RPCType } from "@opentelemetry/core";
@@ -488,7 +487,7 @@ export function chatTurnTelemetryMiddleware(
488
487
  }
489
488
  const traceState: ChatTraceState = { tags: new Map() };
490
489
  const traceRequestTime = Date.now();
491
- if (!appkit.isDatabricksAppEnv()) {
490
+ if (!environmentUtils.isDatabricksAppEnv()) {
492
491
  setChatTraceTag(traceState, MLFLOW_LOCAL_TAG, true);
493
492
  }
494
493
  for (const [name, value] of Object.entries(getRequestTags(req))) {
package/src/workspaces.ts CHANGED
@@ -36,6 +36,7 @@ import {
36
36
  MemoryFileSystem,
37
37
  } from "@dbx-tools/shared-fs";
38
38
  import type { RequestContext } from "@mastra/core/request-context";
39
+ import { createSkill, validateSkillContent, type InlineSkill } from "@mastra/core/skills";
39
40
  import {
40
41
  Workspace,
41
42
  type SkillsResolver,
@@ -208,13 +209,12 @@ export interface DatabricksWorkspaceOptions extends Omit<
208
209
  /** Add user-scoped `/tmp` only when no configured Databricks path mounts writable. */
209
210
  fallbackToTmp?: boolean;
210
211
  /**
211
- * Extra LOCAL skill trees mounted read-only for every request's skill
212
- * discovery. Used by the plugin to expose remote skills from local backing
213
- * storage at stable virtual paths (see `remote-skills.ts`). Databricks-hosted
214
- * remote skills need no entry here because the built-in Assistant mount
215
- * already scans them.
212
+ * Extra local skill trees mounted read-only for file access. The plugin also
213
+ * converts them to native agent-level skills without workspace scanning.
216
214
  */
217
215
  extraSkillMounts?: LocalSkillMount[];
216
+ /** Skill names provided directly to agents and excluded from workspace search. */
217
+ excludeSkillNames?: readonly string[];
218
218
  }
219
219
 
220
220
  /* ------------------------------- defaults ------------------------------- */
@@ -292,7 +292,8 @@ export function databricksWorkspaceConfig(
292
292
  retainFilesystemSource ? mountFilesCache : undefined,
293
293
  filesystemSourceKey,
294
294
  );
295
- const hasSkillPaths = paths.some(pathMayProvideSkills) || extraSkillMounts.length > 0;
295
+ const hasSkillPaths = paths.some(pathMayProvideSkills);
296
+ const excludedSkillNames = new Set(options.excludeSkillNames ?? []);
296
297
  const skills: SkillsResolver =
297
298
  options.skills ??
298
299
  (async ({ requestContext }) => {
@@ -301,6 +302,7 @@ export function databricksWorkspaceConfig(
301
302
  return resolveDistinctSkillPaths(
302
303
  await resolveFilesystem(context),
303
304
  contribution.skillPaths ?? [],
305
+ excludedSkillNames,
304
306
  );
305
307
  });
306
308
  const checkSkillFileMtime = options.checkSkillFileMtime ?? false;
@@ -318,6 +320,7 @@ export function databricksWorkspaceConfig(
318
320
  checkSkillFileMtime,
319
321
  bm25,
320
322
  extraSkillMounts: extraSkillMounts.length,
323
+ excludedSkillNames: excludedSkillNames.size,
321
324
  sandbox: sandbox ? sandboxName(options.sandbox) : "disabled",
322
325
  });
323
326
 
@@ -331,6 +334,7 @@ export function databricksWorkspaceConfig(
331
334
  sandbox: _sandbox,
332
335
  fallbackToTmp: _fallbackToTmp,
333
336
  extraSkillMounts: _extraSkillMounts,
337
+ excludeSkillNames: _excludeSkillNames,
334
338
  ...workspaceOptions
335
339
  } = options;
336
340
  return {
@@ -366,12 +370,22 @@ export function databricksWorkspace(options: DatabricksWorkspaceOptions = {}): W
366
370
  export function bindDatabricksWorkspaceContext(
367
371
  workspace: Workspace,
368
372
  pluginContext: pluginRegistry.PluginContextLike | undefined,
373
+ excludeSkillNames: readonly string[] = [],
369
374
  ): Workspace {
370
375
  const options = databricksWorkspaceOptions.get(workspace);
371
- if (!options || options.pluginContext !== undefined || pluginContext === undefined) {
376
+ if (!options) return workspace;
377
+ const excluded = [...new Set([...(options.excludeSkillNames ?? []), ...excludeSkillNames])];
378
+ if (
379
+ (options.pluginContext !== undefined || pluginContext === undefined) &&
380
+ excluded.length === (options.excludeSkillNames?.length ?? 0)
381
+ ) {
372
382
  return workspace;
373
383
  }
374
- return databricksWorkspace({ ...options, pluginContext });
384
+ return databricksWorkspace({
385
+ ...options,
386
+ ...(pluginContext !== undefined ? { pluginContext } : {}),
387
+ ...(excluded.length > 0 ? { excludeSkillNames: excluded } : {}),
388
+ });
375
389
  }
376
390
 
377
391
  /**
@@ -806,12 +820,60 @@ function localSkillMountResolvers(mounts: readonly LocalSkillMount[]): Workspace
806
820
  const source = await filesystem;
807
821
  return {
808
822
  mounts: { [mount]: filesystems(source, { readOnly: true }) },
809
- skillPaths: [mount],
810
823
  };
811
824
  };
812
825
  });
813
826
  }
814
827
 
828
+ /** Convert fast local or in-memory skill trees into native agent-level skills. */
829
+ export async function inlineSkillsFromLocalMounts(
830
+ mounts: readonly LocalSkillMount[],
831
+ ): Promise<InlineSkill[]> {
832
+ const selected = new Map<string, InlineSkill>();
833
+ for (const mount of mounts) {
834
+ const filesystem = await inMemorySkillFilesystem(mount);
835
+ const entries = await filesystem.readdir("/");
836
+ for (const entry of entries) {
837
+ if (entry.type !== "directory" || selected.has(entry.name)) continue;
838
+ const skillPath = entry.name;
839
+ const skillFile = `${skillPath}/SKILL.md`;
840
+ if (!(await filesystem.exists(skillFile))) continue;
841
+ const content = await filesystem.readFile(skillFile, { encoding: "utf8" });
842
+ const parsed = validateSkillContent({
843
+ content: String(content),
844
+ directoryName: entry.name,
845
+ });
846
+ if (!parsed.valid || !parsed.metadata || parsed.instructions === undefined) {
847
+ throw new Error(`Invalid local skill ${entry.name}: ${parsed.errors.join("; ")}`);
848
+ }
849
+ const references: Record<string, string> = {};
850
+ for (const file of await filesystem.readdir(skillPath, { recursive: true })) {
851
+ if (file.type !== "file" || file.name === "SKILL.md") continue;
852
+ if (!/\.(md|txt|json|ya?ml|toml|sh|[cm]?[jt]sx?|py)$/i.test(file.name)) continue;
853
+ references[file.name] = String(
854
+ await filesystem.readFile(`${skillPath}/${file.name}`, { encoding: "utf8" }),
855
+ );
856
+ }
857
+ const metadata = parsed.metadata;
858
+ selected.set(
859
+ entry.name,
860
+ createSkill({
861
+ name: String(metadata.name),
862
+ description: String(metadata.description),
863
+ instructions: parsed.instructions,
864
+ ...(typeof metadata.license === "string" ? { license: metadata.license } : {}),
865
+ ...(metadata.compatibility !== undefined
866
+ ? { compatibility: metadata.compatibility }
867
+ : {}),
868
+ ...(object.isRecord(metadata.metadata) ? { metadata: metadata.metadata } : {}),
869
+ ...(Object.keys(references).length > 0 ? { references } : {}),
870
+ }),
871
+ );
872
+ }
873
+ }
874
+ return [...selected.values()];
875
+ }
876
+
815
877
  function inMemorySkillFilesystem(mount: LocalSkillMount): Promise<FileSystem> {
816
878
  const key = "root" in mount ? `root:${mount.root}` : `filesystem:${mount.filesystem.id}`;
817
879
  const existing = sharedSkillFilesystems.get(key);
@@ -1062,6 +1124,7 @@ async function resolveWorkspaceFilesystem(
1062
1124
  async function resolveDistinctSkillPaths(
1063
1125
  filesystem: WorkspaceFilesystem,
1064
1126
  paths: readonly string[],
1127
+ excludedSkillNames: ReadonlySet<string>,
1065
1128
  ): Promise<string[]> {
1066
1129
  const selected = new Map<string, string>();
1067
1130
  for (const path of paths) {
@@ -1069,11 +1132,17 @@ async function resolveDistinctSkillPaths(
1069
1132
  const entries = await filesystem.readdir(path);
1070
1133
  if (entries.some((entry) => entry.type === "file" && entry.name === "SKILL.md")) {
1071
1134
  const name = posixPath.basename(path);
1072
- if (!selected.has(name)) selected.set(name, path);
1135
+ if (!excludedSkillNames.has(name) && !selected.has(name)) selected.set(name, path);
1073
1136
  continue;
1074
1137
  }
1075
1138
  for (const entry of entries) {
1076
- if (entry.type !== "directory" || selected.has(entry.name)) continue;
1139
+ if (
1140
+ entry.type !== "directory" ||
1141
+ excludedSkillNames.has(entry.name) ||
1142
+ selected.has(entry.name)
1143
+ ) {
1144
+ continue;
1145
+ }
1077
1146
  selected.set(entry.name, posixPath.join(path, entry.name));
1078
1147
  }
1079
1148
  } catch (error) {