@dbx-tools/appkit-mastra 0.9.93 → 0.9.99

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.
@@ -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-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.93",
38
- "@dbx-tools/core": "^0.9.93",
39
- "@dbx-tools/databricks": "^0.9.93",
40
- "@dbx-tools/fs": "^0.9.93",
41
- "@dbx-tools/genie": "^0.9.93",
42
- "@dbx-tools/model": "^0.9.93",
43
- "@dbx-tools/path": "^0.9.93",
44
- "@dbx-tools/postgres": "^0.9.93",
45
- "@dbx-tools/shared-core": "^0.9.93",
46
- "@dbx-tools/shared-fs": "^0.9.93",
47
- "@dbx-tools/shared-genie": "^0.9.93",
48
- "@dbx-tools/shared-mastra": "^0.9.93",
49
- "@dbx-tools/shared-model": "^0.9.93",
37
+ "@dbx-tools/appkit": "^0.9.99",
38
+ "@dbx-tools/core": "^0.9.99",
39
+ "@dbx-tools/databricks": "^0.9.99",
40
+ "@dbx-tools/fs": "^0.9.99",
41
+ "@dbx-tools/genie": "^0.9.99",
42
+ "@dbx-tools/model": "^0.9.99",
43
+ "@dbx-tools/path": "^0.9.99",
44
+ "@dbx-tools/postgres": "^0.9.99",
45
+ "@dbx-tools/shared-core": "^0.9.99",
46
+ "@dbx-tools/shared-fs": "^0.9.99",
47
+ "@dbx-tools/shared-genie": "^0.9.99",
48
+ "@dbx-tools/shared-mastra": "^0.9.99",
49
+ "@dbx-tools/shared-model": "^0.9.99",
50
50
  "@mastra/ai-sdk": "1.10.5",
51
51
  "@mastra/core": "1.71.0",
52
52
  "@mastra/express": "1.5.15",
@@ -176,18 +176,10 @@
176
176
  "types": "./lib/src/serving.d.ts",
177
177
  "default": "./lib/src/serving.js"
178
178
  },
179
- "./skill-cache": {
180
- "types": "./lib/src/skill-cache.d.ts",
181
- "default": "./lib/src/skill-cache.js"
182
- },
183
179
  "./skill-paths": {
184
180
  "types": "./lib/src/skill-paths.d.ts",
185
181
  "default": "./lib/src/skill-paths.js"
186
182
  },
187
- "./skill-search": {
188
- "types": "./lib/src/skill-search.d.ts",
189
- "default": "./lib/src/skill-search.js"
190
- },
191
183
  "./statement": {
192
184
  "types": "./lib/src/statement.d.ts",
193
185
  "default": "./lib/src/statement.js"
@@ -227,7 +219,7 @@
227
219
  "./package.json": "./package.json"
228
220
  }
229
221
  },
230
- "version": "0.9.93",
222
+ "version": "0.9.99",
231
223
  "types": "./lib/index.d.ts",
