@dbx-tools/appkit-mastra 0.9.106 → 0.9.110
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 +78 -24
- package/index.ts +4 -4
- package/lib/index.d.ts +4 -4
- package/lib/index.js +3 -3
- package/lib/src/agents.d.ts +6 -4
- package/lib/src/agents.js +127 -30
- package/lib/src/chat.js +4 -3
- package/lib/src/config.d.ts +21 -4
- package/lib/src/config.js +11 -3
- package/lib/src/memory.d.ts +8 -0
- package/lib/src/memory.js +24 -1
- package/lib/src/plugin.js +5 -5
- package/lib/src/remote-skills.d.ts +22 -9
- package/lib/src/remote-skills.js +54 -13
- package/lib/src/sandbox.d.ts +2 -1
- package/lib/src/sandbox.js +2 -2
- package/lib/src/workspaces.d.ts +20 -21
- package/lib/src/workspaces.js +65 -48
- package/package.json +17 -15
- package/src/agents.ts +166 -42
- package/src/chat.ts +9 -4
- package/src/config.ts +34 -6
- package/src/memory.ts +33 -0
- package/src/plugin.ts +4 -4
- package/src/remote-skills.ts +74 -16
- package/src/sandbox.ts +3 -2
- package/src/workspaces.ts +88 -72
package/src/remote-skills.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* {@link AITOOLS_SOURCE} constant, a GitHub `owner/repo`, any git / GitLab URL,
|
|
6
6
|
* or a direct download URL - and optional per-source policy.
|
|
7
7
|
* {@link provisionRemoteSkills} materializes every source into a local
|
|
8
|
-
* `SKILL.md` tree at app boot and returns
|
|
9
|
-
*
|
|
8
|
+
* `SKILL.md` tree at app boot and returns read-only backing directories plus
|
|
9
|
+
* their stable virtual workspace mounts.
|
|
10
10
|
*
|
|
11
11
|
* Resolution per source, in order:
|
|
12
12
|
*
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
* identity cannot WRITE the destination, which is the normal case for a
|
|
44
44
|
* Databricks App service principal against the admin-owned shared tree (see
|
|
45
45
|
* {@link openWritableWorkspace}) - the tree is written under
|
|
46
|
-
* {@link localFS.tmpFS} and
|
|
47
|
-
* current process. Storage location degrades; the skills themselves do not.
|
|
46
|
+
* {@link localFS.tmpFS} and exposed through a stable read-only virtual mount
|
|
47
|
+
* for the current process. Storage location degrades; the skills themselves do not.
|
|
48
48
|
*
|
|
49
49
|
* @module
|
|
50
50
|
*/
|
|
@@ -100,6 +100,12 @@ const DEFAULT_DOWNLOAD_TIMEOUT_MS = 60_000;
|
|
|
100
100
|
/** Stable temp directory holding one rebuilt skill tree per remote source. */
|
|
101
101
|
const LOCAL_SKILLS_DIR = "mastra-local-skills";
|
|
102
102
|
|
|
103
|
+
/** Virtual workspace path reserved for locally provisioned Databricks AI Tools. */
|
|
104
|
+
export const AITOOLS_SKILLS_MOUNT = "/.databricks/skills";
|
|
105
|
+
|
|
106
|
+
/** Virtual workspace root for every other locally provisioned remote source. */
|
|
107
|
+
export const REMOTE_SKILLS_MOUNT = "/.skills";
|
|
108
|
+
|
|
103
109
|
/**
|
|
104
110
|
* Bookkeeping file written at the root of every provisioned skill tree, naming
|
|
105
111
|
* when each source was last downloaded. A dotfile so the skill scanners, which
|
|
@@ -156,6 +162,8 @@ interface AiToolsManifest {
|
|
|
156
162
|
export interface RemoteSkillSourceOptions {
|
|
157
163
|
/** `"aitools"`, a GitHub shorthand, a git / GitLab URL, or a download URL. */
|
|
158
164
|
source: net.UrlLike | AiToolsSource;
|
|
165
|
+
/** Stable virtual mount name under `/.skills`; generated from the source when omitted. */
|
|
166
|
+
name?: string;
|
|
159
167
|
/** Install only these skill names from the source. */
|
|
160
168
|
skills?: string | string[];
|
|
161
169
|
/** Include experimental skills. `"aitools"` only. */
|
|
@@ -195,6 +203,14 @@ export type RemoteSkillsOption =
|
|
|
195
203
|
/** A source entry with its `source` flattened to a plain string. */
|
|
196
204
|
type NormalizedSource = Omit<RemoteSkillSourceOptions, "source"> & { source: string };
|
|
197
205
|
|
|
206
|
+
/** Read-only local backing storage exposed at a stable virtual workspace path. */
|
|
207
|
+
export interface LocalSkillMount {
|
|
208
|
+
/** Physical local directory containing the provisioned `SKILL.md` tree. */
|
|
209
|
+
root: string;
|
|
210
|
+
/** Virtual path agents see inside the composed Mastra workspace. */
|
|
211
|
+
mountPath: string;
|
|
212
|
+
}
|
|
213
|
+
|
|
198
214
|
/** Top-level remote-skills provisioning options. */
|
|
199
215
|
export interface ProvisionRemoteSkillsOptions {
|
|
200
216
|
/** Sources to materialize at startup. */
|
|
@@ -226,11 +242,11 @@ export interface ProvisionRemoteSkillsOptions {
|
|
|
226
242
|
/** What {@link provisionRemoteSkills} resolved. */
|
|
227
243
|
export interface ProvisionedRemoteSkills {
|
|
228
244
|
/**
|
|
229
|
-
* Extra LOCAL skill
|
|
230
|
-
*
|
|
231
|
-
*
|
|
245
|
+
* Extra read-only LOCAL skill mounts to hand Mastra when a source couldn't
|
|
246
|
+
* be written to Databricks. Physical temp/cache paths remain hidden behind
|
|
247
|
+
* stable virtual workspace paths.
|
|
232
248
|
*/
|
|
233
|
-
|
|
249
|
+
localSkillMounts: LocalSkillMount[];
|
|
234
250
|
/** Absolute Databricks destination each source was written to, if any. */
|
|
235
251
|
databricksBasePath?: string;
|
|
236
252
|
/**
|
|
@@ -342,6 +358,29 @@ function cacheKey(sourceOptions: NormalizedSource): string {
|
|
|
342
358
|
);
|
|
343
359
|
}
|
|
344
360
|
|
|
361
|
+
/** Stable virtual workspace path for one locally provisioned source. */
|
|
362
|
+
function localSkillMountPath(sourceOptions: NormalizedSource, key: string): string {
|
|
363
|
+
if (isAiToolsSource(sourceOptions.source)) return AITOOLS_SKILLS_MOUNT;
|
|
364
|
+
const configuredName = stringUtils.trimToNull(sourceOptions.name);
|
|
365
|
+
const identifier = configuredName
|
|
366
|
+
? stringUtils.toSlug(configuredName)
|
|
367
|
+
: stringUtils.toSlug(remoteSourceIdentifier(sourceOptions.source)) || "source";
|
|
368
|
+
if (!identifier) {
|
|
369
|
+
throw new Error(
|
|
370
|
+
`remote skill source name must contain letters or numbers: "${configuredName}"`,
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
return posix.join(REMOTE_SKILLS_MOUNT, configuredName ? identifier : `${identifier}-${key}`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/** Human-readable source portion used by generated virtual mount identifiers. */
|
|
377
|
+
function remoteSourceIdentifier(source: string): string {
|
|
378
|
+
const url = net.urlBuilder(source);
|
|
379
|
+
if (!url) return source;
|
|
380
|
+
const path = url.pathname.replace(/\.(git|md|zip|tar|tgz|gz)$/i, "");
|
|
381
|
+
return `${url.hostname}-${path}`;
|
|
382
|
+
}
|
|
383
|
+
|
|
345
384
|
/** The effective reuse window: per-source, then top-level, then seven days. */
|
|
346
385
|
function resolveRefreshTtl(
|
|
347
386
|
sourceOptions: NormalizedSource,
|
|
@@ -416,13 +455,13 @@ function localSkillsFS(key: string): LocalFileSystem {
|
|
|
416
455
|
*
|
|
417
456
|
* Writes each resolved `SKILL.md` tree to the Databricks user Assistant skills
|
|
418
457
|
* folder when a writable workspace is available, else under
|
|
419
|
-
* {@link localFS.tmpFS} returned in {@link ProvisionedRemoteSkills.
|
|
458
|
+
* {@link localFS.tmpFS} returned in {@link ProvisionedRemoteSkills.localSkillMounts}.
|
|
420
459
|
*/
|
|
421
460
|
export async function provisionRemoteSkills(
|
|
422
461
|
option: RemoteSkillsOption | undefined,
|
|
423
462
|
): Promise<ProvisionedRemoteSkills> {
|
|
424
463
|
const options = normalizeRemoteSkillsOption(option);
|
|
425
|
-
const empty: ProvisionedRemoteSkills = {
|
|
464
|
+
const empty: ProvisionedRemoteSkills = { localSkillMounts: [], skillNames: [] };
|
|
426
465
|
if (!options) return empty;
|
|
427
466
|
|
|
428
467
|
const failDefault = options.failOnError !== false;
|
|
@@ -437,7 +476,7 @@ export async function provisionRemoteSkills(
|
|
|
437
476
|
: undefined;
|
|
438
477
|
const databricksBasePath = destination ? requestedBasePath : undefined;
|
|
439
478
|
|
|
440
|
-
const
|
|
479
|
+
const localSkillMounts: LocalSkillMount[] = [];
|
|
441
480
|
const skillNames: string[] = [];
|
|
442
481
|
let staging: LocalFileSystem | undefined;
|
|
443
482
|
|
|
@@ -450,15 +489,16 @@ export async function provisionRemoteSkills(
|
|
|
450
489
|
// Where this source's bookkeeping lives: the shared Databricks tree, or
|
|
451
490
|
// the source's own stable local dir.
|
|
452
491
|
const key = cacheKey(sourceOptions);
|
|
492
|
+
const mountPath = localSkillMountPath(sourceOptions, key);
|
|
453
493
|
const cacheFS = destination ?? localSkillsFS(key);
|
|
454
494
|
const metadata = await readMetadata(cacheFS);
|
|
455
495
|
const cached = metadata?.sources[key];
|
|
456
496
|
if (cached && isFresh(cached, resolveRefreshTtl(sourceOptions, options))) {
|
|
457
497
|
skillNames.push(...cached.skills);
|
|
458
|
-
if (!destination)
|
|
498
|
+
if (!destination) localSkillMounts.push({ root: cacheFS.root, mountPath });
|
|
459
499
|
logger.info("remote skill ready", {
|
|
460
500
|
source: sourceOptions.source,
|
|
461
|
-
destination: databricksBasePath ??
|
|
501
|
+
destination: databricksBasePath ?? mountPath,
|
|
462
502
|
downloadedAt: cached.downloadedAt,
|
|
463
503
|
skills: cached.skills,
|
|
464
504
|
cached: true,
|
|
@@ -489,12 +529,15 @@ export async function provisionRemoteSkills(
|
|
|
489
529
|
// mark a failed provision as fresh and suppress the retry for a day.
|
|
490
530
|
await writeMetadata(destination, key, record);
|
|
491
531
|
} else {
|
|
492
|
-
|
|
532
|
+
localSkillMounts.push({
|
|
533
|
+
root: await persistLocally(key, staged, record),
|
|
534
|
+
mountPath,
|
|
535
|
+
});
|
|
493
536
|
}
|
|
494
537
|
skillNames.push(...record.skills);
|
|
495
538
|
logger.info("remote skill installed", {
|
|
496
539
|
source: sourceOptions.source,
|
|
497
|
-
destination: databricksBasePath ??
|
|
540
|
+
destination: databricksBasePath ?? localSkillMounts.at(-1)?.mountPath,
|
|
498
541
|
skills: record.skills,
|
|
499
542
|
});
|
|
500
543
|
} catch (err) {
|
|
@@ -516,7 +559,22 @@ export async function provisionRemoteSkills(
|
|
|
516
559
|
}
|
|
517
560
|
}
|
|
518
561
|
|
|
519
|
-
|
|
562
|
+
assertUniqueLocalSkillMounts(localSkillMounts);
|
|
563
|
+
return { localSkillMounts, databricksBasePath, skillNames };
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/** Fail before workspace composition when two sources claim one virtual mount. */
|
|
567
|
+
function assertUniqueLocalSkillMounts(mounts: readonly LocalSkillMount[]): void {
|
|
568
|
+
const rootsByMount = new Map<string, string>();
|
|
569
|
+
for (const mount of mounts) {
|
|
570
|
+
const previousRoot = rootsByMount.get(mount.mountPath);
|
|
571
|
+
if (previousRoot && previousRoot !== mount.root) {
|
|
572
|
+
throw new Error(
|
|
573
|
+
`multiple remote skill sources resolve to virtual mount "${mount.mountPath}"; give each source a unique name`,
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
rootsByMount.set(mount.mountPath, mount.root);
|
|
577
|
+
}
|
|
520
578
|
}
|
|
521
579
|
|
|
522
580
|
/** Remove directories dropped by one source unless another source still owns them. */
|
package/src/sandbox.ts
CHANGED
|
@@ -68,7 +68,8 @@ export interface DatabricksSandboxOptions {
|
|
|
68
68
|
commandTimeoutMs?: number;
|
|
69
69
|
/**
|
|
70
70
|
* Provider used only when Databricks Sandbox is definitively unavailable.
|
|
71
|
-
*
|
|
71
|
+
* Disabled by default. Pass `"monty"`, Monty options, or another Mastra
|
|
72
|
+
* sandbox to enable a fallback explicitly.
|
|
72
73
|
*/
|
|
73
74
|
fallback?: false | "monty" | MontySandboxOptions | WorkspaceSandbox;
|
|
74
75
|
}
|
|
@@ -123,7 +124,7 @@ export class DatabricksSandbox implements WorkspaceSandbox {
|
|
|
123
124
|
this.inactivityTimeout = options.inactivityTimeout ?? DEFAULT_INACTIVITY_TIMEOUT;
|
|
124
125
|
this.startupTimeoutMs = options.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
|
|
125
126
|
this.commandTimeoutMs = options.commandTimeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS;
|
|
126
|
-
this.fallbackConfig = options.fallback ??
|
|
127
|
+
this.fallbackConfig = options.fallback ?? false;
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
get provider(): string {
|
package/src/workspaces.ts
CHANGED
|
@@ -60,6 +60,7 @@ import {
|
|
|
60
60
|
type MastraFileSystemAdapterOptions,
|
|
61
61
|
} from "./filesystems.ts";
|
|
62
62
|
import { MontySandbox } from "./monty-sandbox.ts";
|
|
63
|
+
import type { LocalSkillMount } from "./remote-skills.ts";
|
|
63
64
|
import { DatabricksSandbox, type DatabricksWorkspaceSandboxOptions } from "./sandbox.ts";
|
|
64
65
|
import { ORGANIZATION_ASSISTANT_PATH, personalWorkspacePath } from "./skill-paths.ts";
|
|
65
66
|
|
|
@@ -81,7 +82,7 @@ const databricksWorkspaceOptions = new WeakMap<Workspace, DatabricksWorkspaceOpt
|
|
|
81
82
|
|
|
82
83
|
/* -------------------------------- types -------------------------------- */
|
|
83
84
|
|
|
84
|
-
/** Per-request context for
|
|
85
|
+
/** Per-request context for workspace path resolvers. */
|
|
85
86
|
export interface DatabricksWorkspaceContext {
|
|
86
87
|
requestContext?: RequestContext;
|
|
87
88
|
}
|
|
@@ -97,13 +98,9 @@ export interface DatabricksWorkspacePathOptions {
|
|
|
97
98
|
/** Mount description shown in filesystem listings and workspace instructions. */
|
|
98
99
|
description?: string;
|
|
99
100
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
|
|
103
|
-
readable?: boolean;
|
|
104
|
-
/**
|
|
105
|
-
* Skill roots relative to this filesystem root. Defaults to `["."]`.
|
|
106
|
-
* Each path is joined to the actual mount path before Mastra scans it.
|
|
101
|
+
* Skill paths relative to this included filesystem path. Omit for a
|
|
102
|
+
* file-only mount. Each entry is joined to the actual mount path before
|
|
103
|
+
* Mastra receives it.
|
|
107
104
|
*/
|
|
108
105
|
skills?: readonly string[];
|
|
109
106
|
/**
|
|
@@ -176,9 +173,9 @@ type WorkspaceMountResolver = (
|
|
|
176
173
|
) => WorkspaceMountContribution | Promise<WorkspaceMountContribution>;
|
|
177
174
|
|
|
178
175
|
/**
|
|
179
|
-
* Sandbox selection for {@link databricksWorkspace}.
|
|
180
|
-
*
|
|
181
|
-
*
|
|
176
|
+
* Sandbox selection for {@link databricksWorkspace}. Command execution is
|
|
177
|
+
* disabled by default; Monty, Databricks Sandbox, or a Mastra provider/resolver
|
|
178
|
+
* must be selected explicitly.
|
|
182
179
|
*/
|
|
183
180
|
export type WorkspaceSandboxSelection =
|
|
184
181
|
| false
|
|
@@ -204,26 +201,28 @@ export interface DatabricksWorkspaceOptions extends Omit<
|
|
|
204
201
|
paths?: readonly DatabricksWorkspacePathValue[];
|
|
205
202
|
/** Native Mastra filesystems composed beside the Databricks paths. */
|
|
206
203
|
mounts?: Record<string, WorkspaceFilesystem>;
|
|
207
|
-
/**
|
|
204
|
+
/** Native Mastra skill paths or resolver composed with path-declared skills. */
|
|
208
205
|
skills?: SkillsResolver;
|
|
209
206
|
/** Filesystem cache filters. Ignored when the AppKit files-cache plugin is absent. */
|
|
210
207
|
cache?: DatabricksWorkspaceCache;
|
|
211
208
|
/** AppKit plugin context used to discover optional sibling capabilities. */
|
|
212
209
|
pluginContext?: pluginRegistry.PluginContextLike;
|
|
213
210
|
/**
|
|
214
|
-
* Command sandbox.
|
|
215
|
-
*
|
|
216
|
-
* explicit Mastra sandbox/provider resolver.
|
|
211
|
+
* Command sandbox. Disabled by default. Pass `"monty"`, `"databricks"`, or
|
|
212
|
+
* Databricks Sandbox options to enable one of the built-in providers, or pass
|
|
213
|
+
* an explicit Mastra sandbox/provider resolver.
|
|
217
214
|
*/
|
|
218
215
|
sandbox?: WorkspaceSandboxSelection;
|
|
216
|
+
/** Add user-scoped `/tmp` only when no configured Databricks path mounts writable. */
|
|
217
|
+
fallbackToTmp?: boolean;
|
|
219
218
|
/**
|
|
220
|
-
* Extra LOCAL skill
|
|
221
|
-
* Used by the plugin to
|
|
222
|
-
*
|
|
223
|
-
* need no entry here
|
|
224
|
-
* already scans.
|
|
219
|
+
* Extra LOCAL skill trees mounted read-only for every request's skill
|
|
220
|
+
* discovery. Used by the plugin to expose remote skills from local backing
|
|
221
|
+
* storage at stable virtual paths (see `remote-skills.ts`). Databricks-hosted
|
|
222
|
+
* remote skills need no entry here because the built-in Assistant mount
|
|
223
|
+
* already scans them.
|
|
225
224
|
*/
|
|
226
|
-
|
|
225
|
+
extraSkillMounts?: LocalSkillMount[];
|
|
227
226
|
}
|
|
228
227
|
|
|
229
228
|
/* ------------------------------- defaults ------------------------------- */
|
|
@@ -234,14 +233,12 @@ export interface DatabricksWorkspaceOptions extends Omit<
|
|
|
234
233
|
export const DEFAULT_DATABRICKS_WORKSPACE_PATHS: readonly DatabricksWorkspacePathOptions[] = [
|
|
235
234
|
{
|
|
236
235
|
path: ORGANIZATION_ASSISTANT_PATH,
|
|
237
|
-
readable: true,
|
|
238
236
|
skills: ["skills"],
|
|
239
237
|
writable: true,
|
|
240
238
|
createRoot: false,
|
|
241
239
|
},
|
|
242
240
|
{
|
|
243
241
|
path: "~",
|
|
244
|
-
readable: true,
|
|
245
242
|
skills: [".assistant/skills"],
|
|
246
243
|
writable: true,
|
|
247
244
|
createRoot: false,
|
|
@@ -279,7 +276,7 @@ export function databricksWorkspaceConfig(
|
|
|
279
276
|
const filesystemSourceKey = `${id}:${++workspaceSourceSequence}`;
|
|
280
277
|
const paths = resolveDatabricksWorkspacePaths(options);
|
|
281
278
|
const configuredPaths = paths.length;
|
|
282
|
-
const
|
|
279
|
+
const extraSkillMounts = options.extraSkillMounts ?? [];
|
|
283
280
|
const filesCache = pluginRegistry.instance(options.pluginContext, filesCachePlugin)?.exports();
|
|
284
281
|
const configuredCache = options.cache;
|
|
285
282
|
const mountFilesCache = configuredCache === false ? undefined : filesCache;
|
|
@@ -290,9 +287,10 @@ export function databricksWorkspaceConfig(
|
|
|
290
287
|
const resolvers = buildMountResolvers(
|
|
291
288
|
paths,
|
|
292
289
|
options.mounts,
|
|
293
|
-
localSkillMountResolvers(
|
|
290
|
+
localSkillMountResolvers(extraSkillMounts),
|
|
294
291
|
mountFilesCache,
|
|
295
292
|
configuredCache,
|
|
293
|
+
options.fallbackToTmp === true,
|
|
296
294
|
);
|
|
297
295
|
const resolveContribution = contributionResolver(resolvers);
|
|
298
296
|
const resolveFilesystem = (context: DatabricksWorkspaceContext) =>
|
|
@@ -302,6 +300,7 @@ export function databricksWorkspaceConfig(
|
|
|
302
300
|
retainFilesystemSource ? mountFilesCache : undefined,
|
|
303
301
|
filesystemSourceKey,
|
|
304
302
|
);
|
|
303
|
+
const hasSkillPaths = paths.some(pathMayProvideSkills) || extraSkillMounts.length > 0;
|
|
305
304
|
const skills: SkillsResolver =
|
|
306
305
|
options.skills ??
|
|
307
306
|
(async ({ requestContext }) => {
|
|
@@ -322,10 +321,11 @@ export function databricksWorkspaceConfig(
|
|
|
322
321
|
resolverCount: resolvers.length,
|
|
323
322
|
configuredPaths,
|
|
324
323
|
customMounts: Object.keys(options.mounts ?? {}).length,
|
|
325
|
-
|
|
324
|
+
skillsConfigured: options.skills !== undefined || hasSkillPaths,
|
|
325
|
+
customSkillsResolver: options.skills !== undefined,
|
|
326
326
|
checkSkillFileMtime,
|
|
327
327
|
bm25,
|
|
328
|
-
|
|
328
|
+
extraSkillMounts: extraSkillMounts.length,
|
|
329
329
|
sandbox: sandbox ? sandboxName(options.sandbox) : "disabled",
|
|
330
330
|
});
|
|
331
331
|
|
|
@@ -337,7 +337,8 @@ export function databricksWorkspaceConfig(
|
|
|
337
337
|
cache: _cache,
|
|
338
338
|
pluginContext: _pluginContext,
|
|
339
339
|
sandbox: _sandbox,
|
|
340
|
-
|
|
340
|
+
fallbackToTmp: _fallbackToTmp,
|
|
341
|
+
extraSkillMounts: _extraSkillMounts,
|
|
341
342
|
...workspaceOptions
|
|
342
343
|
} = options;
|
|
343
344
|
return {
|
|
@@ -345,7 +346,7 @@ export function databricksWorkspaceConfig(
|
|
|
345
346
|
id,
|
|
346
347
|
name,
|
|
347
348
|
filesystem: resolveFilesystem,
|
|
348
|
-
...(
|
|
349
|
+
...(options.skills !== undefined || hasSkillPaths
|
|
349
350
|
? {
|
|
350
351
|
skills,
|
|
351
352
|
checkSkillFileMtime,
|
|
@@ -393,6 +394,13 @@ export function resolveDatabricksWorkspacePaths(
|
|
|
393
394
|
];
|
|
394
395
|
}
|
|
395
396
|
|
|
397
|
+
/** Whether one included path can contribute Mastra skill paths. */
|
|
398
|
+
function pathMayProvideSkills(path: DatabricksWorkspacePathValue): boolean {
|
|
399
|
+
if (typeof path === "function") return true;
|
|
400
|
+
if (path === false || typeof path === "string") return false;
|
|
401
|
+
return (path.skills?.length ?? 0) > 0;
|
|
402
|
+
}
|
|
403
|
+
|
|
396
404
|
/* ---------------------------- private helpers ---------------------------- */
|
|
397
405
|
|
|
398
406
|
function workspaceTools(configured: WorkspaceToolsConfig | undefined): WorkspaceToolsConfig {
|
|
@@ -408,7 +416,7 @@ function resolveWorkspaceSandbox(
|
|
|
408
416
|
workspaceId: string,
|
|
409
417
|
workspaceName: string,
|
|
410
418
|
): WorkspaceSandbox | WorkspaceSandboxResolver | undefined {
|
|
411
|
-
const configured = selection ??
|
|
419
|
+
const configured = selection ?? false;
|
|
412
420
|
if (configured === false) return undefined;
|
|
413
421
|
if (configured === "monty") return new MontySandbox();
|
|
414
422
|
if (typeof configured === "function" || isSandbox(configured)) return configured;
|
|
@@ -462,9 +470,9 @@ function isSandbox(value: unknown): value is WorkspaceSandbox {
|
|
|
462
470
|
}
|
|
463
471
|
|
|
464
472
|
function sandboxName(selection: WorkspaceSandboxSelection | undefined): string {
|
|
465
|
-
if (selection === undefined || selection ===
|
|
473
|
+
if (selection === undefined || selection === false) return "disabled";
|
|
474
|
+
if (selection === "monty") return "monty";
|
|
466
475
|
if (selection === "databricks") return "databricks";
|
|
467
|
-
if (selection === false) return "disabled";
|
|
468
476
|
if (typeof selection === "function") return "resolver";
|
|
469
477
|
return isSandbox(selection) ? selection.provider : (selection.provider ?? "databricks");
|
|
470
478
|
}
|
|
@@ -504,14 +512,14 @@ async function resolveFilesCacheScope(context: DatabricksWorkspaceContext): Prom
|
|
|
504
512
|
};
|
|
505
513
|
}
|
|
506
514
|
|
|
507
|
-
/** Join one relative skill
|
|
508
|
-
function mountedSkillPath(mount: string,
|
|
509
|
-
if (posixPath.isAbsolute(
|
|
510
|
-
throw new TypeError(`Skill path must be relative to its mount: ${
|
|
515
|
+
/** Join one relative skill path to its actual composite mount path. */
|
|
516
|
+
function mountedSkillPath(mount: string, skillPath: string): string {
|
|
517
|
+
if (posixPath.isAbsolute(skillPath)) {
|
|
518
|
+
throw new TypeError(`Skill path must be relative to its mount: ${skillPath}`);
|
|
511
519
|
}
|
|
512
|
-
const normalized = posixPath.normalize(
|
|
520
|
+
const normalized = posixPath.normalize(skillPath);
|
|
513
521
|
if (!normalized.ok) {
|
|
514
|
-
throw new TypeError(`Skill path escapes its mount: ${
|
|
522
|
+
throw new TypeError(`Skill path escapes its mount: ${skillPath}`);
|
|
515
523
|
}
|
|
516
524
|
return posixPath.join(posixPath.normalizeRoot(mount), normalized.path.slice(1));
|
|
517
525
|
}
|
|
@@ -523,19 +531,21 @@ function isWorkspaceRoot(root: string): boolean {
|
|
|
523
531
|
}
|
|
524
532
|
|
|
525
533
|
/**
|
|
526
|
-
* Mount resolver for
|
|
534
|
+
* Mount resolver for configured Databricks and temporary paths.
|
|
527
535
|
*
|
|
528
536
|
* Gates on workspace file scope (or development mode), then mounts every
|
|
529
537
|
* folder whose location resolves for this request.
|
|
530
538
|
*/
|
|
531
|
-
async function
|
|
539
|
+
async function resolveWorkspacePathMounts(
|
|
532
540
|
paths: readonly DatabricksWorkspacePathValue[],
|
|
533
541
|
context: DatabricksWorkspaceContext,
|
|
534
542
|
filesCache: FilesCacheExports | undefined,
|
|
535
543
|
cacheConfig: DatabricksWorkspaceCache | undefined,
|
|
544
|
+
fallbackToTmp: boolean,
|
|
536
545
|
): Promise<WorkspaceMountContribution> {
|
|
537
546
|
const mounts: Record<string, WorkspaceFilesystem> = {};
|
|
538
547
|
const skillPaths: string[] = [];
|
|
548
|
+
let hasWritableMount = false;
|
|
539
549
|
const requestContext = context.requestContext;
|
|
540
550
|
|
|
541
551
|
const canMountDatabricks = Boolean(requestContext && shouldMountSkillFolders(requestContext));
|
|
@@ -550,9 +560,8 @@ async function resolveSkillFolderMounts(
|
|
|
550
560
|
if (posixPath.isWithinRoot(SCRATCH_MOUNT, configured.path)) {
|
|
551
561
|
const mount = configured.mount ?? configured.path;
|
|
552
562
|
mounts[mount] = userTempFilesystem(context, configured.path);
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
}
|
|
563
|
+
hasWritableMount = true;
|
|
564
|
+
skillPaths.push(...(configured.skills ?? []).map((path) => mountedSkillPath(mount, path)));
|
|
556
565
|
continue;
|
|
557
566
|
}
|
|
558
567
|
if (!canMountDatabricks) {
|
|
@@ -573,12 +582,15 @@ async function resolveSkillFolderMounts(
|
|
|
573
582
|
continue;
|
|
574
583
|
}
|
|
575
584
|
mounts[mount] = resolved.filesystem;
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
}
|
|
585
|
+
hasWritableMount ||= resolved.writable;
|
|
586
|
+
skillPaths.push(...(configured.skills ?? []).map((path) => mountedSkillPath(mount, path)));
|
|
579
587
|
}
|
|
580
588
|
|
|
581
|
-
|
|
589
|
+
if (fallbackToTmp && !hasWritableMount) {
|
|
590
|
+
mounts[SCRATCH_MOUNT] = userTempFilesystem(context, SCRATCH_MOUNT);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
logger.debug("workspace-paths:mounted", {
|
|
582
594
|
mountKeys: Object.keys(mounts),
|
|
583
595
|
skillPaths,
|
|
584
596
|
});
|
|
@@ -593,7 +605,6 @@ interface ResolvedDatabricksWorkspacePath {
|
|
|
593
605
|
path: string;
|
|
594
606
|
displayName?: string;
|
|
595
607
|
description?: string;
|
|
596
|
-
readable?: boolean;
|
|
597
608
|
skills?: readonly string[];
|
|
598
609
|
writable?: boolean;
|
|
599
610
|
createRoot?: boolean;
|
|
@@ -634,12 +645,13 @@ async function resolveSkillFolderFilesystem(
|
|
|
634
645
|
| {
|
|
635
646
|
filesystem: WorkspaceFilesystem;
|
|
636
647
|
root?: string;
|
|
648
|
+
writable: boolean;
|
|
637
649
|
}
|
|
638
650
|
| undefined
|
|
639
651
|
> {
|
|
640
652
|
// A path mount needs the request's OBO client to reach the workspace.
|
|
641
653
|
if (!cache.client) {
|
|
642
|
-
logger.debug("
|
|
654
|
+
logger.debug("workspace-path:skipped", {
|
|
643
655
|
path: folder.path,
|
|
644
656
|
reason: "missing-obo-client",
|
|
645
657
|
});
|
|
@@ -649,12 +661,7 @@ async function resolveSkillFolderFilesystem(
|
|
|
649
661
|
if (!root) return undefined;
|
|
650
662
|
const writable = folder.writable ?? isWorkspaceRoot(root);
|
|
651
663
|
const cacheOptions = cache.fileSystemCache
|
|
652
|
-
? await resolveFileCacheOptions(
|
|
653
|
-
cacheConfig,
|
|
654
|
-
context,
|
|
655
|
-
root,
|
|
656
|
-
folder.readable === false ? [] : (folder.skills ?? ["."]),
|
|
657
|
-
)
|
|
664
|
+
? await resolveFileCacheOptions(cacheConfig, context, root, folder.skills ?? [])
|
|
658
665
|
: undefined;
|
|
659
666
|
const resolved = await databricksFilesystem(
|
|
660
667
|
cache.client,
|
|
@@ -668,7 +675,7 @@ async function resolveSkillFolderFilesystem(
|
|
|
668
675
|
...(folder.displayName ? { displayName: folder.displayName } : {}),
|
|
669
676
|
},
|
|
670
677
|
);
|
|
671
|
-
return resolved ? { ...resolved, root } : undefined;
|
|
678
|
+
return resolved ? { ...resolved, root, writable } : undefined;
|
|
672
679
|
}
|
|
673
680
|
|
|
674
681
|
async function resolveCacheConfig(
|
|
@@ -782,18 +789,21 @@ function resolveWorkspaceIdentity(options: DatabricksWorkspaceOptions): {
|
|
|
782
789
|
return { id, name };
|
|
783
790
|
}
|
|
784
791
|
|
|
785
|
-
/** Collect the
|
|
792
|
+
/** Collect the Databricks path resolver and any caller-supplied mounts. */
|
|
786
793
|
function buildMountResolvers(
|
|
787
794
|
paths: readonly DatabricksWorkspacePathValue[],
|
|
788
795
|
mounts: Record<string, WorkspaceFilesystem> | undefined,
|
|
789
796
|
localMounts: WorkspaceMountResolver[],
|
|
790
797
|
filesCache: FilesCacheExports | undefined,
|
|
791
798
|
cacheConfig: DatabricksWorkspaceCache | undefined,
|
|
799
|
+
fallbackToTmp: boolean,
|
|
792
800
|
): WorkspaceMountResolver[] {
|
|
793
801
|
const resolvers: WorkspaceMountResolver[] = [];
|
|
794
802
|
const pathCount = paths.length;
|
|
795
|
-
if (pathCount > 0) {
|
|
796
|
-
resolvers.push((context) =>
|
|
803
|
+
if (pathCount > 0 || fallbackToTmp) {
|
|
804
|
+
resolvers.push((context) =>
|
|
805
|
+
resolveWorkspacePathMounts(paths, context, filesCache, cacheConfig, fallbackToTmp),
|
|
806
|
+
);
|
|
797
807
|
}
|
|
798
808
|
if (mounts && Object.keys(mounts).length > 0) resolvers.push(() => ({ mounts }));
|
|
799
809
|
resolvers.push(...localMounts);
|
|
@@ -830,10 +840,16 @@ function userTempFilesystem(
|
|
|
830
840
|
});
|
|
831
841
|
}
|
|
832
842
|
|
|
833
|
-
/** Mount startup-provisioned local skill
|
|
834
|
-
function localSkillMountResolvers(
|
|
835
|
-
|
|
836
|
-
|
|
843
|
+
/** Mount startup-provisioned local skill trees into Mastra's workspace filesystem. */
|
|
844
|
+
function localSkillMountResolvers(mounts: readonly LocalSkillMount[]): WorkspaceMountResolver[] {
|
|
845
|
+
const unique = new Map<string, LocalSkillMount>();
|
|
846
|
+
for (const entry of mounts) {
|
|
847
|
+
const root = entry.root.trim();
|
|
848
|
+
if (!root) continue;
|
|
849
|
+
unique.set(`${entry.mountPath}\0${root}`, { root, mountPath: entry.mountPath });
|
|
850
|
+
}
|
|
851
|
+
return [...unique.values()].map(({ root, mountPath }) => {
|
|
852
|
+
const mount = posixPath.normalizeRoot(mountPath);
|
|
837
853
|
const source = new LocalFileSystem({
|
|
838
854
|
root,
|
|
839
855
|
readOnly: true,
|
|
@@ -1055,30 +1071,30 @@ async function resolveWorkspaceFilesystem(
|
|
|
1055
1071
|
}
|
|
1056
1072
|
|
|
1057
1073
|
/**
|
|
1058
|
-
* Expand skill
|
|
1074
|
+
* Expand configured skill paths into concrete skill directories and keep the first
|
|
1059
1075
|
* same-named directory. Mastra still owns parsing, indexing, and refreshes,
|
|
1060
1076
|
* while earlier configured roots retain precedence over later roots.
|
|
1061
1077
|
*/
|
|
1062
1078
|
async function resolveDistinctSkillPaths(
|
|
1063
1079
|
filesystem: WorkspaceFilesystem,
|
|
1064
|
-
|
|
1080
|
+
paths: readonly string[],
|
|
1065
1081
|
): Promise<string[]> {
|
|
1066
1082
|
const selected = new Map<string, string>();
|
|
1067
|
-
for (const
|
|
1083
|
+
for (const path of paths) {
|
|
1068
1084
|
try {
|
|
1069
|
-
const entries = await filesystem.readdir(
|
|
1085
|
+
const entries = await filesystem.readdir(path);
|
|
1070
1086
|
if (entries.some((entry) => entry.type === "file" && entry.name === "SKILL.md")) {
|
|
1071
|
-
const name = posixPath.basename(
|
|
1072
|
-
if (!selected.has(name)) selected.set(name,
|
|
1087
|
+
const name = posixPath.basename(path);
|
|
1088
|
+
if (!selected.has(name)) selected.set(name, path);
|
|
1073
1089
|
continue;
|
|
1074
1090
|
}
|
|
1075
1091
|
for (const entry of entries) {
|
|
1076
1092
|
if (entry.type !== "directory" || selected.has(entry.name)) continue;
|
|
1077
|
-
selected.set(entry.name, posixPath.join(
|
|
1093
|
+
selected.set(entry.name, posixPath.join(path, entry.name));
|
|
1078
1094
|
}
|
|
1079
1095
|
} catch (error) {
|
|
1080
|
-
logger.debug("skill-
|
|
1081
|
-
|
|
1096
|
+
logger.debug("skill-path:skipped", {
|
|
1097
|
+
path,
|
|
1082
1098
|
error: errorUtils.errorMessage(error),
|
|
1083
1099
|
});
|
|
1084
1100
|
}
|