@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/README.md +22 -14
- package/index.ts +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +2 -2
- package/lib/src/agents.d.ts +7 -3
- package/lib/src/agents.js +11 -6
- package/lib/src/config.d.ts +1 -1
- package/lib/src/config.js +2 -2
- package/lib/src/mlflow.js +5 -6
- package/lib/src/plugin.js +16 -4
- package/lib/src/remote-skills.d.ts +8 -9
- package/lib/src/remote-skills.js +43 -10
- package/lib/src/telemetry.js +3 -4
- package/lib/src/workspaces.d.ts +7 -5
- package/lib/src/workspaces.js +69 -11
- package/package.json +20 -17
- package/src/agents.ts +26 -9
- package/src/config.ts +2 -2
- package/src/mlflow.ts +4 -4
- package/src/plugin.ts +15 -3
- package/src/remote-skills.ts +53 -13
- package/src/telemetry.ts +2 -3
- package/src/workspaces.ts +80 -11
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.
|
|
38
|
-
"@dbx-tools/cache": "^0.9.
|
|
39
|
-
"@dbx-tools/core": "^0.9.
|
|
40
|
-
"@dbx-tools/databricks": "^0.9.
|
|
41
|
-
"@dbx-tools/fs": "^0.9.
|
|
42
|
-
"@dbx-tools/genie": "^0.9.
|
|
43
|
-
"@dbx-tools/model": "^0.9.
|
|
44
|
-
"@dbx-tools/model-protocol": "^0.9.
|
|
45
|
-
"@dbx-tools/path": "^0.9.
|
|
46
|
-
"@dbx-tools/postgres": "^0.9.
|
|
47
|
-
"@dbx-tools/shared-core": "^0.9.
|
|
48
|
-
"@dbx-tools/shared-fs": "^0.9.
|
|
49
|
-
"@dbx-tools/shared-genie": "^0.9.
|
|
50
|
-
"@dbx-tools/shared-mastra": "^0.9.
|
|
51
|
-
"@dbx-tools/shared-model": "^0.9.
|
|
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.
|
|
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
|
|
710
|
-
*
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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
|
-
!
|
|
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
|
-
|
|
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
|
|
1059
|
-
//
|
|
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:
|
|
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
|
package/src/remote-skills.ts
CHANGED
|
@@ -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
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* a {@link METADATA_FILE} beside their
|
|
33
|
-
*
|
|
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
|
|
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 = {
|
|
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
|
-
|
|
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 (!
|
|
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
|
|
212
|
-
*
|
|
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)
|
|
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
|
|
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({
|
|
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 (
|
|
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) {
|