232
224
  "dbxToolsConfig": {
233
225
  "tags": [
@@ -336,18 +328,10 @@
336
328
  "types": "./lib/src/serving.d.ts",
337
329
  "default": "./lib/src/serving.js"
338
330
  },
339
- "./skill-cache": {
340
- "types": "./lib/src/skill-cache.d.ts",
341
- "default": "./lib/src/skill-cache.js"
342
- },
343
331
  "./skill-paths": {
344
332
  "types": "./lib/src/skill-paths.d.ts",
345
333
  "default": "./lib/src/skill-paths.js"
346
334
  },
347
- "./skill-search": {
348
- "types": "./lib/src/skill-search.d.ts",
349
- "default": "./lib/src/skill-search.js"
350
- },
351
335
  "./statement": {
352
336
  "types": "./lib/src/statement.d.ts",
353
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 { bindDatabricksWorkspaceContext, databricksWorkspace } from "./workspaces.ts";
56
55
 
57
56
  /**
58
57
  * Tool record accepted by every Mastra `Agent.tools` field and by the
@@ -169,8 +168,8 @@ function deriveToolId(description: string): string {
169
168
  * ```
170
169
  *
171
170
  * Adds the package's default workspace when the definition omits one. That
172
- * workspace carries Databricks skill mounts and Databricks Sandbox command
173
- * execution. An explicit workspace remains the caller's complete override.
171
+ * workspace carries Databricks skill mounts and Monty command execution. An
172
+ * explicit workspace remains the caller's complete override.
174
173
  */
175
174
  export function createAgent<
176
175
  TRequestContext extends Record<string, unknown> | unknown = unknown,
@@ -178,7 +177,7 @@ export function createAgent<
178
177
  MastraAgentDefinition<TRequestContext>,
179
178
  >(def: TDefinition): TDefinition {
180
179
  if (def.workspace) return { ...def };
181
- const workspace = createWorkspace();
180
+ const workspace = databricksWorkspace();
182
181
  markDefaultWorkspace(workspace);
183
182
  return { ...def, workspace };
184
183
  }
@@ -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
  /**
@@ -339,7 +338,7 @@ export interface MastraAgentDefinition<
339
338
  /**
340
339
  * Mastra {@link Workspace} for this agent (filesystem, sandbox, or other
341
340
  * providers). Assistant skills from Databricks workspace paths are wired
342
- * via {@link createWorkspace}.
341
+ * via {@link databricksWorkspace}.
343
342
  */
344
343
  workspace?: Workspace | MastraAgentWorkspaceResolver;
345
344
  }
@@ -577,17 +576,19 @@ 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
- workspace = createWorkspace({
583
+ workspace = databricksWorkspace({
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);
590
+ } else if (workspace) {
591
+ workspace = bindDatabricksWorkspaceContext(workspace, context);
591
592
  }
592
593
  const gated = approvalGatedToolIds(tools);
593
594
  if (gated.length > 0) approvalGatedByAgent.push({ agentId: id, toolIds: gated });
@@ -668,21 +669,9 @@ function toolErrorLoggingProcessor(logger: log.Logger): OutputProcessor {
668
669
  function workspaceSkillInputProcessors(
669
670
  workspace: Workspace | undefined,
670
671
  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
- }
672
+ ): SkillSearchProcessor[] {
673
+ if (!workspace || config.workspaceSkills === false) return [];
674
+ const options = typeof config.workspaceSkills === "object" ? config.workspaceSkills : undefined;
686
675
  if (!workspace.skills) return [];
687
676
  return [
688
677
  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"));
@@ -11,12 +11,13 @@
11
11
 
12
12
  import { configUtils } from "@dbx-tools/core";
13
13
  import { errorUtils, functionUtils, log } from "@dbx-tools/shared-core";
14
+ import { availableParallelism } from "node:os";
14
15
  import type {
15
16
  CommandResult,
16
17
  ExecuteCommandOptions,
17
18
  SandboxInfo,
18
- WorkspaceSandbox,
19
19
  } from "@mastra/core/workspace";
20
+ import { MastraSandbox, type ProviderStatus } from "@mastra/core/workspace";
20
21
  import type { CheckoutOptions, Monty, MontyCrashedError, MontySession } from "@pydantic/monty";
21
22
 
22
23
  const DEFAULT_COMMAND_TIMEOUT_MS = 30_000;
@@ -38,6 +39,11 @@ interface MontyContext {
38
39
  pool: MontyPool;
39
40
  }
40
41
 
42
+ /** @internal Size the shared worker pool from the host's available processors. */
43
+ export function montyMaxProcesses(parallelism: number = availableParallelism()): number {
44
+ return Math.max(4, Math.floor(parallelism) * 2);
45
+ }
46
+
41
47
  const montyContext = functionUtils.memoize(async (): Promise<MontyContext> => {
42
48
  const errors: Error[] = [];
43
49
  const targets = configUtils.boolean(undefined, "MONTY_FORCE_WASM")
@@ -47,12 +53,13 @@ const montyContext = functionUtils.memoize(async (): Promise<MontyContext> => {
47
53
  try {
48
54
  const module = await target.load();
49
55
  logger.debug(`${target.name} module available`);
56
+ const maxProcesses = montyMaxProcesses();
50
57
  const pool = await module.Monty.create({
51
58
  minProcesses: 1,
52
- maxProcesses: 4,
59
+ maxProcesses,
53
60
  requestTimeout: 35,
54
61
  });
55
- logger.debug(`${target.name} pool created`);
62
+ logger.debug(`${target.name} pool created`, { maxProcesses });
56
63
  return {
57
64
  crashedError: module.MontyCrashedError,
58
65
  pool,
@@ -82,12 +89,11 @@ export interface MontySandboxOptions {
82
89
  }
83
90
 
84
91
  /** Python-only, deny-by-default sandbox backed by Monty's selected worker pool. */
85
- export class MontySandbox implements WorkspaceSandbox {
92
+ export class MontySandbox extends MastraSandbox {
86
93
  readonly id: string;
87
94
  readonly name: string;
88
95
  readonly provider = "monty";
89
- status: WorkspaceSandbox["status"] = "pending";
90
- error?: string;
96
+ status: ProviderStatus = "pending";
91
97
 
92
98
  private readonly commandTimeoutMs: number;
93
99
  private readonly maxMemoryBytes: number;
@@ -96,8 +102,10 @@ export class MontySandbox implements WorkspaceSandbox {
96
102
  private lastUsedAt?: Date;
97
103
 
98
104
  constructor(options: MontySandboxOptions = {}) {
105
+ const name = options.name ?? "Pydantic Monty";
106
+ super({ name });
99
107
  this.id = options.id ?? "monty";
100
- this.name = options.name ?? "Pydantic Monty";
108
+ this.name = name;
101
109
  this.commandTimeoutMs = options.commandTimeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS;
102
110
  this.maxMemoryBytes = options.maxMemoryBytes ?? DEFAULT_MAX_MEMORY_BYTES;
103
111
  this.typeCheck = options.typeCheck ?? false;
@@ -105,33 +113,14 @@ export class MontySandbox implements WorkspaceSandbox {
105
113
 
106
114
  /** Warm the shared crash-isolated worker pool. */
107
115
  async start(): Promise<void> {
108
- if (this.status === "running") return;
109
- this.status = "starting";
110
- this.error = undefined;
111
- try {
112
- await montyContext();
113
- this.status = "running";
114
- } catch (caught) {
115
- this.status = "error";
116
- this.error = errorUtils.errorMessage(caught);
117
- throw caught;
118
- }
116
+ await montyContext();
119
117
  }
120
118
 
121
119
  /** The process-wide pool stays warm; this instance carries no open session. */
122
- async stop(): Promise<void> {
123
- this.status = "stopped";
124
- }
120
+ async stop(): Promise<void> {}
125
121
 
126
122
  /** The process-wide pool stays warm; each command closes its own checkout. */
127
- async destroy(): Promise<void> {
128
- this.status = "destroyed";
129
- }
130
-
131
- /** Monty sessions are ephemeral and do not provide persistent checkpoints. */
132
- async snapshot(): Promise<void> {}
133
-
134
- readonly supportsCheckpoints = false;
123
+ async destroy(): Promise<void> {}
135
124
 
136
125
  /** Execute Python source and map Monty output to Mastra's command result. */
137
126
  async executeCommand(
@@ -9,16 +9,7 @@ import { urlBuilder, type UrlBuilder } from "@dbx-tools/shared-core/net";
9
9
  import type { JSONSchema7 } from "json-schema";
10
10
  import { z } from "zod";
11
11
 
12
- const HTTP_METHODS = [
13
- "GET",
14
- "POST",
15
- "PUT",
16
- "PATCH",
17
- "DELETE",
18
- "OPTIONS",
19
- "HEAD",
20
- "TRACE",
21
- ] as const;
12
+ const HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD", "TRACE"] as const;
22
13
 
23
14
  const JsonObjectSchema = z
24
15
  .record(z.string(), z.unknown())
@@ -208,7 +199,7 @@ function operationUrl(document: OpenApiDocument, sourceUrl: URL | undefined, pat
208
199
  ? base.withPathAppend(path).toString()
209
200
  : `${server.replace(/\/$/, "")}/${path.replace(/^\//, "")}`;
210
201
  }
211
- return sourceUrl ? urlBuilder(sourceUrl)?.withPathReplace(path).toString() ?? path : path;
202
+ return sourceUrl ? (urlBuilder(sourceUrl)?.withPathReplace(path).toString() ?? path) : path;
212
203
  }
213
204
 
214
205
  function resolveReferences(
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,35 @@ 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(
474
+ (error: unknown, req: express.Request, res: express.Response, next: express.NextFunction) => {
475
+ if (!isStaleMastraResumeError(error)) {
476
+ next(error);
477
+ return;
478
+ }
479
+ logger.warn("ignored stale mastra resume", {
480
+ path: req.path,
481
+ error: errorUtils.errorMessage(error),
482
+ });
483
+ if (res.headersSent) {
484
+ res.end();
485
+ return;
486
+ }
487
+ res.status(200);
488
+ res.setHeader("content-type", "text/event-stream; charset=utf-8");
489
+ res.setHeader("cache-control", "no-cache");
490
+ res.write(`data: ${JSON.stringify({ type: "start" })}\n\n`);
491
+ res.write(`data: ${JSON.stringify({ type: "finish" })}\n\n`);
492
+ res.write("data: [DONE]\n\n");
493
+ res.end();
494
+ },
495
+ );
496
+ }