@dbx-tools/appkit-mastra 0.9.79 → 0.9.95

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.
Files changed (42) hide show
  1. package/README.md +90 -48
  2. package/index.ts +4 -12
  3. package/lib/index.d.ts +4 -12
  4. package/lib/index.js +4 -9
  5. package/lib/src/agents.js +11 -23
  6. package/lib/src/chat.js +20 -1
  7. package/lib/src/config.d.ts +9 -11
  8. package/lib/src/config.js +7 -7
  9. package/lib/src/filesystems.d.ts +20 -2
  10. package/lib/src/filesystems.js +29 -2
  11. package/lib/src/monty-sandbox.d.ts +4 -3
  12. package/lib/src/monty-sandbox.js +54 -21
  13. package/lib/src/plugin.js +3 -2
  14. package/lib/src/remote-skills.d.ts +3 -4
  15. package/lib/src/remote-skills.js +5 -14
  16. package/lib/src/server.d.ts +7 -0
  17. package/lib/src/server.js +33 -2
  18. package/lib/src/skill-paths.d.ts +8 -4
  19. package/lib/src/skill-paths.js +12 -6
  20. package/lib/src/workspaces.d.ts +39 -33
  21. package/lib/src/workspaces.js +208 -168
  22. package/lib/tsconfig.tsbuildinfo +1 -1
  23. package/package.json +14 -38
  24. package/src/agents.ts +11 -24
  25. package/src/chat.ts +24 -0
  26. package/src/config.ts +15 -17
  27. package/src/filesystems.ts +43 -0
  28. package/src/monty-sandbox.ts +61 -22
  29. package/src/plugin.ts +2 -0
  30. package/src/remote-skills.ts +5 -15
  31. package/src/server.ts +33 -1
  32. package/src/skill-paths.ts +13 -5
  33. package/src/workspaces.ts +285 -210
  34. package/lib/src/monty-native.d.ts +0 -40
  35. package/lib/src/monty-native.js +0 -100
  36. package/lib/src/skill-cache.d.ts +0 -94
  37. package/lib/src/skill-cache.js +0 -251
  38. package/lib/src/skill-search.d.ts +0 -30
  39. package/lib/src/skill-search.js +0 -171
  40. package/src/monty-native.ts +0 -149
  41. package/src/skill-cache.ts +0 -347
  42. package/src/skill-search.ts +0 -203
@@ -1 +1 @@
1
- {"root":["../src/agents.ts","../src/chart.ts","../src/chat.ts","../src/config.ts","../src/defaults.ts","../src/evaluation.ts","../src/filesystems.ts","../src/genie.ts","../src/identity.ts","../src/mcp.ts","../src/memory.ts","../src/mlflow.ts","../src/model.ts","../src/monty-native.ts","../src/monty-sandbox.ts","../src/observability.ts","../src/openapi-tool.ts","../src/plugin.ts","../src/processors.ts","../src/remote-skills.ts","../src/rest.ts","../src/sandbox.ts","../src/server.ts","../src/serving-sanitize.ts","../src/serving.ts","../src/skill-cache.ts","../src/skill-paths.ts","../src/skill-search.ts","../src/statement.ts","../src/storage-schema.ts","../src/style.ts","../src/summarize.ts","../src/telemetry.ts","../src/trace-attributes.ts","../src/validation.ts","../src/workspaces.ts","../src/writer.ts","../index.ts"],"version":"5.9.3"}
1
+ {"root":["../src/agents.ts","../src/chart.ts","../src/chat.ts","../src/config.ts","../src/defaults.ts","../src/evaluation.ts","../src/filesystems.ts","../src/genie.ts","../src/identity.ts","../src/mcp.ts","../src/memory.ts","../src/mlflow.ts","../src/model.ts","../src/monty-sandbox.ts","../src/observability.ts","../src/openapi-tool.ts","../src/plugin.ts","../src/processors.ts","../src/remote-skills.ts","../src/rest.ts","../src/sandbox.ts","../src/server.ts","../src/serving-sanitize.ts","../src/serving.ts","../src/skill-paths.ts","../src/statement.ts","../src/storage-schema.ts","../src/style.ts","../src/summarize.ts","../src/telemetry.ts","../src/trace-attributes.ts","../src/validation.ts","../src/workspaces.ts","../src/writer.ts","../index.ts"],"version":"5.9.3"}
package/package.json CHANGED
@@ -34,19 +34,19 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@databricks/appkit": "0.81.0",
37
- "@dbx-tools/appkit": "^0.9.79",
38
- "@dbx-tools/core": "^0.9.79",
39
- "@dbx-tools/databricks": "^0.9.79",
40
- "@dbx-tools/fs": "^0.9.79",
41
- "@dbx-tools/genie": "^0.9.79",
42
- "@dbx-tools/model": "^0.9.79",
43
- "@dbx-tools/path": "^0.9.79",
44
- "@dbx-tools/postgres": "^0.9.79",
45
- "@dbx-tools/shared-core": "^0.9.79",
46
- "@dbx-tools/shared-fs": "^0.9.79",
47
- "@dbx-tools/shared-genie": "^0.9.79",
48
- "@dbx-tools/shared-mastra": "^0.9.79",
49
- "@dbx-tools/shared-model": "^0.9.79",
37
+ "@dbx-tools/appkit": "^0.9.95",
38
+ "@dbx-tools/core": "^0.9.95",
39
+ "@dbx-tools/databricks": "^0.9.95",
40
+ "@dbx-tools/fs": "^0.9.95",
41
+ "@dbx-tools/genie": "^0.9.95",
42
+ "@dbx-tools/model": "^0.9.95",
43
+ "@dbx-tools/path": "^0.9.95",
44
+ "@dbx-tools/postgres": "^0.9.95",
45
+ "@dbx-tools/shared-core": "^0.9.95",
46
+ "@dbx-tools/shared-fs": "^0.9.95",
47
+ "@dbx-tools/shared-genie": "^0.9.95",
48
+ "@dbx-tools/shared-mastra": "^0.9.95",
49
+ "@dbx-tools/shared-model": "^0.9.95",
50
50
  "@mastra/ai-sdk": "1.10.5",
