@dbx-tools/appkit-mastra 0.6.215 → 0.6.217

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/src/workspaces.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Mastra workspace factory for Databricks Apps.
3
3
  *
4
- * Builds a per-request {@link Workspace} whose filesystem is a
5
- * {@link CompositeFilesystem} over the NAMED skill folders resolved for that
6
- * request. A skill folder maps a name to a location plus its readable /
7
- * writable policy: a Databricks path mounted through the OBO client on
8
- * {@link MASTRA_USER_KEY}, or any {@link WorkspaceFilesystem} a consuming
4
+ * Builds a per-request {@link Workspace} with Databricks Sandbox command
5
+ * execution plus a {@link CompositeFilesystem} over the NAMED skill folders
6
+ * resolved for that request. A skill folder maps a name to a location plus its
7
+ * readable / writable policy: a Databricks path mounted through the OBO client
8
+ * on {@link MASTRA_USER_KEY}, or any {@link WorkspaceFilesystem} a consuming
9
9
  * library already owns. {@link DEFAULT_SKILL_FOLDERS} supplies the Assistant
10
10
  * trees, and `skillFolders` merges over it - same name overrides, `false`
11
11
  * disables, a new name adds. Optional mount resolvers contribute further
@@ -18,9 +18,11 @@
18
18
  * @module
19
19
  */
20
20
 
21
+ import { createHash } from "node:crypto";
22
+ import { ConfigurationError, createWorkspaceClient } from "@databricks/appkit";
21
23
  import type { WorkspaceClient } from "@databricks/appkit";
22
24
  import { DatabricksFileSystem, workspace as databricksWorkspace } from "@dbx-tools/databricks";
23
- import { error, log, string, token } from "@dbx-tools/shared-core";
25
+ import { error, log, object, string, token } from "@dbx-tools/shared-core";
24
26
  import type { RequestContext } from "@mastra/core/request-context";
