@dbx-tools/appkit-mastra 0.9.93 → 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.
@@ -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.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",
@@ -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.95",
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 { 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"));
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
+ }
@@ -10,10 +10,18 @@
10
10
  * @module
11
11
  */
12
12
 
13
- /** Shared Assistant skills tree, readable by everyone in the workspace. */
14
- export const ASSISTANT_SHARED_SKILLS_PATH = "/Workspace/.assistant/skills";
13
+ /** Organization Assistant root, mounted at its actual workspace path. */
14
+ export const ORGANIZATION_ASSISTANT_PATH = "/Workspace/.assistant";
15
15
 
16
- /** Assistant skills tree owned by one user (the "save this as a skill" target). */
17
- export function userAssistantSkillsPath(userEmail: string): string {
18
- return `/Users/${userEmail.trim()}/.assistant/skills`;
16
+ /** Organization skills tree, readable by everyone in the workspace. */
17
+ export const ORGANIZATION_SKILLS_PATH = `${ORGANIZATION_ASSISTANT_PATH}/skills`;
18
+
19
+ /** Workspace home root for one user. */
20
+ export function personalWorkspacePath(userEmail: string): string {
21
+ return `/Workspace/Users/${userEmail.trim()}`;
22
+ }
23
+
24
+ /** Personal skills tree owned by one user. */
25
+ export function personalSkillsPath(userEmail: string): string {
26
+ return `${personalWorkspacePath(userEmail)}/.assistant/skills`;
19
27
  }