51
51
  "@mastra/core": "1.71.0",
52
52
  "@mastra/express": "1.5.15",
@@ -132,10 +132,6 @@
132
132
  "types": "./lib/src/model.d.ts",
133
133
  "default": "./lib/src/model.js"
134
134
  },
135
- "./monty-native": {
136
- "types": "./lib/src/monty-native.d.ts",
137
- "default": "./lib/src/monty-native.js"
138
- },
139
135
  "./monty-sandbox": {
140
136
  "types": "./lib/src/monty-sandbox.d.ts",
141
137
  "default": "./lib/src/monty-sandbox.js"
@@ -180,18 +176,10 @@
180
176
  "types": "./lib/src/serving.d.ts",
181
177
  "default": "./lib/src/serving.js"
182
178
  },
183
- "./skill-cache": {
184
- "types": "./lib/src/skill-cache.d.ts",
185
- "default": "./lib/src/skill-cache.js"
186
- },
187
179
  "./skill-paths": {
188
180
  "types": "./lib/src/skill-paths.d.ts",
189
181
  "default": "./lib/src/skill-paths.js"
190
182
  },
191
- "./skill-search": {
192
- "types": "./lib/src/skill-search.d.ts",
193
- "default": "./lib/src/skill-search.js"
194
- },
195
183
  "./statement": {
196
184
  "types": "./lib/src/statement.d.ts",
197
185
  "default": "./lib/src/statement.js"
@@ -231,7 +219,7 @@
231
219
  "./package.json": "./package.json"
232
220
  }
233
221
  },
234
- "version": "0.9.79",
222
+ "version": "0.9.95",
235
223
  "types": "./lib/index.d.ts",