25
27
  import {
26
28
  CompositeFilesystem,
@@ -28,10 +30,14 @@ import {
28
30
  type SkillsContext,
29
31
  type SkillsResolver,
30
32
  type WorkspaceFilesystem,
33
+ type WorkspaceSandbox,
34
+ type WorkspaceSandboxResolver,
31
35
  } from "@mastra/core/workspace";
32
36
 
33
37
  import { MASTRA_SCOPES_KEY, MASTRA_USER_EMAIL_KEY, MASTRA_USER_KEY, type User } from "./config.ts";
34
38
  import { scratchFilesystem, filesystems } from "./filesystems.ts";
39
+ import { MontySandbox } from "./monty-sandbox.ts";
40
+ import { DatabricksSandbox, type DatabricksWorkspaceSandboxOptions } from "./sandbox.ts";
35
41
  import { ASSISTANT_SHARED_SKILLS_PATH, userAssistantSkillsPath } from "./skill-paths.ts";
36
42
 
37
43
  /* ------------------------------ constants ------------------------------ */
@@ -97,6 +103,19 @@ export type WorkspaceMountResolver = (
97
103
  /** Names carried by {@link DEFAULT_SKILL_FOLDERS}. */
98
104
  export type DefaultSkillFolderName = "workspace-team" | "workspace-team-app";
99
105
 
106
+ /**
107
+ * Sandbox selection for {@link createWorkspace}. Databricks is the default;
108
+ * `false` disables command execution, and a Mastra provider or resolver is an
109
+ * explicit replacement.
110
+ */
111
+ export type WorkspaceSandboxSelection =
112
+ | false
113
+ | "databricks"
114
+ | "monty"
115
+ | DatabricksWorkspaceSandboxOptions
116
+ | WorkspaceSandbox
117
+ | WorkspaceSandboxResolver;
118
+
100
119
  /** Options for {@link createWorkspace}. */
101
120
  export interface CreateWorkspaceOptions {
102
121
  /** Workspace id; derived from `name` or `"workspace"` when omitted. */
@@ -122,6 +141,12 @@ export interface CreateWorkspaceOptions {
122
141
  checkSkillFileMtime?: boolean;
123
142
  /** Enable BM25 keyword search over indexed workspace content. */
124
143
  bm25?: boolean;
144
+ /**
145
+ * Command sandbox. Defaults to Databricks Sandbox. Pass `false` to disable
146
+ * command execution, or an explicit Mastra sandbox/provider resolver to
147
+ * replace Databricks for this workspace.
148
+ */
149
+ sandbox?: WorkspaceSandboxSelection;
125
150
  /**
126
151
  * Extra LOCAL skill scan paths added to every request's skill discovery.
127
152
  * Used by the plugin to surface remote skills provisioned to a local temp
@@ -204,6 +229,7 @@ export function createWorkspace(options: CreateWorkspaceOptions = {}): Workspace
204
229
  : undefined);
205
230
  const checkSkillFileMtime = options.checkSkillFileMtime ?? folderNames.length > 0;
206
231
  const bm25 = options.bm25 !== false;
232
+ const sandbox = resolveWorkspaceSandbox(options.sandbox, id, name);
207
233
  logger.debug("workspace:create", {
208
234
  id,
209
235
  name,
@@ -214,6 +240,7 @@ export function createWorkspace(options: CreateWorkspaceOptions = {}): Workspace
214
240
  checkSkillFileMtime,
215
241
  bm25,
216
242
  extraSkillPaths: extraSkillPaths.length,
243
+ sandbox: sandbox ? sandboxName(options.sandbox) : "disabled",
217
244
  });
218
245
 
219
246
  return new Workspace({
@@ -226,6 +253,12 @@ export function createWorkspace(options: CreateWorkspaceOptions = {}): Workspace
226
253
  checkSkillFileMtime,
227
254
  }
228
255
  : {}),
256
+ ...(sandbox
257
+ ? {
258
+ sandbox,
259
+ instructions: { dynamicSandbox: "resolve" as const },
260
+ }
261
+ : {}),
229
262
  bm25,
230
263
  });
231
264
  }
@@ -253,6 +286,72 @@ export function resolveSkillFolders(
253
286
 
254
287
  /* ---------------------------- private helpers ---------------------------- */
255
288
 
289
+ function resolveWorkspaceSandbox(
290
+ selection: WorkspaceSandboxSelection | undefined,
291
+ workspaceId: string,
292
+ workspaceName: string,
293
+ ): WorkspaceSandbox | WorkspaceSandboxResolver | undefined {
294
+ const configured = selection ?? "databricks";
295
+ if (configured === false) return undefined;
296
+ if (configured === "monty") return new MontySandbox();
297
+ if (typeof configured === "function" || isSandbox(configured)) return configured;
298
+
299
+ const options: DatabricksWorkspaceSandboxOptions =
300
+ configured === "databricks" ? { provider: "databricks" } : configured;
301
+ return ({ requestContext }) => {
302
+ const user = requestContext.get(MASTRA_USER_KEY) as User | undefined;
303
+ if (!user) {
304
+ throw ConfigurationError.resourceNotFound(
305
+ "Databricks sandbox user context",
306
+ "Invoke command tools from an agent turn served by the Mastra plugin.",
307
+ );
308
+ }
309
+ const configuredId =
310
+ typeof options.sandboxId === "function"
311
+ ? options.sandboxId({ requestContext })
312
+ : options.sandboxId;
313
+ const sandboxId = configuredId ?? defaultSandboxId(workspaceId, user.id);
314
+ const client =
315
+ typeof options.client === "function"
316
+ ? options.client({ requestContext })
317
+ : (options.client ?? createWorkspaceClient());
318
+ const {
319
+ provider: _provider,
320
+ sandboxId: _sandboxId,
321
+ client: _client,
322
+ ...sandboxOptions
323
+ } = options;
324
+ return new DatabricksSandbox({
325
+ ...sandboxOptions,
326
+ client,
327
+ sandboxId,
328
+ displayName: options.displayName ?? `Mastra ${workspaceName}`,
329
+ });
330
+ };
331
+ }
332
+
333
+ function defaultSandboxId(workspaceId: string, userId: string): string {
334
+ const digest = createHash("sha256")
335
+ .update(object.toStableKey({ workspaceId, userId }))
336
+ .digest("hex")
337
+ .slice(0, 32);
338
+ return `mastra-${digest}`;
339
+ }
340
+
341
+ function isSandbox(value: unknown): value is WorkspaceSandbox {
342
+ return Boolean(
343
+ value && typeof value === "object" && "id" in value && "provider" in value && "status" in value,
344
+ );
345
+ }
346
+
347
+ function sandboxName(selection: WorkspaceSandboxSelection | undefined): string {
348
+ if (selection === undefined || selection === "databricks") return "databricks";
349
+ if (selection === "monty") return "monty";
350
+ if (selection === false) return "disabled";
351
+ if (typeof selection === "function") return "resolver";
352
+ return isSandbox(selection) ? selection.provider : (selection.provider ?? "databricks");
353
+ }
354
+
256
355
  /**
257
356
  * Return whether the request token carries a scope that allows workspace
258
357
  * file API access (`workspace` or `all-apis` on {@link MASTRA_SCOPES_KEY}).