236
224
  "dbxToolsConfig": {
237
225
  "tags": [
@@ -296,10 +284,6 @@
296
284
  "types": "./lib/src/model.d.ts",
297
285
  "default": "./lib/src/model.js"
298
286
  },
299
- "./monty-native": {
300
- "types": "./lib/src/monty-native.d.ts",
301
- "default": "./lib/src/monty-native.js"
302
- },
303
287
  "./monty-sandbox": {
304
288
  "types": "./lib/src/monty-sandbox.d.ts",
305
289
  "default": "./lib/src/monty-sandbox.js"
@@ -344,18 +328,10 @@
344
328
  "types": "./lib/src/serving.d.ts",
345
329
  "default": "./lib/src/serving.js"
346
330
  },
347
- "./skill-cache": {
348
- "types": "./lib/src/skill-cache.d.ts",
349
- "default": "./lib/src/skill-cache.js"
350
- },
351
331
  "./skill-paths": {
352
332
  "types": "./lib/src/skill-paths.d.ts",
353
333
  "default": "./lib/src/skill-paths.js"
354
334
  },
355
- "./skill-search": {
356
- "types": "./lib/src/skill-search.d.ts",
357
- "default": "./lib/src/skill-search.js"
358
- },
359
335
  "./statement": {
360
336
  "types": "./lib/src/statement.d.ts",
361
337
  "default": "./lib/src/statement.js"
package/src/agents.ts CHANGED
@@ -49,10 +49,9 @@ import type { MemoryBuilder } from "./memory.ts";
49
49
  import { buildModel, RESPONSES_PROVIDER_OPTIONS } from "./model.ts";
50
50
  import { stripStaleChartsProcessor } from "./processors.ts";
51
51
  import { MASTRA_RESOLVED_MODEL_KEY } from "./serving.ts";
52
- import { CatalogueSkillSearchProcessor } from "./skill-search.ts";
53
52
  import { TYPOGRAPHY_RULE } from "./style.ts";
54
53
  import { buildSummarizeTool } from "./summarize.ts";
55
- import { createWorkspace, workspaceSkillCatalogueResolver } from "./workspaces.ts";
54
+ import { createWorkspace } from "./workspaces.ts";
56
55
 
57
56
  /**
58
57
  * Tool record accepted by every Mastra `Agent.tools` field and by the
@@ -188,16 +187,16 @@ export function createAgent<
188
187
  * options, so {@link buildAgents} may rebuild it with startup-provisioned
189
188
  * `extraSkillPaths` (a caller-supplied workspace is never touched).
190
189
  */
191
- const DEFAULT_WORKSPACE = Symbol.for("dbx-tools/appkit-mastra/default-workspace");
190
+ const defaultWorkspaces = new WeakSet<Workspace>();
192
191
 
193
192
  /** Brand `workspace` as the auto-created default. */
194
193
  function markDefaultWorkspace(workspace: Workspace): void {
195
- (workspace as unknown as Record<symbol, boolean>)[DEFAULT_WORKSPACE] = true;
194
+ defaultWorkspaces.add(workspace);
196
195
  }
197
196
 
198
197
  /** Whether `workspace` was auto-created by {@link createAgent}. */
199
198
  function isDefaultWorkspace(workspace: Workspace | undefined): boolean {
200
- return Boolean(workspace && (workspace as unknown as Record<symbol, boolean>)[DEFAULT_WORKSPACE]);
199
+ return workspace !== undefined && defaultWorkspaces.has(workspace);
201
200
  }
202
201
 
203
202
  /**
@@ -577,14 +576,14 @@ export async function buildAgents(opts: {
577
576
  (def.workspace === undefined && !workspace) ||
578
577
  ((extraSkillPaths?.length ||
579
578
  config.sandbox !== undefined ||
580
- config.workspaceSkillRefreshTtlMs !== undefined) &&
579
+ config.workspaceTools !== undefined ||
580
+ context !== undefined) &&
581
581
  isDefaultWorkspace(workspace))
582
582
  ) {
583
583
  workspace = createWorkspace({
584
584
  extraSkillPaths,
585
- ...(config.workspaceSkillRefreshTtlMs !== undefined
586
- ? { workspaceSkillRefreshTtlMs: config.workspaceSkillRefreshTtlMs }
587
- : {}),
585
+ ...(config.workspaceTools !== undefined ? { tools: config.workspaceTools } : {}),
586
+ pluginContext: context,
588
587
  sandbox: config.sandbox === true ? "databricks" : config.sandbox,
589
588
  });
590
589
  markDefaultWorkspace(workspace);
@@ -668,21 +667,9 @@ function toolErrorLoggingProcessor(logger: log.Logger): OutputProcessor {
668
667
  function workspaceSkillInputProcessors(
669
668
  workspace: Workspace | undefined,
670
669
  config: MastraPluginConfig,
671
- ): Array<SkillSearchProcessor | CatalogueSkillSearchProcessor> {
672
- if (!workspace || config.workspaceSkillSearch === false) return [];
673
- const options =
674
- typeof config.workspaceSkillSearch === "object" ? config.workspaceSkillSearch : undefined;
675
- const catalogue = workspaceSkillCatalogueResolver(workspace);
676
- if (catalogue) {
677
- return [
678
- new CatalogueSkillSearchProcessor({
679
- resolve: catalogue,
680
- topK: options?.topK ?? 5,
681
- minScore: options?.minScore ?? 0.1,
682
- ...(options?.ttlMs !== undefined ? { ttlMs: options.ttlMs } : {}),
683
- }),
684
- ];
685
- }
670
+ ): SkillSearchProcessor[] {
671
+ if (!workspace || config.workspaceSkills === false) return [];
672
+ const options = typeof config.workspaceSkills === "object" ? config.workspaceSkills : undefined;
686
673
  if (!workspace.skills) return [];
687
674
  return [
688
675
  new SkillSearchProcessor({
package/src/chat.ts CHANGED
@@ -4,9 +4,16 @@
4
4
  * @module
5
5
  */
6
6
 
7
+ import { errorUtils, log } from "@dbx-tools/shared-core";
7
8
  import { routes } from "@dbx-tools/shared-mastra";
9
+ import {
10
+ isStaleMastraResumeError,
11
+ STALE_MASTRA_RESUME_STREAM_TEXT,
12
+ } from "@dbx-tools/shared-mastra/resume";
8
13
  import { chatRoute } from "@mastra/ai-sdk";
9
14
 
15
+ const logger = log.logger("mastra/chat");
16
+
10
17
  /** Register the official AI SDK chat stream plus its approval continuation. */
11
18
  export function agentChatRoutes() {
12
19
  const path = `${routes.MASTRA_ROUTES.chat}/:agentId` as `${string}:agentId`;
@@ -15,6 +22,23 @@ export function agentChatRoutes() {
15
22
  path,
16
23
  sendReasoning: true,
17
24
  sendSources: true,
25
+ onError: serializeChatStreamError,
18
26
  }),
19
27
  ];
20
28
  }
29
+
30
+ /**
31
+ * Keep a late or duplicate resume from failing the UI stream. Mastra's
32
+ * `chatRoute` already swallows a couple of resume ids on the v6 approval
33
+ * path; this covers the generic workflow errors `consumeStream` still
34
+ * surfaces on v5.
35
+ */
36
+ function serializeChatStreamError(error: unknown): string {
37
+ if (isStaleMastraResumeError(error)) {
38
+ logger.warn("ignored stale mastra resume", {
39
+ error: errorUtils.errorMessage(error),
40
+ });
41
+ return STALE_MASTRA_RESUME_STREAM_TEXT;
42
+ }
43
+ return errorUtils.errorMessage(error);
44
+ }
package/src/config.ts CHANGED
@@ -20,6 +20,7 @@ import type { BrandContext } from "@dbx-tools/shared-core";
20
20
  import type { AgentConfig } from "@mastra/core/agent";
21
21
  import type { RequestContext } from "@mastra/core/request-context";
22
22
  import { MASTRA_RESOURCE_ID_KEY, MASTRA_THREAD_ID_KEY } from "@mastra/core/request-context";
23
+ import type { WorkspaceToolsConfig } from "@mastra/core/workspace";
23
24
  import type { PgVectorConfig, PostgresStoreConfig } from "@mastra/pg";
24
25
 
25
26
  import type { AnyMastraAgentDefinition, MastraTools } from "./agents.ts";
@@ -147,8 +148,8 @@ export type MastraMemoryConfig = PgVectorConfig & {
147
148
  id?: string;
148
149
  };
149
150
 
150
- /** On-demand workspace skill search configuration. */
151
- export interface MastraSkillSearchConfig {
151
+ /** Mastra native workspace skill-search configuration. */
152
+ export interface MastraWorkspaceSkillsConfig {
152
153
  /** Maximum skill matches returned by one search. Defaults to 5. */
153
154
  topK?: number;
154
155
  /** Minimum BM25 relevance score. Defaults to 0.1. */
@@ -217,16 +218,13 @@ export interface MastraPluginConfig extends BasePluginConfig {
217
218
  * custom `workspace` keeps that workspace and its sandbox.
218
219
  */
219
220
  sandbox?: boolean | "databricks" | "monty" | DatabricksWorkspaceSandboxOptions;
221
+ /** Native Mastra workspace tool availability, approval, and hook configuration. */
222
+ workspaceTools?: WorkspaceToolsConfig;
220
223
  /**
221
- * Refresh TTL for the complete user-scoped workspace skill catalogue.
222
- * Defaults to five minutes; auxiliary files remain uncached.
224
+ * Use Mastra's native workspace skill manager. Defaults to true. Pass an
225
+ * object to tune on-demand discovery and loaded-skill state.
223
226
  */
224
- workspaceSkillRefreshTtlMs?: number;
225
- /**
226
- * Use dbx-tools catalogue-backed on-demand skill search. Defaults to true.
227
- * Pass an object to tune search.
228
- */
229
- workspaceSkillSearch?: boolean | MastraSkillSearchConfig;
227
+ workspaceSkills?: boolean | MastraWorkspaceSkillsConfig;
230
228
  /**
231
229
  * Code-defined agents. Accepts three shapes for convenience:
232
230
  *
@@ -613,7 +611,7 @@ export interface MastraPluginConfig extends BasePluginConfig {
613
611
  * through neither fails startup unless `failOnError: false`.
614
612
  *
615
613
  * Provisioned skills are written to the Databricks user's Assistant skills
616
- * folder (`/Users/<email>/.assistant/skills`, the "save this as a skill"
614
+ * folder (`/Workspace/Users/<email>/.assistant/skills`, the personal skill
617
615
  * target) so they persist and are picked up by the built-in Assistant-skills
618
616
  * mount. With no writable workspace, they go to a local temp dir instead.
619
617
  *
@@ -667,15 +665,15 @@ export const MASTRA_CONFIG_SCHEMA: ConfigSchema = {
667
665
  description:
668
666
  'Command sandbox for auto-created agent workspaces. Defaults to Monty; false disables command execution, and true, "databricks", or an object selects/configures Databricks Sandbox with optional fallback.',
669
667
  },
670
- workspaceSkillRefreshTtlMs: {
671
- type: "number",
668
+ workspaceSkills: {
669
+ type: ["boolean", "object"],
672
670
  description:
673
- "Refresh TTL in milliseconds for the complete user-scoped workspace skill catalogue. Defaults to 5 minutes; auxiliary file reads stay fresh.",
671
+ "Use Mastra's native on-demand workspace skill search and writable skill saves. Defaults to true.",
674
672
  },
675
- workspaceSkillSearch: {
676
- type: ["boolean", "object"],
673
+ workspaceTools: {
674
+ type: "object",
677
675
  description:
678
- "Use dbx-tools catalogue-backed on-demand skill search with lazy auxiliary reads. Defaults to true.",
676
+ "Native Mastra workspace tool configuration for availability, approvals, read-before-write, output limits, and hooks.",
679
677
  },
680
678
  defaultAgent: {
681
679
  type: "string",
@@ -22,12 +22,14 @@ import {
22
22
  FileExistsError,
23
23
  FileNotFoundError,
24
24
  IsDirectoryError,
25
+ CompositeFilesystem,
25
26
  MastraFilesystem,
26
27
  NotDirectoryError,
27
28
  PermissionError,
28
29
  WorkspaceReadOnlyError,
29
30
  } from "@mastra/core/workspace";
30
31
  import type {
32
+ CompositeFilesystemConfig,
31
33
  CopyOptions,
32
34
  FileContent,
33
35
  FileEntry,
@@ -49,6 +51,15 @@ export interface MastraFileSystemAdapterOptions extends MastraFilesystemOptions
49
51
  /** Override the Mastra display name. Defaults to `MastraFileSystemAdapter`. */
50
52
  name?: string;
51
53
 
54
+ /** Human-friendly mount name surfaced by composite filesystem listings. */
55
+ displayName?: string;
56
+
57
+ /** Mount description surfaced by composite filesystem listings and help. */
58
+ description?: string;
59
+
60
+ /** Replace the generated filesystem usage instructions. */
61
+ instructions?: string;
62
+
52
63
  /**
53
64
  * Override the Mastra provider id. Defaults to the source
54
65
  * {@link FileSystem.backend}.
@@ -117,6 +128,14 @@ export class MastraFileSystemAdapter extends MastraFilesystem {
117
128
  return this.options.provider ?? this.fs.backend;
118
129
  }
119
130
 
131
+ get displayName(): string | undefined {
132
+ return this.options.displayName;
133
+ }
134
+
135
+ get description(): string | undefined {
136
+ return this.options.description;
137
+ }
138
+
120
139
  get readOnly(): boolean {
121
140
  return this.options.readOnly === true || this.fs.readOnly;
122
141
  }
@@ -145,7 +164,9 @@ export class MastraFileSystemAdapter extends MastraFilesystem {
145
164
  }
146
165
 
147
166
  getInstructions(): string {
167
+ if (this.options.instructions !== undefined) return this.options.instructions;
148
168
  return [
169
+ ...(this.description ? [this.description] : []),
149
170
  `Files are served by a ${this.fs.backend} filesystem rooted at ${this.fs.root}.`,
150
171
  "Workspace paths are absolute within this mount (for example `/notes/report.md`).",
151
172
  ].join(" ");
@@ -298,6 +319,28 @@ export class MastraFileSystemAdapter extends MastraFilesystem {
298
319
  }
299
320
  }
300
321
 
322
+ /**
323
+ * Composite mount namespace whose virtual root has no repository-level
324
+ * `.gitignore`. Mastra's list tool probes that relative file before walking;
325
+ * an empty result keeps native ignore handling enabled without routing the
326
+ * virtual-root lookup into an unrelated mount.
327
+ */
328
+ export class MountedCompositeFilesystem extends CompositeFilesystem {
329
+ constructor(config: CompositeFilesystemConfig) {
330
+ super(config);
331
+ }
332
+
333
+ override async readFile(inputPath: string, options?: ReadOptions): Promise<string | Buffer> {
334
+ if (
335
+ (inputPath === ".gitignore" || inputPath === "/.gitignore") &&
336
+ !this.getFilesystemForPath(inputPath)
337
+ ) {
338
+ return options?.encoding ? "" : Buffer.alloc(0);
339
+ }
340
+ return super.readFile(inputPath, options);
341
+ }
342
+ }
343
+
301
344
  /** Fresh writable local temp mount on a root unique to this call. */
302
345
  export function scratchFilesystem(): MastraFileSystemAdapter {
303
346
  return filesystems(localFS.scratchFS("mastra"));
@@ -1,38 +1,70 @@
1
1
  /**
2
2
  * Pydantic Monty fallback for Mastra command execution.
3
3
  *
4
- * Uses the Node subprocess-worker build of Monty. It executes Python source
4
+ * Prefers Monty's native Node subprocess workers and falls back to its WASM
5
+ * workers when the native package is unavailable. It executes Python source
5
6
  * with no host filesystem, network, environment, shell, or third-party package
6
- * access unless a future caller explicitly adds those capabilities.
7
+ * access.
7
8
  *
8
9
  * @module
9
10
  */
10
11
 
11
- import { errorUtils, functionUtils } from "@dbx-tools/shared-core";
12
+ import { configUtils } from "@dbx-tools/core";
13
+ import { errorUtils, functionUtils, log } from "@dbx-tools/shared-core";
12
14
  import type {
13
15
  CommandResult,
14
16
  ExecuteCommandOptions,
15
17
  SandboxInfo,
16
18
  WorkspaceSandbox,
17
19
  } from "@mastra/core/workspace";
18
- import type { CheckoutOptions, Monty as MontyPool, MontySession } from "@pydantic/monty/node";
19
-
20
- import { ensureMontyNativeBinding } from "./monty-native.ts";
20
+ import type { CheckoutOptions, Monty, MontyCrashedError, MontySession } from "@pydantic/monty";
21
21
 
22
22
  const DEFAULT_COMMAND_TIMEOUT_MS = 30_000;
23
23
  const DEFAULT_MAX_MEMORY_BYTES = 10_000_000;
24
+ const NATIVE_MONTY_TARGET = {
25
+ name: "native Monty",
26
+ load: () => import("@pydantic/monty/node"),
27
+ } as const;
28
+ const WASM_MONTY_TARGET = {
29
+ name: "WASM Monty",
30
+ load: () => import("@pydantic/monty/wasm"),
31
+ } as const;
32
+ const logger = log.logger("mastra/monty");
33
+
34
+ type MontyPool = Pick<Monty, "checkout">;
35
+
36
+ interface MontyContext {
37
+ crashedError: typeof MontyCrashedError;
38
+ pool: MontyPool;
39
+ }
24
40
 
25
- const montyModule = functionUtils.memoize(async () => {
26
- await ensureMontyNativeBinding();
27
- return import("@pydantic/monty/node");
28
- });
29
- const montyPool = functionUtils.memoize(async (): Promise<MontyPool> => {
30
- const { Monty } = await montyModule();
31
- return Monty.create({
32
- minProcesses: 1,
33
- maxProcesses: 4,
34
- requestTimeout: 35,
35
- });
41
+ const montyContext = functionUtils.memoize(async (): Promise<MontyContext> => {
42
+ const errors: Error[] = [];
43
+ const targets = configUtils.boolean(undefined, "MONTY_FORCE_WASM")
44
+ ? [WASM_MONTY_TARGET]
45
+ : [NATIVE_MONTY_TARGET, WASM_MONTY_TARGET];
46
+ for (const target of targets) {
47
+ try {
48
+ const module = await target.load();
49
+ logger.debug(`${target.name} module available`);
50
+ const pool = await module.Monty.create({
51
+ minProcesses: 1,
52
+ maxProcesses: 4,
53
+ requestTimeout: 35,
54
+ });
55
+ logger.debug(`${target.name} pool created`);
56
+ return {
57
+ crashedError: module.MontyCrashedError,
58
+ pool,
59
+ };
60
+ } catch (error) {
61
+ errors.push(errorUtils.toError(error));
62
+ logger.debug(`${target.name} unavailable`, {
63
+ error: errorUtils.errorMessage(error),
64
+ });
65
+ }
66
+ }
67
+ throw new AggregateError(errors, "Failed to create Monty pool");
36
68
  });
37
69
 
38
70
  /** Resource controls for the Monty Python fallback. */
@@ -49,7 +81,7 @@ export interface MontySandboxOptions {
49
81
  typeCheck?: boolean;
50
82
  }
51
83
 
52
- /** Python-only, deny-by-default sandbox backed by Monty's Node worker pool. */
84
+ /** Python-only, deny-by-default sandbox backed by Monty's selected worker pool. */
53
85
  export class MontySandbox implements WorkspaceSandbox {
54
86
  readonly id: string;
55
87
  readonly name: string;
@@ -77,7 +109,7 @@ export class MontySandbox implements WorkspaceSandbox {
77
109
  this.status = "starting";
78
110
  this.error = undefined;
79
111
  try {
80
- await montyPool();
112
+ await montyContext();
81
113
  this.status = "running";
82
114
  } catch (caught) {
83
115
  this.status = "error";
@@ -140,7 +172,7 @@ export class MontySandbox implements WorkspaceSandbox {
140
172
 
141
173
  await this.start();
142
174
  const timeout = options.timeout ?? this.commandTimeoutMs;
143
- const pool = await montyPool();
175
+ const pool = (await montyContext()).pool;
144
176
  const session = await checkoutSession(
145
177
  pool,
146
178
  {
@@ -188,8 +220,8 @@ export class MontySandbox implements WorkspaceSandbox {
188
220
  }
189
221
  const message = errorUtils.errorMessage(caught);
190
222
  output.emit("stderr", output.stderr.value ? `\n${message}` : message);
191
- const { MontyCrashedError } = await montyModule();
192
- const timedOut = caught instanceof MontyCrashedError && caught.timedOut;
223
+ const { crashedError } = await montyContext();
224
+ const timedOut = caught instanceof crashedError && caught.timedOut;
193
225
  return {
194
226
  command,
195
227
  args,
@@ -409,3 +441,10 @@ function failedResult(
409
441
  executionTimeMs: performance.now() - started,
410
442
  };
411
443
  }
444
+
445
+ if (import.meta.main) {
446
+ const sandbox = new MontySandbox();
447
+ await sandbox.start();
448
+ console.log(await sandbox.executeCommand("print('Hello, world!')"));
449
+ await sandbox.stop();
450
+ }
package/src/plugin.ts CHANGED
@@ -111,6 +111,7 @@ import { buildObservability, configureOtelPropagation } from "./observability.ts
111
111
  import { provisionRemoteSkills } from "./remote-skills.ts";
112
112
  import {
113
113
  attachRoutePatchMiddleware,
114
+ attachStaleResumeRecovery,
114
115
  createRequestContext,
115
116
  isMastraRequestAllowed,
116
117
  MastraServer,
@@ -1185,6 +1186,7 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
1185
1186
  customApiRoutes: agentChatRoutes(),
1186
1187
  });
1187
1188
  await this.mastraServer.init();
1189
+ attachStaleResumeRecovery(this.mastraApp);
1188
1190
  this.logger.info("ready", {
1189
1191
  agents: Object.keys(this.built.agents),
1190
1192
  defaultAgent: this.built.defaultAgentId,
@@ -36,10 +36,9 @@
36
36
  * Pass `refreshTtlMs: 0` to download on every boot.
37
37
  *
38
38
  * The default destination is the Databricks workspace Assistant skills tree
39
- * (`/Workspace/.assistant/skills`, the same tree a "save this as a skill"
40
- * action writes to), so provisioned skills persist across restarts and are
39
+ * (`/Workspace/.assistant/skills`), so provisioned skills persist across restarts and are
41
40
  * discovered by the built-in Assistant-skills mount. Pass `userEmail` (or an
42
- * explicit `databricksBasePath`) to target `/Users/<email>/.assistant/skills`
41
+ * explicit `databricksBasePath`) to target `/Workspace/Users/<email>/.assistant/skills`
43
42
  * instead. When no Databricks client is resolvable at startup - or the resolved
44
43
  * identity cannot WRITE the destination, which is the normal case for a
45
44
  * Databricks App service principal against the admin-owned shared tree (see
@@ -73,8 +72,7 @@ import {
73
72
  import type { OneOrMany } from "@dbx-tools/shared-core";
74
73
  import type { FileSystem } from "@dbx-tools/shared-fs";
75
74
 
76
- import { ASSISTANT_SHARED_SKILLS_PATH, userAssistantSkillsPath } from "./skill-paths.ts";
77
- import { clearWorkspaceSkillCache } from "./skill-cache.ts";
75
+ import { ORGANIZATION_SKILLS_PATH, personalSkillsPath } from "./skill-paths.ts";
78
76
 
79
77
  const logger = log.logger("mastra/remote-skills");
80
78
 
@@ -208,7 +206,7 @@ export interface ProvisionRemoteSkillsOptions {
208
206
  failOnError?: boolean;
209
207
  /**
210
208
  * Absolute Databricks path that roots the destination Assistant skills tree.
211
- * Defaults to the OBO user's `/Users/<email>/.assistant/skills`.
209
+ * Defaults to the OBO user's `/Workspace/Users/<email>/.assistant/skills`.
212
210
  */
213
211
  databricksBasePath?: string;
214
212
  /** Auth-scoped Databricks client. Defaults to the AppKit execution context. */
@@ -442,7 +440,6 @@ export async function provisionRemoteSkills(
442
440
  const localSkillPaths: string[] = [];
443
441
  const skillNames: string[] = [];
444
442
  let staging: LocalFileSystem | undefined;
445
- let workspaceUpdated = false;
446
443
 
447
444
  try {
448
445
  const sources = Array.isArray(options.sources) ? options.sources : [options.sources];
@@ -491,7 +488,6 @@ export async function provisionRemoteSkills(
491
488
  // Only after the copy lands: a metadata entry written first would
492
489
  // mark a failed provision as fresh and suppress the retry for a day.
493
490
  await writeMetadata(destination, key, record);
494
- workspaceUpdated = true;
495
491
  } else {
496
492
  localSkillPaths.push(await persistLocally(key, staged, record));
497
493
  }
@@ -520,12 +516,6 @@ export async function provisionRemoteSkills(
520
516
  }
521
517
  }
522
518
 
523
- if (workspaceUpdated && client?.config?.getHost) {
524
- await clearWorkspaceSkillCache({
525
- host: (await client.config.getHost()).toString(),
526
- });
527
- }
528
-
529
519
  return { localSkillPaths, databricksBasePath, skillNames };
530
520
  }
531
521
 
@@ -603,7 +593,7 @@ function resolveDatabricksBasePath(
603
593
  // A named user targets their personal Assistant tree (the "save a skill"
604
594
  // target); otherwise the shared workspace Assistant tree, which the built-in
605
595
  // Assistant-skills mount already scans.
606
- return email ? userAssistantSkillsPath(email) : ASSISTANT_SHARED_SKILLS_PATH;
596
+ return email ? personalSkillsPath(email) : ORGANIZATION_SKILLS_PATH;
607
597
  }
608
598
 
609
599
  /**
package/src/server.ts CHANGED
@@ -6,8 +6,9 @@
6
6
  */
7
7
 
8
8
  import { getExecutionContext } from "@databricks/appkit";
9
- import { hash, http, log, object, stringUtils, token } from "@dbx-tools/shared-core";
9
+ import { errorUtils, hash, http, log, object, stringUtils, token } from "@dbx-tools/shared-core";
10
10
  import { feedback, thread } from "@dbx-tools/shared-mastra";
11
+ import { isStaleMastraResumeError } from "@dbx-tools/shared-mastra/resume";
11
12
  import {
12
13
  MASTRA_RESOURCE_ID_KEY,
13
14
  MASTRA_THREAD_ID_KEY,
@@ -42,6 +43,7 @@ import { recordActiveTraceAuth, recordActiveTraceUser } from "./telemetry.ts";
42
43
  * when no SDK is registered, which must never be surfaced as a trace to
43
44
  * attach feedback to.
44
45
  */
46
+ const logger = log.logger("mastra/server");
45
47
  const INVALID_TRACE_ID = "0".repeat(32);
46
48
  const TRUSTED_REQUEST_CONTEXT_KEYS = [
47
49
  MASTRA_RESOURCE_ID_KEY,
@@ -460,3 +462,33 @@ export function attachRoutePatchMiddleware(app: express.Express): void {
460
462
  next();
461
463
  });
462
464
  }
465
+
466
+ /**
467
+ * Finish a chat request when Mastra throws because the workflow is no
468
+ * longer suspended. Duplicate approve/resume and late `consumeStream`
469
+ * both hit that path; the client already has (or will reload) the
470
+ * completed turn.
471
+ */
472
+ export function attachStaleResumeRecovery(app: express.Express): void {
473
+ app.use((error: unknown, req: express.Request, res: express.Response, next: express.NextFunction) => {
474
+ if (!isStaleMastraResumeError(error)) {
475
+ next(error);
476
+ return;
477
+ }
478
+ logger.warn("ignored stale mastra resume", {
479
+ path: req.path,
480
+ error: errorUtils.errorMessage(error),
481
+ });
482
+ if (res.headersSent) {
483
+ res.end();
484
+ return;
485
+ }
486
+ res.status(200);
487
+ res.setHeader("content-type", "text/event-stream; charset=utf-8");
488
+ res.setHeader("cache-control", "no-cache");
489
+ res.write(`data: ${JSON.stringify({ type: "start" })}\n\n`);
490
+ res.write(`data: ${JSON.stringify({ type: "finish" })}\n\n`);
491
+ res.write("data: [DONE]\n\n");
492
+ res.end();
493
+ });
494
+ }