@mastra/factory 0.24.0-alpha.9 → 0.25.0-alpha.0
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 +40 -13
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +18 -4
- package/dist/auth.js.map +1 -1
- package/dist/boards/review.d.ts.map +1 -1
- package/dist/boards/review.js +3 -2
- package/dist/boards/review.js.map +1 -1
- package/dist/environment/build-runner.js +2 -1
- package/dist/environment/build-runner.js.map +1 -1
- package/dist/environment/build-workflow.js +1 -1
- package/dist/environment/build-workflow.js.map +1 -1
- package/dist/environment/build.js +16 -14
- package/dist/environment/build.js.map +1 -1
- package/dist/factory.d.ts +3 -0
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +172 -88
- package/dist/factory.js.map +1 -1
- package/dist/integrations/base.d.ts +15 -2
- package/dist/integrations/base.d.ts.map +1 -1
- package/dist/integrations/github/app-identity.js.map +1 -1
- package/dist/integrations/github/integration.js +1 -1
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/reconcile-worker.js +1 -1
- package/dist/integrations/github/reconcile-worker.js.map +1 -1
- package/dist/integrations/github/routes.d.ts +1 -1
- package/dist/integrations/github/routes.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +24 -13
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/rules.d.ts.map +1 -1
- package/dist/integrations/github/rules.js +53 -9
- package/dist/integrations/github/rules.js.map +1 -1
- package/dist/integrations/github/sandbox.js +2 -2
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/integrations/github/session-subscriptions.d.ts.map +1 -1
- package/dist/integrations/github/session-subscriptions.js +1 -2
- package/dist/integrations/github/session-subscriptions.js.map +1 -1
- package/dist/integrations/github/subscriptions.d.ts +7 -4
- package/dist/integrations/github/subscriptions.d.ts.map +1 -1
- package/dist/integrations/github/subscriptions.js +5 -5
- package/dist/integrations/github/subscriptions.js.map +1 -1
- package/dist/integrations/gitlab/api.js.map +1 -1
- package/dist/integrations/gitlab/integration.js.map +1 -1
- package/dist/integrations/gitlab/reconciliation-config.js +2 -1
- package/dist/integrations/gitlab/reconciliation-config.js.map +1 -1
- package/dist/integrations/gitlab/routes.d.ts.map +1 -1
- package/dist/integrations/gitlab/routes.js +10 -6
- package/dist/integrations/gitlab/routes.js.map +1 -1
- package/dist/integrations/gitlab/rules.js.map +1 -1
- package/dist/integrations/gitlab/session-subscriptions.d.ts.map +1 -1
- package/dist/integrations/gitlab/session-subscriptions.js +1 -2
- package/dist/integrations/gitlab/session-subscriptions.js.map +1 -1
- package/dist/integrations/gitlab/subscriptions.d.ts +7 -4
- package/dist/integrations/gitlab/subscriptions.d.ts.map +1 -1
- package/dist/integrations/gitlab/subscriptions.js +5 -5
- package/dist/integrations/gitlab/subscriptions.js.map +1 -1
- package/dist/integrations/incidentio/api.js.map +1 -1
- package/dist/integrations/incidentio/integration.js.map +1 -1
- package/dist/integrations/incidentio/rules.js.map +1 -1
- package/dist/integrations/issue-reconcile-worker.js +1 -1
- package/dist/integrations/issue-reconcile-worker.js.map +1 -1
- package/dist/integrations/issue-reconciler.js +1 -1
- package/dist/integrations/issue-reconciler.js.map +1 -1
- package/dist/integrations/jira/api.js.map +1 -1
- package/dist/integrations/jira/integration.js.map +1 -1
- package/dist/integrations/jira/rules.js.map +1 -1
- package/dist/integrations/linear/agent-tools.d.ts.map +1 -1
- package/dist/integrations/linear/agent-tools.js.map +1 -1
- package/dist/integrations/linear/integration.js.map +1 -1
- package/dist/integrations/linear/rules.js.map +1 -1
- package/dist/integrations/platform/api-client.js.map +1 -1
- package/dist/integrations/platform/github/event-worker.js +1 -1
- package/dist/integrations/platform/github/event-worker.js.map +1 -1
- package/dist/integrations/platform/github/integration.js +2 -2
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/integrations/platform/gitlab/event-worker.js.map +1 -1
- package/dist/integrations/platform/gitlab/integration.js.map +1 -1
- package/dist/integrations/platform/incidentio/integration.js +2 -1
- package/dist/integrations/platform/incidentio/integration.js.map +1 -1
- package/dist/integrations/platform/jira/integration.js.map +1 -1
- package/dist/integrations/platform/linear/event-worker.js +1 -1
- package/dist/integrations/platform/linear/event-worker.js.map +1 -1
- package/dist/integrations/platform/linear/integration.js.map +1 -1
- package/dist/{session → integrations/slack}/channel-reply-processor.d.ts +11 -9
- package/dist/integrations/slack/channel-reply-processor.d.ts.map +1 -0
- package/dist/integrations/slack/channel-reply-processor.js +61 -0
- package/dist/integrations/slack/channel-reply-processor.js.map +1 -0
- package/dist/integrations/slack/channel-session-link-processor.d.ts.map +1 -0
- package/dist/{session → integrations/slack}/channel-session-link-processor.js +14 -4
- package/dist/integrations/slack/channel-session-link-processor.js.map +1 -0
- package/dist/integrations/slack/connect-route.js +1 -1
- package/dist/integrations/slack/connect-route.js.map +1 -1
- package/dist/integrations/slack/feed-publisher.js +1 -1
- package/dist/integrations/slack/feed-publisher.js.map +1 -1
- package/dist/integrations/slack/integration.d.ts +25 -1
- package/dist/integrations/slack/integration.d.ts.map +1 -1
- package/dist/integrations/slack/integration.js +56 -6
- package/dist/integrations/slack/integration.js.map +1 -1
- package/dist/integrations/slack/reply-reminder-processor.d.ts +23 -0
- package/dist/integrations/slack/reply-reminder-processor.d.ts.map +1 -0
- package/dist/integrations/slack/reply-reminder-processor.js +58 -0
- package/dist/integrations/slack/reply-reminder-processor.js.map +1 -0
- package/dist/integrations/slack/reply-tool.d.ts +31 -0
- package/dist/integrations/slack/reply-tool.d.ts.map +1 -0
- package/dist/integrations/slack/reply-tool.js +203 -0
- package/dist/integrations/slack/reply-tool.js.map +1 -0
- package/dist/integrations/slack/share-file-tool.d.ts +45 -0
- package/dist/integrations/slack/share-file-tool.d.ts.map +1 -0
- package/dist/integrations/slack/share-file-tool.js +106 -0
- package/dist/integrations/slack/share-file-tool.js.map +1 -0
- package/dist/integrations/slack/slack.d.ts +16 -5
- package/dist/integrations/slack/slack.d.ts.map +1 -1
- package/dist/integrations/slack/slack.js +141 -40
- package/dist/integrations/slack/slack.js.map +1 -1
- package/dist/integrations/subscription-session.d.ts +0 -1
- package/dist/integrations/subscription-session.d.ts.map +1 -1
- package/dist/integrations/subscription-session.js +1 -4
- package/dist/integrations/subscription-session.js.map +1 -1
- package/dist/integrations/workos/integration.js.map +1 -1
- package/dist/mcp/events.d.ts +73 -0
- package/dist/mcp/events.d.ts.map +1 -0
- package/dist/mcp/events.js +158 -0
- package/dist/mcp/events.js.map +1 -0
- package/dist/mcp/routes.d.ts +0 -1
- package/dist/mcp/routes.d.ts.map +1 -1
- package/dist/mcp/routes.js +31 -4
- package/dist/mcp/routes.js.map +1 -1
- package/dist/packages/_internals/workspace/dist/index.js +18 -1
- package/dist/packages/_internals/workspace/dist/index.js.map +1 -1
- package/dist/platform-auth-host.d.ts.map +1 -1
- package/dist/platform-auth-host.js +1 -1
- package/dist/platform-auth-host.js.map +1 -1
- package/dist/routes/attention-activity.js.map +1 -1
- package/dist/routes/attention-parked.js.map +1 -1
- package/dist/routes/attention-providers.js.map +1 -1
- package/dist/routes/automation-runs.js.map +1 -1
- package/dist/routes/config.d.ts +8 -7
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +70 -28
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/contracts.d.ts +2 -0
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/contracts.js +2 -2
- package/dist/routes/contracts.js.map +1 -1
- package/dist/routes/custom-provider-source.js.map +1 -1
- package/dist/routes/fs.d.ts +31 -23
- package/dist/routes/fs.d.ts.map +1 -1
- package/dist/routes/fs.js +218 -64
- package/dist/routes/fs.js.map +1 -1
- package/dist/routes/intake.js +2 -1
- package/dist/routes/intake.js.map +1 -1
- package/dist/routes/knowledge.js +1 -1
- package/dist/routes/knowledge.js.map +1 -1
- package/dist/routes/oauth.js +3 -2
- package/dist/routes/oauth.js.map +1 -1
- package/dist/routes/projects.d.ts +1 -1
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +33 -31
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/provider-credentials.d.ts.map +1 -1
- package/dist/routes/provider-credentials.js +6 -1
- package/dist/routes/provider-credentials.js.map +1 -1
- package/dist/routes/session-access.d.ts +60 -0
- package/dist/routes/session-access.d.ts.map +1 -0
- package/dist/routes/session-access.js +202 -0
- package/dist/routes/session-access.js.map +1 -0
- package/dist/routes/skills.d.ts +2 -4
- package/dist/routes/skills.d.ts.map +1 -1
- package/dist/routes/skills.js +23 -68
- package/dist/routes/skills.js.map +1 -1
- package/dist/routes/source-control-sessions.d.ts +8 -1
- package/dist/routes/source-control-sessions.d.ts.map +1 -1
- package/dist/routes/source-control-sessions.js +239 -19
- package/dist/routes/source-control-sessions.js.map +1 -1
- package/dist/routes/surface.d.ts +7 -0
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +20 -8
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/tenant-credentials.js.map +1 -1
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/binding-context.js +11 -9
- package/dist/rules/binding-context.js.map +1 -1
- package/dist/rules/dispatcher.d.ts +1 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +21 -13
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/processor.d.ts +2 -2
- package/dist/rules/processor.d.ts.map +1 -1
- package/dist/rules/processor.js +3 -3
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/start-coordinator.d.ts +4 -0
- package/dist/rules/start-coordinator.d.ts.map +1 -1
- package/dist/rules/start-coordinator.js +36 -4
- package/dist/rules/start-coordinator.js.map +1 -1
- package/dist/rules/tools.js +2 -2
- package/dist/rules/tools.js.map +1 -1
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/sandbox/callback-factory-sandbox.js.map +1 -1
- package/dist/sandbox/local-factory-sandbox.js.map +1 -1
- package/dist/sandbox/session-retirement.d.ts +11 -0
- package/dist/sandbox/session-retirement.d.ts.map +1 -1
- package/dist/sandbox/session-retirement.js +38 -1
- package/dist/sandbox/session-retirement.js.map +1 -1
- package/dist/sandbox/session-sandbox.d.ts +2 -0
- package/dist/sandbox/session-sandbox.d.ts.map +1 -1
- package/dist/sandbox/session-sandbox.js +5 -1
- package/dist/sandbox/session-sandbox.js.map +1 -1
- package/dist/secret-encryption.d.ts.map +1 -1
- package/dist/secret-encryption.js +65 -25
- package/dist/secret-encryption.js.map +1 -1
- package/dist/session/credential-policy.d.ts +4 -2
- package/dist/session/credential-policy.d.ts.map +1 -1
- package/dist/session/credential-policy.js +18 -7
- package/dist/session/credential-policy.js.map +1 -1
- package/dist/session/default-model-hydration.d.ts +1 -0
- package/dist/session/default-model-hydration.d.ts.map +1 -1
- package/dist/session/default-model-hydration.js +3 -2
- package/dist/session/default-model-hydration.js.map +1 -1
- package/dist/session/environment-state-processor.js +1 -1
- package/dist/session/environment-state-processor.js.map +1 -1
- package/dist/session/factory-session.d.ts.map +1 -1
- package/dist/session/factory-session.js +14 -1
- package/dist/session/factory-session.js.map +1 -1
- package/dist/session/filesystem-capture.d.ts +3 -3
- package/dist/session/filesystem-capture.d.ts.map +1 -1
- package/dist/session/filesystem-capture.js +16 -17
- package/dist/session/filesystem-capture.js.map +1 -1
- package/dist/session/first-exec-capture.d.ts +1 -1
- package/dist/session/first-exec-capture.d.ts.map +1 -1
- package/dist/session/first-exec-capture.js +1 -1
- package/dist/session/first-exec-capture.js.map +1 -1
- package/dist/session/first-message-capture.d.ts +1 -1
- package/dist/session/first-message-capture.d.ts.map +1 -1
- package/dist/session/first-message-capture.js +1 -1
- package/dist/session/first-message-capture.js.map +1 -1
- package/dist/session/live-sessions.d.ts +1 -1
- package/dist/session/live-sessions.js +1 -1
- package/dist/session/live-sessions.js.map +1 -1
- package/dist/session/memory-settings-hydration.d.ts +1 -1
- package/dist/session/memory-settings-hydration.d.ts.map +1 -1
- package/dist/session/memory-settings-hydration.js +2 -1
- package/dist/session/memory-settings-hydration.js.map +1 -1
- package/dist/session/resource-migration.d.ts +105 -0
- package/dist/session/resource-migration.d.ts.map +1 -0
- package/dist/session/resource-migration.js +195 -0
- package/dist/session/resource-migration.js.map +1 -0
- package/dist/session/run-audit.d.ts +1 -0
- package/dist/session/run-audit.d.ts.map +1 -1
- package/dist/session/run-audit.js +7 -2
- package/dist/session/run-audit.js.map +1 -1
- package/dist/session/session-threads.d.ts +63 -0
- package/dist/session/session-threads.d.ts.map +1 -0
- package/dist/session/session-threads.js +97 -0
- package/dist/session/session-threads.js.map +1 -0
- package/dist/session/source-control-tools.js +8 -8
- package/dist/session/source-control-tools.js.map +1 -1
- package/dist/session/thinking-default.d.ts +6 -13
- package/dist/session/thinking-default.d.ts.map +1 -1
- package/dist/session/thinking-default.js +19 -24
- package/dist/session/thinking-default.js.map +1 -1
- package/dist/session/thread-session-stamp.d.ts +21 -0
- package/dist/session/thread-session-stamp.d.ts.map +1 -0
- package/dist/session/thread-session-stamp.js +20 -0
- package/dist/session/thread-session-stamp.js.map +1 -0
- package/dist/session/thread-title-mirror.d.ts +1 -1
- package/dist/session/thread-title-mirror.d.ts.map +1 -1
- package/dist/session/thread-title-mirror.js +1 -1
- package/dist/session/thread-title-mirror.js.map +1 -1
- package/dist/session-owner-signal.d.ts +2 -0
- package/dist/session-owner-signal.d.ts.map +1 -0
- package/dist/session-owner-signal.js +6 -0
- package/dist/session-owner-signal.js.map +1 -0
- package/dist/skills/service.d.ts +11 -14
- package/dist/skills/service.d.ts.map +1 -1
- package/dist/skills/service.js +12 -22
- package/dist/skills/service.js.map +1 -1
- package/dist/spa-static.js.map +1 -1
- package/dist/state-signing.js +1 -1
- package/dist/state-signing.js.map +1 -1
- package/dist/storage/domains/audit/actions.d.ts +1 -0
- package/dist/storage/domains/audit/actions.d.ts.map +1 -1
- package/dist/storage/domains/audit/actions.js +2 -1
- package/dist/storage/domains/audit/actions.js.map +1 -1
- package/dist/storage/domains/audit/agent-audit.d.ts.map +1 -1
- package/dist/storage/domains/audit/agent-audit.js +2 -2
- package/dist/storage/domains/audit/agent-audit.js.map +1 -1
- package/dist/storage/domains/audit/base.d.ts +18 -2
- package/dist/storage/domains/audit/base.d.ts.map +1 -1
- package/dist/storage/domains/audit/base.js +22 -4
- package/dist/storage/domains/audit/base.js.map +1 -1
- package/dist/storage/domains/audit/domain.js.map +1 -1
- package/dist/storage/domains/channel-identity/base.js.map +1 -1
- package/dist/storage/domains/comments/base.js.map +1 -1
- package/dist/storage/domains/comments/domain.js.map +1 -1
- package/dist/storage/domains/comments/feed-context.js.map +1 -1
- package/dist/storage/domains/credentials/base.js.map +1 -1
- package/dist/storage/domains/custom-providers/base.js.map +1 -1
- package/dist/storage/domains/intake/base.js.map +1 -1
- package/dist/storage/domains/integrations/base.d.ts +8 -0
- package/dist/storage/domains/integrations/base.d.ts.map +1 -1
- package/dist/storage/domains/integrations/base.js +7 -0
- package/dist/storage/domains/integrations/base.js.map +1 -1
- package/dist/storage/domains/memory-settings/base.js.map +1 -1
- package/dist/storage/domains/model-defaults/base.d.ts +8 -0
- package/dist/storage/domains/model-defaults/base.d.ts.map +1 -1
- package/dist/storage/domains/model-defaults/base.js +18 -0
- package/dist/storage/domains/model-defaults/base.js.map +1 -1
- package/dist/storage/domains/projects/base.js.map +1 -1
- package/dist/storage/domains/queue-health/base.js.map +1 -1
- package/dist/storage/domains/source-control/base.d.ts +44 -0
- package/dist/storage/domains/source-control/base.d.ts.map +1 -1
- package/dist/storage/domains/source-control/base.js +109 -3
- package/dist/storage/domains/source-control/base.js.map +1 -1
- package/dist/storage/domains/source-control/inmemory.d.ts +23 -1
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/source-control/inmemory.js +35 -0
- package/dist/storage/domains/source-control/inmemory.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +14 -0
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +25 -1
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/storage/domains/work-items/board-version.js.map +1 -1
- package/dist/storage/domains/work-items/metrics.js.map +1 -1
- package/dist/tools/approval-tools.js +1 -1
- package/dist/tools/approval-tools.js.map +1 -1
- package/dist/tools/deliver.d.ts +8 -5
- package/dist/tools/deliver.d.ts.map +1 -1
- package/dist/tools/deliver.js +22 -8
- package/dist/tools/deliver.js.map +1 -1
- package/dist/tools/messaging-tools.d.ts +1 -1
- package/dist/tools/messaging-tools.d.ts.map +1 -1
- package/dist/tools/messaging-tools.js +203 -72
- package/dist/tools/messaging-tools.js.map +1 -1
- package/dist/tools/permissions.d.ts +1 -1
- package/dist/tools/permissions.d.ts.map +1 -1
- package/dist/tools/permissions.js +3 -2
- package/dist/tools/permissions.js.map +1 -1
- package/dist/tools/reports.d.ts.map +1 -1
- package/dist/tools/reports.js +10 -3
- package/dist/tools/reports.js.map +1 -1
- package/dist/tools/run-watch.d.ts +2 -0
- package/dist/tools/run-watch.d.ts.map +1 -1
- package/dist/tools/run-watch.js +42 -2
- package/dist/tools/run-watch.js.map +1 -1
- package/dist/tools/sandbox-tools.d.ts +12 -0
- package/dist/tools/sandbox-tools.d.ts.map +1 -0
- package/dist/tools/sandbox-tools.js +40 -0
- package/dist/tools/sandbox-tools.js.map +1 -0
- package/dist/tools/scope.d.ts +2 -1
- package/dist/tools/scope.d.ts.map +1 -1
- package/dist/tools/scope.js +5 -7
- package/dist/tools/scope.js.map +1 -1
- package/dist/tools/session-tools.d.ts +1 -1
- package/dist/tools/session-tools.d.ts.map +1 -1
- package/dist/tools/session-tools.js +39 -18
- package/dist/tools/session-tools.js.map +1 -1
- package/dist/tools/sessions-state-processor.js +2 -2
- package/dist/tools/sessions-state-processor.js.map +1 -1
- package/dist/tools/side-sessions.d.ts +1 -1
- package/dist/tools/side-sessions.d.ts.map +1 -1
- package/dist/tools/side-sessions.js +1 -1
- package/dist/tools/side-sessions.js.map +1 -1
- package/dist/work-item-branch.d.ts +11 -3
- package/dist/work-item-branch.d.ts.map +1 -1
- package/dist/work-item-branch.js +34 -9
- package/dist/work-item-branch.js.map +1 -1
- package/dist/workspace.d.ts +7 -3
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +32 -17
- package/dist/workspace.js.map +1 -1
- package/factory-skills/configure-factory-rules/SKILL.md +2 -2
- package/package.json +16 -15
- package/dist/session/channel-reply-processor.d.ts.map +0 -1
- package/dist/session/channel-reply-processor.js +0 -46
- package/dist/session/channel-reply-processor.js.map +0 -1
- package/dist/session/channel-session-link-processor.d.ts.map +0 -1
- package/dist/session/channel-session-link-processor.js.map +0 -1
- /package/dist/{session → integrations/slack}/channel-session-link-processor.d.ts +0 -0
|
@@ -53,7 +53,6 @@ export declare function createSessionAsRowOwner(controller: Pick<MountedMastraCo
|
|
|
53
53
|
ownerResourceId: string;
|
|
54
54
|
row: FactorySessionOwner;
|
|
55
55
|
tags: Record<string, string>;
|
|
56
|
-
scope?: string;
|
|
57
56
|
/** Bind this exact thread at creation instead of resuming or creating another. */
|
|
58
57
|
threadId?: string;
|
|
59
58
|
}): Promise<import("@mastra/core/harness").Session<import("@mastra/code-sdk/schema").MastraCodeState>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subscription-session.d.ts","sourceRoot":"","sources":["../../src/integrations/subscription-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAI9D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAEvF,oEAAoE;AACpE,MAAM,MAAM,mBAAmB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpE,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE;QAAE,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAA;KAAE,CAAC;CACtF;AAED;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG,uBAAuB,CAAC;IAC3D,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC,CAAC,CAAC;AAEH;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,YAAY,EAAE,sBAAsB,EACpC,aAAa,EAAE,yBAAyB,GAAG,SAAS,GACnD,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAyBrC;AAED;;;;;;;;GAQG;AACH,wBAAsB,wBAAwB,CAC5C,cAAc,EAAE,cAAc,EAC9B,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,yBAAyB,EACxC,OAAO,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GACnC,OAAO,CAAC,UAAU,GAAG,mBAAmB,GAAG,yBAAyB,GAAG,uBAAuB,CAAC,CAUjG;AAED;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,IAAI,CAAC,iBAAiB,CAAC,YAAY,CAAC,EAAE,eAAe,CAAC,EAClE,IAAI,EAAE;IACJ,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,GAAG,EAAE,mBAAmB,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,
|
|
1
|
+
{"version":3,"file":"subscription-session.d.ts","sourceRoot":"","sources":["../../src/integrations/subscription-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAI9D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAEvF,oEAAoE;AACpE,MAAM,MAAM,mBAAmB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpE,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE;QAAE,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAA;KAAE,CAAC;CACtF;AAED;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG,uBAAuB,CAAC;IAC3D,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC,CAAC,CAAC;AAEH;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,YAAY,EAAE,sBAAsB,EACpC,aAAa,EAAE,yBAAyB,GAAG,SAAS,GACnD,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAyBrC;AAED;;;;;;;;GAQG;AACH,wBAAsB,wBAAwB,CAC5C,cAAc,EAAE,cAAc,EAC9B,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,yBAAyB,EACxC,OAAO,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GACnC,OAAO,CAAC,UAAU,GAAG,mBAAmB,GAAG,yBAAyB,GAAG,uBAAuB,CAAC,CAUjG;AAED;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,IAAI,CAAC,iBAAiB,CAAC,YAAY,CAAC,EAAE,eAAe,CAAC,EAClE,IAAI,EAAE;IACJ,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,GAAG,EAAE,mBAAmB,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,sGAcF;AAED,wBAAsB,0BAA0B,CAC9C,UAAU,EAAE,iBAAiB,CAAC,YAAY,CAAC,EAC3C,YAAY,EAAE,sBAAsB,EACpC,OAAO,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,yBAAyB,CAAA;CAAE,kHAyEtE"}
|
|
@@ -69,7 +69,6 @@ async function createSessionAsRowOwner(controller, args) {
|
|
|
69
69
|
id: args.sessionId,
|
|
70
70
|
ownerId: args.row.userId,
|
|
71
71
|
resourceId: args.ownerResourceId,
|
|
72
|
-
scope: args.scope,
|
|
73
72
|
tags: args.tags,
|
|
74
73
|
...args.threadId ? { threadId: args.threadId } : {},
|
|
75
74
|
requestContext
|
|
@@ -84,8 +83,7 @@ async function resolveSubscriptionSession(controller, subscription, options) {
|
|
|
84
83
|
const thread = await controller.queryThreadById({ threadId });
|
|
85
84
|
if (!thread) return void 0;
|
|
86
85
|
const ownerResourceId = thread.resourceId || resourceId;
|
|
87
|
-
|
|
88
|
-
let session = await controller.getSessionByResource(ownerResourceId, scope);
|
|
86
|
+
let session = await controller.getSession(sessionId);
|
|
89
87
|
if (!session) {
|
|
90
88
|
const tags = {
|
|
91
89
|
factoryProjectId: resourceId,
|
|
@@ -97,7 +95,6 @@ async function resolveSubscriptionSession(controller, subscription, options) {
|
|
|
97
95
|
sessionId,
|
|
98
96
|
ownerResourceId,
|
|
99
97
|
row: sessionRow,
|
|
100
|
-
scope,
|
|
101
98
|
tags
|
|
102
99
|
});
|
|
103
100
|
} else if (!hasResolvedOrg(session.state?.get()?.factoryOrgId)) try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subscription-session.js","names":[],"sources":["../../src/integrations/subscription-session.ts"],"sourcesContent":["import type { MountedMastraCode } from '@mastra/code-sdk';\nimport { RequestContext } from '@mastra/core/request-context';\n\nimport { primeTenantCredentialsForRequestContext } from '../routes/tenant-credentials.js';\nimport { hasResolvedOrg, seedSessionOrg } from '../session/org-seed.js';\nimport type { IntegrationSubscription } from '../storage/domains/integrations/base.js';\n\n/** The Factory session row fields a woken session has to run as. */\nexport type FactorySessionOwner = { userId: string; orgId: string };\n\nexport interface SubscriptionSessionLookup {\n sessions: { getBySessionId(sessionId: string): Promise<FactorySessionOwner | null> };\n}\n\n/**\n * Bring the session that owns a subscribed thread online so a provider event\n * can be delivered to it. Provider-neutral: GitHub and GitLab subscriptions\n * carry the same session binding and differ only in their target data.\n */\nexport type SubscriptionSessionRow = IntegrationSubscription<{\n projectRepositoryId: string;\n subscribedByUserId?: string | null;\n}>;\n\n/**\n * The request context a provider-triggered run executes under: the user who\n * subscribed the thread, in the subscription's organization. A webhook carries\n * no signed-in user and tenant credential resolution fails closed without one,\n * so a woken run would otherwise stop before it could persist or answer the\n * notification. The subscription row already names that user, so delivery\n * costs no extra read; an older row without one falls back to the Factory\n * session's owner, the identity used when a session is recreated for delivery.\n */\nexport async function subscriptionRunContext(\n subscription: SubscriptionSessionRow,\n sourceControl: SubscriptionSessionLookup | undefined,\n): Promise<RequestContext | undefined> {\n let userId = subscription.data.subscribedByUserId ?? undefined;\n let orgId: string | undefined = subscription.orgId;\n if (!userId && subscription.sessionId && sourceControl) {\n const sessionRow = await sourceControl.sessions.getBySessionId(subscription.sessionId);\n userId = sessionRow?.userId;\n orgId = sessionRow?.orgId ?? orgId;\n }\n if (!userId || !hasResolvedOrg(orgId)) return undefined;\n const requestContext = new RequestContext();\n requestContext.set('user', { workosId: userId, organizationId: orgId });\n // The web auth middleware primes credential snapshots per request; a\n // webhook-triggered run has no request, so prime here or the model resolves\n // against an empty snapshot and the run fails closed before persisting. A\n // priming failure propagates: handing back an unprimed context would let the\n // caller accept the delivery, and retire a terminal subscription, for a run\n // that then fails without its credentials and cannot be redelivered.\n try {\n await primeTenantCredentialsForRequestContext(requestContext);\n } catch (error) {\n throw new Error(`Unable to prime tenant credentials for subscription ${subscription.id}; not delivered.`, {\n cause: error,\n });\n }\n return requestContext;\n}\n\n/**\n * Attach the Factory session's owner, in its organization, to the request\n * context of a run that has no inbound request (a notification or peer-signal\n * wake), and prime that tenant's credentials. The live session's `ownerId`\n * isn't used: sessions opened from the browser or Slack are owned by the\n * controller id, not a user. Leaves the context alone when `sessionId` names no\n * Factory session or its org is unresolved, so credential resolution fails\n * closed.\n */\nexport async function prepareSessionRunContext(\n requestContext: RequestContext,\n sessionId: string,\n sourceControl: SubscriptionSessionLookup,\n options?: { expectedOrgId?: string },\n): Promise<'prepared' | 'session-not-found' | 'organization-unresolved' | 'organization-mismatch'> {\n const sessionRow = await sourceControl.sessions.getBySessionId(sessionId);\n if (!sessionRow) return 'session-not-found';\n if (!hasResolvedOrg(sessionRow.orgId)) return 'organization-unresolved';\n if (options?.expectedOrgId !== undefined && sessionRow.orgId !== options.expectedOrgId) {\n return 'organization-mismatch';\n }\n requestContext.set('user', { workosId: sessionRow.userId, organizationId: sessionRow.orgId });\n await primeTenantCredentialsForRequestContext(requestContext);\n return 'prepared';\n}\n\n/**\n * Create the controller session for a Factory session that isn't live, as its\n * row's owner in the row's organization. Used when an event or message has to\n * reach a session nobody has open: there is no signed-in user, so the session\n * runs as the person it belongs to.\n */\nexport async function createSessionAsRowOwner(\n controller: Pick<MountedMastraCode['controller'], 'createSession'>,\n args: {\n sessionId: string;\n ownerResourceId: string;\n row: FactorySessionOwner;\n tags: Record<string, string>;\n scope?: string;\n /** Bind this exact thread at creation instead of resuming or creating another. */\n threadId?: string;\n },\n) {\n const requestContext = new RequestContext();\n requestContext.set('user', { workosId: args.row.userId, organizationId: args.row.orgId });\n const session = await controller.createSession({\n id: args.sessionId,\n ownerId: args.row.userId,\n resourceId: args.ownerResourceId,\n scope: args.scope,\n tags: args.tags,\n ...(args.threadId ? { threadId: args.threadId } : {}),\n requestContext,\n });\n await seedSessionOrg(session, args.row.orgId);\n return session;\n}\n\nexport async function resolveSubscriptionSession(\n controller: MountedMastraCode['controller'],\n subscription: SubscriptionSessionRow,\n options: { label: string; sourceControl?: SubscriptionSessionLookup },\n) {\n const { label } = options;\n const { sessionId, resourceId, threadId } = subscription;\n if (!sessionId || !resourceId || !threadId) {\n throw new Error(`${label} subscription ${subscription.id} is missing its session binding.`);\n }\n // Read the thread straight from storage before touching sessions. This answers\n // two questions at once, and `queryThreadById` does it without constructing a\n // session (so no workspace or sandbox is provisioned just to make the check).\n //\n // First: do we even have this thread? A change request's events can reach a\n // deployment that never owned the subscribed thread, and delivery must not\n // fabricate a session for a thread that lives somewhere else.\n //\n // Second: which resource owns it? The subscription records the Factory project\n // as its `resourceId`, but an unscoped session is registered under its own id,\n // so the stored value routinely names a resource that does not own the thread.\n // The thread row is the authoritative answer; the stored id is only a fallback.\n const thread = await controller.queryThreadById({ threadId });\n if (!thread) return undefined;\n const ownerResourceId = thread.resourceId || resourceId;\n const scope = subscription.sessionScope || undefined;\n let session = await controller.getSessionByResource(ownerResourceId, scope);\n if (!session) {\n const tags = {\n factoryProjectId: resourceId,\n projectRepositoryId: subscription.data.projectRepositoryId,\n };\n // Creating the session resolves its workspace, which authorizes the caller\n // against the Factory session row — no signed-in user, so run as its owner.\n // The session is created under the resource that owns the thread, so the\n // thread switch below resolves; the persisted Factory session is keyed by\n // the subscription's session ID.\n const sessionRow = await options.sourceControl?.sessions.getBySessionId(sessionId);\n if (!sessionRow) {\n throw new Error(`${label} subscription ${subscription.id} has no Factory session ${sessionId} to run as.`);\n }\n session = await createSessionAsRowOwner(controller, {\n sessionId,\n ownerResourceId,\n row: sessionRow,\n scope,\n tags,\n });\n } else if (!hasResolvedOrg(session.state?.get()?.factoryOrgId)) {\n // A session created before the org seed existed carries the project tag and\n // no org, so capture would refuse for the rest of its life even though the\n // org is recoverable. Heal it — but only here, and only when it is missing:\n // hoisting the row fetch above would make an existing-session delivery throw\n // on a missing row where it previously succeeded, and fetching it every time\n // would add a storage read to every delivery. A row that is missing or a\n // lookup that throws leaves the session marked unresolved, and delivery\n // continues either way.\n try {\n const sessionRow = await options.sourceControl?.sessions.getBySessionId(sessionId);\n await seedSessionOrg(session, sessionRow?.orgId);\n } catch (error) {\n console.warn(`[${label} webhook] Unable to resolve the session organization.`, error);\n await seedSessionOrg(session, undefined);\n }\n } else if (session.state?.get()?.factoryOrgUnresolved) {\n // The org is present, so an earlier failed resolution left a stale marker\n // behind. Clear it without a storage read — nothing else re-seeds a session\n // once the start hook has run, so the marker would otherwise outlive its\n // cause.\n await seedSessionOrg(session, session.state.get()?.factoryOrgId);\n }\n if (session.thread.getId() !== threadId) {\n await session.thread.switch({ threadId, emitEvent: false });\n }\n if (session.thread.getId() !== threadId) {\n throw new Error(`Session ${sessionId} did not bind thread ${threadId}.`);\n }\n return session;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"subscription-session.js","names":[],"sources":["../../src/integrations/subscription-session.ts"],"sourcesContent":["import type { MountedMastraCode } from '@mastra/code-sdk';\nimport { RequestContext } from '@mastra/core/request-context';\n\nimport { primeTenantCredentialsForRequestContext } from '../routes/tenant-credentials.js';\nimport { hasResolvedOrg, seedSessionOrg } from '../session/org-seed.js';\nimport type { IntegrationSubscription } from '../storage/domains/integrations/base.js';\n\n/** The Factory session row fields a woken session has to run as. */\nexport type FactorySessionOwner = { userId: string; orgId: string };\n\nexport interface SubscriptionSessionLookup {\n sessions: { getBySessionId(sessionId: string): Promise<FactorySessionOwner | null> };\n}\n\n/**\n * Bring the session that owns a subscribed thread online so a provider event\n * can be delivered to it. Provider-neutral: GitHub and GitLab subscriptions\n * carry the same session binding and differ only in their target data.\n */\nexport type SubscriptionSessionRow = IntegrationSubscription<{\n projectRepositoryId: string;\n subscribedByUserId?: string | null;\n}>;\n\n/**\n * The request context a provider-triggered run executes under: the user who\n * subscribed the thread, in the subscription's organization. A webhook carries\n * no signed-in user and tenant credential resolution fails closed without one,\n * so a woken run would otherwise stop before it could persist or answer the\n * notification. The subscription row already names that user, so delivery\n * costs no extra read; an older row without one falls back to the Factory\n * session's owner, the identity used when a session is recreated for delivery.\n */\nexport async function subscriptionRunContext(\n subscription: SubscriptionSessionRow,\n sourceControl: SubscriptionSessionLookup | undefined,\n): Promise<RequestContext | undefined> {\n let userId = subscription.data.subscribedByUserId ?? undefined;\n let orgId: string | undefined = subscription.orgId;\n if (!userId && subscription.sessionId && sourceControl) {\n const sessionRow = await sourceControl.sessions.getBySessionId(subscription.sessionId);\n userId = sessionRow?.userId;\n orgId = sessionRow?.orgId ?? orgId;\n }\n if (!userId || !hasResolvedOrg(orgId)) return undefined;\n const requestContext = new RequestContext();\n requestContext.set('user', { workosId: userId, organizationId: orgId });\n // The web auth middleware primes credential snapshots per request; a\n // webhook-triggered run has no request, so prime here or the model resolves\n // against an empty snapshot and the run fails closed before persisting. A\n // priming failure propagates: handing back an unprimed context would let the\n // caller accept the delivery, and retire a terminal subscription, for a run\n // that then fails without its credentials and cannot be redelivered.\n try {\n await primeTenantCredentialsForRequestContext(requestContext);\n } catch (error) {\n throw new Error(`Unable to prime tenant credentials for subscription ${subscription.id}; not delivered.`, {\n cause: error,\n });\n }\n return requestContext;\n}\n\n/**\n * Attach the Factory session's owner, in its organization, to the request\n * context of a run that has no inbound request (a notification or peer-signal\n * wake), and prime that tenant's credentials. The live session's `ownerId`\n * isn't used: sessions opened from the browser or Slack are owned by the\n * controller id, not a user. Leaves the context alone when `sessionId` names no\n * Factory session or its org is unresolved, so credential resolution fails\n * closed.\n */\nexport async function prepareSessionRunContext(\n requestContext: RequestContext,\n sessionId: string,\n sourceControl: SubscriptionSessionLookup,\n options?: { expectedOrgId?: string },\n): Promise<'prepared' | 'session-not-found' | 'organization-unresolved' | 'organization-mismatch'> {\n const sessionRow = await sourceControl.sessions.getBySessionId(sessionId);\n if (!sessionRow) return 'session-not-found';\n if (!hasResolvedOrg(sessionRow.orgId)) return 'organization-unresolved';\n if (options?.expectedOrgId !== undefined && sessionRow.orgId !== options.expectedOrgId) {\n return 'organization-mismatch';\n }\n requestContext.set('user', { workosId: sessionRow.userId, organizationId: sessionRow.orgId });\n await primeTenantCredentialsForRequestContext(requestContext);\n return 'prepared';\n}\n\n/**\n * Create the controller session for a Factory session that isn't live, as its\n * row's owner in the row's organization. Used when an event or message has to\n * reach a session nobody has open: there is no signed-in user, so the session\n * runs as the person it belongs to.\n */\nexport async function createSessionAsRowOwner(\n controller: Pick<MountedMastraCode['controller'], 'createSession'>,\n args: {\n sessionId: string;\n ownerResourceId: string;\n row: FactorySessionOwner;\n tags: Record<string, string>;\n /** Bind this exact thread at creation instead of resuming or creating another. */\n threadId?: string;\n },\n) {\n const requestContext = new RequestContext();\n requestContext.set('user', { workosId: args.row.userId, organizationId: args.row.orgId });\n const session = await controller.createSession({\n id: args.sessionId,\n ownerId: args.row.userId,\n resourceId: args.ownerResourceId,\n tags: args.tags,\n ...(args.threadId ? { threadId: args.threadId } : {}),\n requestContext,\n });\n await seedSessionOrg(session, args.row.orgId);\n return session;\n}\n\nexport async function resolveSubscriptionSession(\n controller: MountedMastraCode['controller'],\n subscription: SubscriptionSessionRow,\n options: { label: string; sourceControl?: SubscriptionSessionLookup },\n) {\n const { label } = options;\n const { sessionId, resourceId, threadId } = subscription;\n if (!sessionId || !resourceId || !threadId) {\n throw new Error(`${label} subscription ${subscription.id} is missing its session binding.`);\n }\n // Read the thread straight from storage before touching sessions. This answers\n // two questions at once, and `queryThreadById` does it without constructing a\n // session (so no workspace or sandbox is provisioned just to make the check).\n //\n // First: do we even have this thread? A change request's events can reach a\n // deployment that never owned the subscribed thread, and delivery must not\n // fabricate a session for a thread that lives somewhere else.\n //\n // Second: which resource owns it? The subscription records the Factory project\n // as its `resourceId`, but the session may run under a different resource\n // (its own id, for sessions created before Factory resources were shared).\n // The thread row is the authoritative answer; the stored id is only a fallback.\n const thread = await controller.queryThreadById({ threadId });\n if (!thread) return undefined;\n const ownerResourceId = thread.resourceId || resourceId;\n let session = await controller.getSession(sessionId);\n if (!session) {\n const tags = {\n factoryProjectId: resourceId,\n projectRepositoryId: subscription.data.projectRepositoryId,\n };\n // Creating the session resolves its workspace, which authorizes the caller\n // against the Factory session row — no signed-in user, so run as its owner.\n // The session is created under the resource that owns the thread, so the\n // thread switch below resolves; the persisted Factory session is keyed by\n // the subscription's session ID.\n const sessionRow = await options.sourceControl?.sessions.getBySessionId(sessionId);\n if (!sessionRow) {\n throw new Error(`${label} subscription ${subscription.id} has no Factory session ${sessionId} to run as.`);\n }\n session = await createSessionAsRowOwner(controller, {\n sessionId,\n ownerResourceId,\n row: sessionRow,\n tags,\n });\n } else if (!hasResolvedOrg(session.state?.get()?.factoryOrgId)) {\n // A session created before the org seed existed carries the project tag and\n // no org, so capture would refuse for the rest of its life even though the\n // org is recoverable. Heal it — but only here, and only when it is missing:\n // hoisting the row fetch above would make an existing-session delivery throw\n // on a missing row where it previously succeeded, and fetching it every time\n // would add a storage read to every delivery. A row that is missing or a\n // lookup that throws leaves the session marked unresolved, and delivery\n // continues either way.\n try {\n const sessionRow = await options.sourceControl?.sessions.getBySessionId(sessionId);\n await seedSessionOrg(session, sessionRow?.orgId);\n } catch (error) {\n console.warn(`[${label} webhook] Unable to resolve the session organization.`, error);\n await seedSessionOrg(session, undefined);\n }\n } else if (session.state?.get()?.factoryOrgUnresolved) {\n // The org is present, so an earlier failed resolution left a stale marker\n // behind. Clear it without a storage read — nothing else re-seeds a session\n // once the start hook has run, so the marker would otherwise outlive its\n // cause.\n await seedSessionOrg(session, session.state.get()?.factoryOrgId);\n }\n if (session.thread.getId() !== threadId) {\n await session.thread.switch({ threadId, emitEvent: false });\n }\n if (session.thread.getId() !== threadId) {\n throw new Error(`Session ${sessionId} did not bind thread ${threadId}.`);\n }\n return session;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration.js","names":[
|
|
1
|
+
{"version":3,"file":"integration.js","names":[],"sources":["../../../src/integrations/workos/integration.ts"],"sourcesContent":["import { WorkOSAdminPortal } from '@mastra/auth-workos';\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport type { AuditEventRow } from '../../storage/domains/audit/base.js';\nimport type { FactoryIntegration, IntegrationContext } from '../base.js';\n\ntype WorkOSClient = ConstructorParameters<typeof WorkOSAdminPortal>[0];\n\nconst UNKNOWN_LOCATION = 'unknown';\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\nfunction flattenMetadata(metadata: Record<string, unknown>): Record<string, string | number | boolean> {\n const flat: Record<string, string | number | boolean> = {};\n for (const [key, value] of Object.entries(metadata)) {\n if (value === undefined || value === null) continue;\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n flat[key] = value;\n continue;\n }\n try {\n flat[key] = JSON.stringify(value);\n } catch {\n // Drop unserializable metadata rather than the event.\n }\n }\n return flat;\n}\n\nexport function toWorkOSEvent(event: AuditEventRow): {\n action: string;\n occurredAt: Date;\n actor: { type: string; id: string };\n targets: Array<{ type: string; id: string; name?: string }>;\n context: { location: string; userAgent?: string };\n metadata: Record<string, string | number | boolean>;\n} {\n return {\n action: event.action,\n occurredAt: event.occurredAt,\n actor: { type: event.actorType === 'human' ? 'user' : event.actorType, id: event.actorId },\n targets: event.targets.map(target => ({\n type: target.type,\n id: target.id,\n ...(target.name !== undefined ? { name: target.name } : {}),\n })),\n context: {\n location: event.context.location ?? UNKNOWN_LOCATION,\n ...(event.context.userAgent !== undefined ? { userAgent: event.context.userAgent } : {}),\n },\n metadata: flattenMetadata(event.metadata),\n };\n}\n\n/** Optional WorkOS mirror and Admin Portal route, independent of the auth adapter. */\nexport class WorkOSAuditIntegration implements FactoryIntegration {\n readonly id = 'workos';\n readonly #client: WorkOSClient;\n readonly #returnUrl: string;\n\n constructor({ client, returnUrl }: { client: WorkOSClient; returnUrl: string }) {\n this.#client = client;\n this.#returnUrl = returnUrl;\n }\n\n async audit({ event }: { event: AuditEventRow }): Promise<void> {\n try {\n await this.#client.auditLogs.createEvent(event.orgId, toWorkOSEvent(event));\n } catch (err) {\n console.warn('[Audit] Failed to forward audit event to WorkOS', {\n action: event.action,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n routes(ctx: IntegrationContext): ApiRoute[] {\n const { auth } = ctx;\n return [\n registerApiRoute('/web/audit/portal-link', {\n method: 'GET',\n handler: async cc => {\n const c = loose(cc);\n await auth.ensureUser(c);\n const tenant = auth.tenant(c);\n if (!tenant) return c.json({ error: 'unauthorized' }, 401);\n if (!tenant.orgId) {\n return c.json(\n { error: 'organization_required', message: 'The audit trail requires a WorkOS organization.' },\n 403,\n );\n }\n\n try {\n const portal = new WorkOSAdminPortal(this.#client, { returnUrl: this.#returnUrl });\n const url = await portal.getPortalLink(tenant.orgId, 'audit_logs');\n return c.json({ url });\n } catch (err) {\n console.warn('[Audit] Failed to generate WorkOS Admin Portal link', {\n error: err instanceof Error ? err.message : String(err),\n });\n return c.json({ error: 'portal_link_failed' }, 502);\n }\n },\n }),\n ];\n }\n\n diagnostics(): Record<string, unknown> {\n return { configured: true };\n }\n}\n"],"mappings":";;;AAUA,MAAM,mBAAmB;AAEzB,SAAS,MAAM,GAAqB;CAClC,OAAO;AACT;AAEA,SAAS,gBAAgB,UAA8E;CACrG,MAAM,OAAkD,CAAC;CACzD,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,GAAG;EACnD,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM;EAC3C,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,YAAY,OAAO,UAAU,WAAW;GACxF,KAAK,OAAO;GACZ;EACF;EACA,IAAI;GACF,KAAK,OAAO,KAAK,UAAU,KAAK;EAClC,QAAQ,CAER;CACF;CACA,OAAO;AACT;AAEA,SAAgB,cAAc,OAO5B;CACA,OAAO;EACL,QAAQ,MAAM;EACd,YAAY,MAAM;EAClB,OAAO;GAAE,MAAM,MAAM,cAAc,UAAU,SAAS,MAAM;GAAW,IAAI,MAAM;EAAQ;EACzF,SAAS,MAAM,QAAQ,KAAI,YAAW;GACpC,MAAM,OAAO;GACb,IAAI,OAAO;GACX,GAAI,OAAO,SAAS,KAAA,IAAY,EAAE,MAAM,OAAO,KAAK,IAAI,CAAC;EAC3D,EAAE;EACF,SAAS;GACP,UAAU,MAAM,QAAQ,YAAY;GACpC,GAAI,MAAM,QAAQ,cAAc,KAAA,IAAY,EAAE,WAAW,MAAM,QAAQ,UAAU,IAAI,CAAC;EACxF;EACA,UAAU,gBAAgB,MAAM,QAAQ;CAC1C;AACF;;AAGA,IAAa,yBAAb,MAAkE;CAChE,KAAc;CACd;CACA;CAEA,YAAY,EAAE,QAAQ,aAA0D;EAC9E,KAAK,UAAU;EACf,KAAK,aAAa;CACpB;CAEA,MAAM,MAAM,EAAE,SAAkD;EAC9D,IAAI;GACF,MAAM,KAAK,QAAQ,UAAU,YAAY,MAAM,OAAO,cAAc,KAAK,CAAC;EAC5E,SAAS,KAAK;GACZ,QAAQ,KAAK,mDAAmD;IAC9D,QAAQ,MAAM;IACd,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;GACxD,CAAC;EACH;CACF;CAEA,OAAO,KAAqC;EAC1C,MAAM,EAAE,SAAS;EACjB,OAAO,CACL,iBAAiB,0BAA0B;GACzC,QAAQ;GACR,SAAS,OAAM,OAAM;IACnB,MAAM,IAAI,MAAM,EAAE;IAClB,MAAM,KAAK,WAAW,CAAC;IACvB,MAAM,SAAS,KAAK,OAAO,CAAC;IAC5B,IAAI,CAAC,QAAQ,OAAO,EAAE,KAAK,EAAE,OAAO,eAAe,GAAG,GAAG;IACzD,IAAI,CAAC,OAAO,OACV,OAAO,EAAE,KACP;KAAE,OAAO;KAAyB,SAAS;IAAkD,GAC7F,GACF;IAGF,IAAI;KAEF,MAAM,MAAM,MAAM,IADC,kBAAkB,KAAK,SAAS,EAAE,WAAW,KAAK,WAAW,CACzD,CAAC,CAAC,cAAc,OAAO,OAAO,YAAY;KACjE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC;IACvB,SAAS,KAAK;KACZ,QAAQ,KAAK,uDAAuD,EAClE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EACxD,CAAC;KACD,OAAO,EAAE,KAAK,EAAE,OAAO,qBAAqB,GAAG,GAAG;IACpD;GACF;EACF,CAAC,CACH;CACF;CAEA,cAAuC;EACrC,OAAO,EAAE,YAAY,KAAK;CAC5B;AACF"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { SourceControlSessionLookup } from '../session/factory-session.js';
|
|
3
|
+
import type { AuditStorage } from '../storage/domains/audit/base.js';
|
|
4
|
+
import type { WorkItemsStorage } from '../storage/domains/work-items/base.js';
|
|
5
|
+
declare const activityPayload: z.ZodObject<{
|
|
6
|
+
action: z.ZodString;
|
|
7
|
+
occurredAt: z.ZodString;
|
|
8
|
+
actor: z.ZodObject<{
|
|
9
|
+
type: z.ZodEnum<{
|
|
10
|
+
human: "human";
|
|
11
|
+
agent: "agent";
|
|
12
|
+
system: "system";
|
|
13
|
+
}>;
|
|
14
|
+
id: z.ZodString;
|
|
15
|
+
name: z.ZodOptional<z.ZodString>;
|
|
16
|
+
}, z.core.$strip>;
|
|
17
|
+
workItemId: z.ZodOptional<z.ZodString>;
|
|
18
|
+
sessionId: z.ZodOptional<z.ZodString>;
|
|
19
|
+
privateSession: z.ZodOptional<z.ZodLiteral<true>>;
|
|
20
|
+
summary: z.ZodString;
|
|
21
|
+
}, z.core.$strip>;
|
|
22
|
+
export type FactoryActivity = z.infer<typeof activityPayload>;
|
|
23
|
+
export interface FactoryActivityEventDeps {
|
|
24
|
+
orgId: string;
|
|
25
|
+
factoryProjectId: string;
|
|
26
|
+
audit: Pick<AuditStorage, 'list'>;
|
|
27
|
+
workItems: Pick<WorkItemsStorage, 'getForProject'>;
|
|
28
|
+
sessions: Pick<SourceControlSessionLookup, 'getBySessionId'>;
|
|
29
|
+
/** Who is polling: sessions they can't view keep their activity out. */
|
|
30
|
+
viewer: {
|
|
31
|
+
orgId: string;
|
|
32
|
+
userId: string;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* How long polling waits before moving past a row. `occurred_at` is set when the
|
|
37
|
+
* row is written, so a row that commits after a poll is still younger than this
|
|
38
|
+
* and lies ahead of the cursor, even if it shares a millisecond with the cursor row.
|
|
39
|
+
*/
|
|
40
|
+
export declare const ACTIVITY_SETTLE_MS = 5000;
|
|
41
|
+
/**
|
|
42
|
+
* `factory.activity`: the Factory's audit trail as polled MCP events (SEP-3415, experimental).
|
|
43
|
+
*
|
|
44
|
+
* Serves activity on work items and sessions. Sessions follow the same rule as the
|
|
45
|
+
* session routes (`canViewSourceControlSession`). Work item activity is always served;
|
|
46
|
+
* when it happened in a session the caller can't see (another member's private
|
|
47
|
+
* session, or one that can't be found) it is marked `privateSession` and names no
|
|
48
|
+
* session. Activity on such a session alone is not served. An agent's activity
|
|
49
|
+
* counts as its session's when the agent's thread is a session. Events naming neither (intake settings, rule bindings'
|
|
50
|
+
* own actions) are not served.
|
|
51
|
+
*
|
|
52
|
+
* Poll-only by design: the MCP server is built per request, so there is nothing
|
|
53
|
+
* to push from, and every poll reads storage from the client's cursor. Events are
|
|
54
|
+
* delivered once they are {@link ACTIVITY_SETTLE_MS} old.
|
|
55
|
+
*/
|
|
56
|
+
export declare function factoryActivityEvent(deps: FactoryActivityEventDeps): import("@mastra/mcp").MCPServerEventDefinition<{
|
|
57
|
+
workItemId?: string | undefined;
|
|
58
|
+
actions?: string[] | undefined;
|
|
59
|
+
}, {
|
|
60
|
+
action: string;
|
|
61
|
+
occurredAt: string;
|
|
62
|
+
actor: {
|
|
63
|
+
type: "human" | "agent" | "system";
|
|
64
|
+
id: string;
|
|
65
|
+
name?: string | undefined;
|
|
66
|
+
};
|
|
67
|
+
summary: string;
|
|
68
|
+
workItemId?: string | undefined;
|
|
69
|
+
sessionId?: string | undefined;
|
|
70
|
+
privateSession?: true | undefined;
|
|
71
|
+
}>;
|
|
72
|
+
export {};
|
|
73
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/mcp/events.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAGhF,OAAO,KAAK,EAAE,YAAY,EAAiB,MAAM,kCAAkC,CAAC;AAQpF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAa9E,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;;iBAanB,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAClC,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;IACnD,QAAQ,EAAE,IAAI,CAAC,0BAA0B,EAAE,gBAAgB,CAAC,CAAC;IAC7D,wEAAwE;IACxE,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3C;AAmCD;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,OAAQ,CAAC;AAExC;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,wBAAwB;;;;;;;;;;;;;;;GAuFlE"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { canViewSourceControlSession } from "../routes/session-access.js";
|
|
2
|
+
import { ACTOR_PROFILE_METADATA_KEY, auditCursorAt, decodeAuditAfterCursor, encodeAuditCursor } from "../storage/domains/audit/base.js";
|
|
3
|
+
import { parseAuditAction } from "../storage/domains/audit/actions.js";
|
|
4
|
+
import { AUDIT_ACTOR_TYPES } from "../storage/domains/audit/actors.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { defineEvent } from "@mastra/mcp";
|
|
7
|
+
//#region src/mcp/events.ts
|
|
8
|
+
const activityArgs = z.object({
|
|
9
|
+
workItemId: z.string().min(1).optional().describe("Only activity on this work item."),
|
|
10
|
+
actions: z.array(z.string().min(1)).max(50).optional().describe("Only these actions, e.g. `factory.work_item.stage_moved`.")
|
|
11
|
+
}).strict();
|
|
12
|
+
const activityPayload = z.object({
|
|
13
|
+
action: z.string(),
|
|
14
|
+
occurredAt: z.string(),
|
|
15
|
+
actor: z.object({
|
|
16
|
+
type: z.enum(AUDIT_ACTOR_TYPES),
|
|
17
|
+
id: z.string(),
|
|
18
|
+
name: z.string().optional()
|
|
19
|
+
}),
|
|
20
|
+
workItemId: z.string().optional(),
|
|
21
|
+
sessionId: z.string().optional(),
|
|
22
|
+
/** The work item event happened in a session the caller can't see; which one stays hidden. */
|
|
23
|
+
privateSession: z.literal(true).optional(),
|
|
24
|
+
summary: z.string()
|
|
25
|
+
});
|
|
26
|
+
function toActivity(event, { workItem, session, privateSession, anonymousAgent }) {
|
|
27
|
+
const profile = event.metadata[ACTOR_PROFILE_METADATA_KEY];
|
|
28
|
+
const parsed = parseAuditAction(event.action);
|
|
29
|
+
const verb = parsed ? `${parsed.namespace === "work_item" ? "" : `${parsed.namespace} `}${parsed.leaf}`.replaceAll("_", " ") : event.action;
|
|
30
|
+
return {
|
|
31
|
+
action: event.action,
|
|
32
|
+
occurredAt: event.occurredAt.toISOString(),
|
|
33
|
+
actor: {
|
|
34
|
+
type: event.actorType,
|
|
35
|
+
id: (privateSession || anonymousAgent) && event.actorType === "agent" ? "agent" : event.actorId,
|
|
36
|
+
...typeof profile?.name === "string" ? { name: profile.name } : {}
|
|
37
|
+
},
|
|
38
|
+
...workItem ? { workItemId: workItem.id } : {},
|
|
39
|
+
...session ? { sessionId: session.sessionId } : {},
|
|
40
|
+
...privateSession ? { privateSession } : {},
|
|
41
|
+
summary: `${verb}: ${workItem ? workItem.name ?? workItem.id : session?.title ?? session?.branch ?? session?.sessionId}${privateSession ? " (in a private session)" : ""}`
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* How long polling waits before moving past a row. `occurred_at` is set when the
|
|
46
|
+
* row is written, so a row that commits after a poll is still younger than this
|
|
47
|
+
* and lies ahead of the cursor, even if it shares a millisecond with the cursor row.
|
|
48
|
+
*/
|
|
49
|
+
const ACTIVITY_SETTLE_MS = 5e3;
|
|
50
|
+
/**
|
|
51
|
+
* `factory.activity`: the Factory's audit trail as polled MCP events (SEP-3415, experimental).
|
|
52
|
+
*
|
|
53
|
+
* Serves activity on work items and sessions. Sessions follow the same rule as the
|
|
54
|
+
* session routes (`canViewSourceControlSession`). Work item activity is always served;
|
|
55
|
+
* when it happened in a session the caller can't see (another member's private
|
|
56
|
+
* session, or one that can't be found) it is marked `privateSession` and names no
|
|
57
|
+
* session. Activity on such a session alone is not served. An agent's activity
|
|
58
|
+
* counts as its session's when the agent's thread is a session. Events naming neither (intake settings, rule bindings'
|
|
59
|
+
* own actions) are not served.
|
|
60
|
+
*
|
|
61
|
+
* Poll-only by design: the MCP server is built per request, so there is nothing
|
|
62
|
+
* to push from, and every poll reads storage from the client's cursor. Events are
|
|
63
|
+
* delivered once they are {@link ACTIVITY_SETTLE_MS} old.
|
|
64
|
+
*/
|
|
65
|
+
function factoryActivityEvent(deps) {
|
|
66
|
+
const project = {
|
|
67
|
+
orgId: deps.orgId,
|
|
68
|
+
factoryProjectId: deps.factoryProjectId
|
|
69
|
+
};
|
|
70
|
+
return defineEvent({
|
|
71
|
+
description: "Activity on this Factory’s work items and the sessions you can see: items created, updated, moved and commented on, runs, approvals, signals and agent commits, pushes and pull requests.",
|
|
72
|
+
inputSchema: activityArgs,
|
|
73
|
+
payloadSchema: activityPayload,
|
|
74
|
+
authorize: async ({ arguments: args }) => !args.workItemId || await deps.workItems.getForProject(deps.orgId, deps.factoryProjectId, args.workItemId) !== null,
|
|
75
|
+
poll: async ({ arguments: args, cursor, maxEvents, maxAgeMs }) => {
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
if (cursor == null || !decodeAuditAfterCursor(cursor)) return {
|
|
78
|
+
events: [],
|
|
79
|
+
cursor: auditCursorAt(new Date(now)),
|
|
80
|
+
...cursor != null ? { truncated: true } : {}
|
|
81
|
+
};
|
|
82
|
+
const page = await deps.audit.list({
|
|
83
|
+
...project,
|
|
84
|
+
...args.actions?.length ? { actions: args.actions } : {},
|
|
85
|
+
after: cursor,
|
|
86
|
+
limit: maxEvents
|
|
87
|
+
});
|
|
88
|
+
const sessionCache = /* @__PURE__ */ new Map();
|
|
89
|
+
const lookup = (sessionId) => {
|
|
90
|
+
let found = sessionCache.get(sessionId);
|
|
91
|
+
if (!found) sessionCache.set(sessionId, found = deps.sessions.getBySessionId(sessionId));
|
|
92
|
+
return found;
|
|
93
|
+
};
|
|
94
|
+
const visible = (session) => session !== null && canViewSourceControlSession(session, deps.viewer);
|
|
95
|
+
/** What the event is about, or `undefined` when the caller may not see it. */
|
|
96
|
+
const subjectOf = async (event) => {
|
|
97
|
+
const workItem = event.targets.find((target) => target.type === "work_item");
|
|
98
|
+
const named = event.targets.filter((target) => target.type === "session").map((target) => target.id);
|
|
99
|
+
const sessions = await Promise.all(named.map(lookup));
|
|
100
|
+
let hidden = !sessions.every(visible);
|
|
101
|
+
let session = sessions[0];
|
|
102
|
+
let anonymousAgent = false;
|
|
103
|
+
if (event.actorType === "agent" && event.actorId.startsWith("agent:")) {
|
|
104
|
+
const actorSession = await lookup(event.actorId.slice(6));
|
|
105
|
+
if (!actorSession) anonymousAgent = true;
|
|
106
|
+
else if (!visible(actorSession)) hidden = true;
|
|
107
|
+
session ??= actorSession ?? void 0;
|
|
108
|
+
}
|
|
109
|
+
if (hidden) return workItem ? {
|
|
110
|
+
workItem,
|
|
111
|
+
privateSession: true
|
|
112
|
+
} : void 0;
|
|
113
|
+
if (!workItem && !session) return void 0;
|
|
114
|
+
return {
|
|
115
|
+
...workItem ? { workItem } : {},
|
|
116
|
+
...session ? { session } : {},
|
|
117
|
+
...anonymousAgent ? { anonymousAgent: true } : {}
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
const settledUntil = now - ACTIVITY_SETTLE_MS;
|
|
121
|
+
const expiredBefore = maxAgeMs === void 0 ? -Infinity : now - maxAgeMs;
|
|
122
|
+
const events = [];
|
|
123
|
+
let next = cursor;
|
|
124
|
+
let unsettled = false;
|
|
125
|
+
let expired = false;
|
|
126
|
+
for (const event of page.events) {
|
|
127
|
+
const at = event.occurredAt.getTime();
|
|
128
|
+
if (at > settledUntil) {
|
|
129
|
+
unsettled = true;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
next = encodeAuditCursor(event);
|
|
133
|
+
if (at < expiredBefore) {
|
|
134
|
+
expired = true;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (args.workItemId && !event.targets.some((t) => t.type === "work_item" && t.id === args.workItemId)) continue;
|
|
138
|
+
const subject = await subjectOf(event);
|
|
139
|
+
if (!subject) continue;
|
|
140
|
+
events.push({
|
|
141
|
+
eventId: event.id,
|
|
142
|
+
timestamp: event.occurredAt,
|
|
143
|
+
data: toActivity(event, subject)
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
events,
|
|
148
|
+
cursor: next,
|
|
149
|
+
...page.nextCursor && !unsettled ? { hasMore: true } : {},
|
|
150
|
+
...expired ? { truncated: true } : {}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
//#endregion
|
|
156
|
+
export { ACTIVITY_SETTLE_MS, factoryActivityEvent };
|
|
157
|
+
|
|
158
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","names":[],"sources":["../../src/mcp/events.ts"],"sourcesContent":["import { defineEvent } from '@mastra/mcp';\nimport { z } from 'zod';\n\nimport { canViewSourceControlSession } from '../routes/session-access.js';\nimport type { SourceControlSessionLookup } from '../session/factory-session.js';\nimport { parseAuditAction } from '../storage/domains/audit/actions.js';\nimport { AUDIT_ACTOR_TYPES } from '../storage/domains/audit/actors.js';\nimport type { AuditStorage, AuditEventRow } from '../storage/domains/audit/base.js';\nimport {\n ACTOR_PROFILE_METADATA_KEY,\n auditCursorAt,\n decodeAuditAfterCursor,\n encodeAuditCursor,\n} from '../storage/domains/audit/base.js';\nimport type { SourceControlSession } from '../storage/domains/source-control/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\n\nconst activityArgs = z\n .object({\n workItemId: z.string().min(1).optional().describe('Only activity on this work item.'),\n actions: z\n .array(z.string().min(1))\n .max(50)\n .optional()\n .describe('Only these actions, e.g. `factory.work_item.stage_moved`.'),\n })\n .strict();\n\nconst activityPayload = z.object({\n action: z.string(),\n occurredAt: z.string(),\n actor: z.object({\n type: z.enum(AUDIT_ACTOR_TYPES),\n id: z.string(),\n name: z.string().optional(),\n }),\n workItemId: z.string().optional(),\n sessionId: z.string().optional(),\n /** The work item event happened in a session the caller can't see; which one stays hidden. */\n privateSession: z.literal(true).optional(),\n summary: z.string(),\n});\n\nexport type FactoryActivity = z.infer<typeof activityPayload>;\n\nexport interface FactoryActivityEventDeps {\n orgId: string;\n factoryProjectId: string;\n audit: Pick<AuditStorage, 'list'>;\n workItems: Pick<WorkItemsStorage, 'getForProject'>;\n sessions: Pick<SourceControlSessionLookup, 'getBySessionId'>;\n /** Who is polling: sessions they can't view keep their activity out. */\n viewer: { orgId: string; userId: string };\n}\n\ntype Subject = {\n workItem?: { id: string; name?: string };\n session?: SourceControlSession;\n privateSession?: true;\n /** The agent's thread couldn't be checked, so its id isn't shown. */\n anonymousAgent?: true;\n};\n\nfunction toActivity(\n event: AuditEventRow,\n { workItem, session, privateSession, anonymousAgent }: Subject,\n): FactoryActivity {\n const profile = event.metadata[ACTOR_PROFILE_METADATA_KEY] as { name?: unknown } | undefined;\n const parsed = parseAuditAction(event.action);\n const verb = parsed\n ? `${parsed.namespace === 'work_item' ? '' : `${parsed.namespace} `}${parsed.leaf}`.replaceAll('_', ' ')\n : event.action;\n return {\n action: event.action,\n occurredAt: event.occurredAt.toISOString(),\n actor: {\n type: event.actorType,\n // An agent's id is its thread, which is the hidden session's id.\n id: (privateSession || anonymousAgent) && event.actorType === 'agent' ? 'agent' : event.actorId,\n ...(typeof profile?.name === 'string' ? { name: profile.name } : {}),\n },\n ...(workItem ? { workItemId: workItem.id } : {}),\n ...(session ? { sessionId: session.sessionId } : {}),\n ...(privateSession ? { privateSession } : {}),\n summary: `${verb}: ${workItem ? (workItem.name ?? workItem.id) : (session?.title ?? session?.branch ?? session?.sessionId)}${privateSession ? ' (in a private session)' : ''}`,\n };\n}\n\n/**\n * How long polling waits before moving past a row. `occurred_at` is set when the\n * row is written, so a row that commits after a poll is still younger than this\n * and lies ahead of the cursor, even if it shares a millisecond with the cursor row.\n */\nexport const ACTIVITY_SETTLE_MS = 5_000;\n\n/**\n * `factory.activity`: the Factory's audit trail as polled MCP events (SEP-3415, experimental).\n *\n * Serves activity on work items and sessions. Sessions follow the same rule as the\n * session routes (`canViewSourceControlSession`). Work item activity is always served;\n * when it happened in a session the caller can't see (another member's private\n * session, or one that can't be found) it is marked `privateSession` and names no\n * session. Activity on such a session alone is not served. An agent's activity\n * counts as its session's when the agent's thread is a session. Events naming neither (intake settings, rule bindings'\n * own actions) are not served.\n *\n * Poll-only by design: the MCP server is built per request, so there is nothing\n * to push from, and every poll reads storage from the client's cursor. Events are\n * delivered once they are {@link ACTIVITY_SETTLE_MS} old.\n */\nexport function factoryActivityEvent(deps: FactoryActivityEventDeps) {\n const project = { orgId: deps.orgId, factoryProjectId: deps.factoryProjectId };\n return defineEvent({\n description:\n 'Activity on this Factory’s work items and the sessions you can see: items created, updated, moved and commented on, runs, approvals, signals and agent commits, pushes and pull requests.',\n inputSchema: activityArgs,\n payloadSchema: activityPayload,\n // The server is already scoped to the caller's Factory; this only rejects a work item outside it.\n authorize: async ({ arguments: args }) =>\n !args.workItemId ||\n (await deps.workItems.getForProject(deps.orgId, deps.factoryProjectId, args.workItemId)) !== null,\n poll: async ({ arguments: args, cursor, maxEvents, maxAgeMs }) => {\n const now = Date.now();\n if (cursor == null || !decodeAuditAfterCursor(cursor)) {\n // No cursor means \"from now\". An unreadable one can't be resumed, so say events may have been missed.\n return { events: [], cursor: auditCursorAt(new Date(now)), ...(cursor != null ? { truncated: true } : {}) };\n }\n\n const page = await deps.audit.list({\n ...project,\n ...(args.actions?.length ? { actions: args.actions } : {}),\n after: cursor,\n limit: maxEvents,\n });\n const sessionCache = new Map<string, Promise<SourceControlSession | null>>();\n const lookup = (sessionId: string) => {\n let found = sessionCache.get(sessionId);\n if (!found) sessionCache.set(sessionId, (found = deps.sessions.getBySessionId(sessionId)));\n return found;\n };\n const visible = (session: SourceControlSession | null): session is SourceControlSession =>\n session !== null && canViewSourceControlSession(session, deps.viewer);\n /** What the event is about, or `undefined` when the caller may not see it. */\n const subjectOf = async (event: AuditEventRow): Promise<Subject | undefined> => {\n const workItem = event.targets.find(target => target.type === 'work_item');\n const named = event.targets.filter(target => target.type === 'session').map(target => target.id);\n const sessions = await Promise.all(named.map(lookup));\n let hidden = !sessions.every(visible);\n let session = sessions[0];\n let anonymousAgent = false;\n if (event.actorType === 'agent' && event.actorId.startsWith('agent:')) {\n const actorSession = await lookup(event.actorId.slice('agent:'.length));\n // A thread that isn't a session (anymore) may still have been a private one.\n if (!actorSession) anonymousAgent = true;\n else if (!visible(actorSession)) hidden = true;\n session ??= actorSession ?? undefined;\n }\n if (hidden) return workItem ? { workItem, privateSession: true } : undefined;\n if (!workItem && !session) return undefined;\n return {\n ...(workItem ? { workItem } : {}),\n ...(session ? { session } : {}),\n ...(anonymousAgent ? { anonymousAgent: true } : {}),\n };\n };\n\n const settledUntil = now - ACTIVITY_SETTLE_MS;\n const expiredBefore = maxAgeMs === undefined ? -Infinity : now - maxAgeMs;\n const events = [];\n let next = cursor;\n let unsettled = false;\n let expired = false;\n for (const event of page.events) {\n const at = event.occurredAt.getTime();\n if (at > settledUntil) {\n unsettled = true;\n break;\n }\n // Skipped rows still advance the cursor, so a filtered-out page isn't read again.\n next = encodeAuditCursor(event);\n if (at < expiredBefore) {\n expired = true;\n continue;\n }\n if (args.workItemId && !event.targets.some(t => t.type === 'work_item' && t.id === args.workItemId)) continue;\n const subject = await subjectOf(event);\n if (!subject) continue;\n events.push({ eventId: event.id, timestamp: event.occurredAt, data: toActivity(event, subject) });\n }\n return {\n events,\n cursor: next,\n ...(page.nextCursor && !unsettled ? { hasMore: true } : {}),\n ...(expired ? { truncated: true } : {}),\n };\n },\n });\n}\n"],"mappings":"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"}
|
package/dist/mcp/routes.d.ts
CHANGED
|
@@ -28,7 +28,6 @@ export interface FactoryMcpRoutesDeps {
|
|
|
28
28
|
/** Source-control handles a connector session can be filed under, tried in order. */
|
|
29
29
|
sourceControls: readonly SourceControlStorageHandle[];
|
|
30
30
|
tools: FactoryToolDependencies;
|
|
31
|
-
version?: string;
|
|
32
31
|
}
|
|
33
32
|
/** The connector session a user's MCP client acts from: one per user and Factory, stable across requests. */
|
|
34
33
|
export declare function connectorSessionId(orgId: string, userId: string, factoryProjectId: string): string;
|
package/dist/mcp/routes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/mcp/routes.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGpD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAQ/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAGpD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AAG5F,OAAO,KAAK,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/mcp/routes.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGpD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAQ/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAGpD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AAG5F,OAAO,KAAK,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAInF,eAAO,MAAM,gBAAgB,yCAAyC,CAAC;AASvE,MAAM,WAAW,gBAAgB;IAC/B,2FAA2F;IAC3F,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC;AAgBD,+FAA+F;AAC/F,eAAO,MAAM,iCAAiC,+BAA+B,CAAC;AAE9E,wBAAgB,8BAA8B,CAC5C,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,MAAM,EAAE,gBAAgB,GAAG,SAAS,EACpC,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACpD,MAAM,EAAE,CAMV;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,SAAS,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,QAAQ,EAAE,IAAI,CAAC,sBAAsB,EAAE,KAAK,CAAC,CAAC;IAC9C,qFAAqF;IACrF,cAAc,EAAE,SAAS,0BAA0B,EAAE,CAAC;IACtD,KAAK,EAAE,uBAAuB,CAAC;CAChC;AAED,6GAA6G;AAC7G,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAGlG;AAyFD,sEAAsE;AACtE,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,oBAAoB,EAC1B,OAAO,GAAE;IAAE,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAAO,GAC3C,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAe1C;AAuFD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,oBAAoB,GAAG,QAAQ,EAAE,CAoB5E"}
|
package/dist/mcp/routes.js
CHANGED
|
@@ -5,6 +5,7 @@ import { auditActorProfile } from "../storage/domains/audit/base.js";
|
|
|
5
5
|
import { FACTORY_TOOL_ANNOTATIONS } from "../tools/annotations.js";
|
|
6
6
|
import { createFactoryTools } from "../tools/index.js";
|
|
7
7
|
import { propagateClientDisconnect } from "./disconnect.js";
|
|
8
|
+
import { factoryActivityEvent } from "./events.js";
|
|
8
9
|
import { registerApiRoute } from "@mastra/core/server";
|
|
9
10
|
import { createHash } from "crypto";
|
|
10
11
|
import { z } from "zod";
|
|
@@ -19,6 +20,18 @@ const PROTECTED_RESOURCE_PREFIX = "/.well-known/oauth-protected-resource";
|
|
|
19
20
|
* that started this one; a connector session is started by a person, not a session.
|
|
20
21
|
*/
|
|
21
22
|
const EXCLUDED_TOOLS = /* @__PURE__ */ new Set(["factory_message_origin"]);
|
|
23
|
+
/**
|
|
24
|
+
* MCP requires a server version. The Factory doesn't expose its package
|
|
25
|
+
* version at runtime, so this versions the endpoint's interface instead.
|
|
26
|
+
*/
|
|
27
|
+
const FACTORY_MCP_SERVER_VERSION = "1.0.0";
|
|
28
|
+
/** What a connected client is told the server is for and where to start. */
|
|
29
|
+
const FACTORY_MCP_INSTRUCTIONS = [
|
|
30
|
+
"A Mastra Factory tracks software work as work items on a board; agent sessions plan and build them.",
|
|
31
|
+
"Start with `factory_list_attention` for what needs a person, or `search` and `fetch` to find a work item.",
|
|
32
|
+
"Work item tools file, update, move and follow items; session tools start, message and follow the agent sessions working on them.",
|
|
33
|
+
"Every call runs as the signed-in user. When the Factory has a repository, session tools act from a private connector session of theirs."
|
|
34
|
+
].join(" ");
|
|
22
35
|
/** Mastra Platform's WorkOS AuthKit, which issues tokens Platform's `/auth/verify` accepts. */
|
|
23
36
|
const PLATFORM_MCP_AUTHORIZATION_SERVER = "https://auth-kit.mastra.ai";
|
|
24
37
|
function resolveMcpAuthorizationServers(providerName, config, env = process.env) {
|
|
@@ -171,7 +184,7 @@ async function handleMcp(deps, c) {
|
|
|
171
184
|
factoryProjectId,
|
|
172
185
|
userId: tenant.userId,
|
|
173
186
|
sessionId,
|
|
174
|
-
resourceId:
|
|
187
|
+
resourceId: tenant.userId,
|
|
175
188
|
threadId: sessionId,
|
|
176
189
|
kind: "user",
|
|
177
190
|
interactive: true,
|
|
@@ -180,9 +193,22 @@ async function handleMcp(deps, c) {
|
|
|
180
193
|
const server = new MCPServer({
|
|
181
194
|
id: `factory-${factoryProjectId}`,
|
|
182
195
|
name: `${project.name ?? "Mastra"} Factory`,
|
|
183
|
-
version:
|
|
196
|
+
version: FACTORY_MCP_SERVER_VERSION,
|
|
184
197
|
description: "Manage work items, sessions and decisions in a Mastra Factory.",
|
|
185
|
-
|
|
198
|
+
instructions: FACTORY_MCP_INSTRUCTIONS,
|
|
199
|
+
legacy: "stateless",
|
|
200
|
+
tools: createFactoryMcpTools(scope, deps, { persistedSession: persisted }),
|
|
201
|
+
events: { "factory.activity": factoryActivityEvent({
|
|
202
|
+
orgId: tenant.orgId,
|
|
203
|
+
factoryProjectId,
|
|
204
|
+
audit: deps.tools.audit,
|
|
205
|
+
workItems: deps.tools.workItems,
|
|
206
|
+
sessions: deps.tools.sessions,
|
|
207
|
+
viewer: {
|
|
208
|
+
orgId: tenant.orgId,
|
|
209
|
+
userId: tenant.userId
|
|
210
|
+
}
|
|
211
|
+
}) }
|
|
186
212
|
});
|
|
187
213
|
const { req, res } = toReqRes(c.req.raw);
|
|
188
214
|
const done = server.startHTTP({
|
|
@@ -190,7 +216,8 @@ async function handleMcp(deps, c) {
|
|
|
190
216
|
httpPath: mcpPath,
|
|
191
217
|
req,
|
|
192
218
|
res
|
|
193
|
-
}).catch(() => {
|
|
219
|
+
}).catch((error) => {
|
|
220
|
+
console.warn("[factory] MCP request failed:", error);
|
|
194
221
|
if (!res.headersSent) {
|
|
195
222
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
196
223
|
res.end(JSON.stringify({
|
package/dist/mcp/routes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.js","names":[],"sources":["../../src/mcp/routes.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\n\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport { createTool } from '@mastra/core/tools';\nimport type { Tool } from '@mastra/core/tools';\nimport { MCPServer } from '@mastra/mcp';\nimport { toFetchResponse, toReqRes } from 'fetch-to-node';\nimport type { Context } from 'hono';\nimport { z } from 'zod';\n\nimport { getFactoryAuthUser } from '../auth.js';\nimport { PLATFORM_AUTH_PROVIDER } from '../platform-auth-host.js';\nimport type { RouteAuth } from '../routes/route.js';\nimport { ensureFactorySourceSession, FactorySourceSessionResolutionError } from '../session/factory-session.js';\nimport { auditActorProfile } from '../storage/domains/audit/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport { FACTORY_TOOL_ANNOTATIONS } from '../tools/annotations.js';\nimport { createFactoryTools } from '../tools/index.js';\nimport type { FactoryToolDependencies, FactoryToolScope } from '../tools/index.js';\nimport { propagateClientDisconnect } from './disconnect.js';\n\nexport const FACTORY_MCP_PATH = '/web/factory/projects/:projectId/mcp';\nconst PROTECTED_RESOURCE_PREFIX = '/.well-known/oauth-protected-resource';\n\n/**\n * Tools an MCP client never gets. `factory_message_origin` answers the session\n * that started this one; a connector session is started by a person, not a session.\n */\nconst EXCLUDED_TOOLS = new Set(['factory_message_origin']);\n\nexport interface FactoryMcpConfig {\n /** Serve the MCP endpoint. Default `true`; it is never served without an auth provider. */\n enabled?: boolean;\n /**\n * OAuth authorization servers advertised in the protected-resource metadata\n * (RFC 9728) so clients like ChatGPT know where to sign in. Defaults to the\n * `MASTRA_FACTORY_MCP_AUTHORIZATION_SERVER` env var, then to Mastra Platform's\n * AuthKit when the Factory uses Platform auth. The endpoint isn't served\n * without one, since clients would have nowhere to sign in.\n */\n authorizationServers?: string[];\n}\n\n/** Mastra Platform's WorkOS AuthKit, which issues tokens Platform's `/auth/verify` accepts. */\nexport const PLATFORM_MCP_AUTHORIZATION_SERVER = 'https://auth-kit.mastra.ai';\n\nexport function resolveMcpAuthorizationServers(\n providerName: string | undefined,\n config: FactoryMcpConfig | undefined,\n env: Record<string, string | undefined> = process.env,\n): string[] {\n if (config?.authorizationServers?.length) return config.authorizationServers;\n const fromEnv = env.MASTRA_FACTORY_MCP_AUTHORIZATION_SERVER?.trim();\n if (fromEnv) return [fromEnv];\n if (providerName === PLATFORM_AUTH_PROVIDER) return [PLATFORM_MCP_AUTHORIZATION_SERVER];\n return [];\n}\n\nexport interface FactoryMcpRoutesDeps {\n auth: RouteAuth;\n publicOrigin: string;\n authorizationServers: string[];\n projects: Pick<FactoryProjectsStorage, 'get'>;\n /** Source-control handles a connector session can be filed under, tried in order. */\n sourceControls: readonly SourceControlStorageHandle[];\n tools: FactoryToolDependencies;\n version?: string;\n}\n\n/** The connector session a user's MCP client acts from: one per user and Factory, stable across requests. */\nexport function connectorSessionId(orgId: string, userId: string, factoryProjectId: string): string {\n const hex = createHash('sha256').update(`mcp\\0${orgId}\\0${userId}\\0${factoryProjectId}`).digest('hex');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-5${hex.slice(13, 16)}-8${hex.slice(17, 20)}-${hex.slice(20, 32)}`;\n}\n\n/**\n * Get or create the private user session behind a person's MCP client, so\n * session-scoped tools (start/signal sessions, follow work items) have a real\n * session to act from. A Factory without a connected repository has nowhere to\n * file one; its tools still run against the stable id.\n */\nasync function ensureConnectorSession(\n deps: FactoryMcpRoutesDeps,\n args: { orgId: string; userId: string; factoryProjectId: string },\n): Promise<{ sessionId: string; persisted: boolean }> {\n const sessionId = connectorSessionId(args.orgId, args.userId, args.factoryProjectId);\n if (await deps.tools.sessions.getBySessionId(sessionId)) return { sessionId, persisted: true };\n for (const sourceControl of deps.sourceControls) {\n try {\n await ensureFactorySourceSession({\n sourceControl,\n orgId: args.orgId,\n factoryProjectId: args.factoryProjectId,\n branch: `factory/mcp-${sessionId.slice(0, 8)}`,\n attributeToUserId: args.userId,\n sessionId,\n visibility: 'private',\n });\n return { sessionId, persisted: true };\n } catch (error) {\n if (!(error instanceof FactorySourceSessionResolutionError)) throw error;\n }\n }\n return { sessionId, persisted: false };\n}\n\nfunction workItemUrl(publicOrigin: string, factoryProjectId: string, workItemId: string): string {\n return `${publicOrigin}/factory/${encodeURIComponent(factoryProjectId)}/work-items/${encodeURIComponent(workItemId)}`;\n}\n\n/**\n * `search` and `fetch`, the two tools ChatGPT's connectors and deep research\n * look for, answered from the Factory's work items.\n */\nfunction createSearchFetchTools(\n scope: FactoryToolScope,\n deps: FactoryMcpRoutesDeps,\n inspect: Tool<any, any, any, any> | undefined,\n): Record<string, Tool<any, any, any, any>> {\n const project = { orgId: scope.orgId, factoryProjectId: scope.factoryProjectId };\n return {\n search: createTool({\n id: 'search',\n description:\n 'Search this Factory’s work items by title or id. Returns matching items with ids to pass to `fetch`.',\n inputSchema: z.object({ query: z.string().describe('Words to look for in work item titles.') }),\n execute: async ({ query }) => {\n const terms = query.toLowerCase().split(/\\s+/).filter(Boolean);\n const items = await deps.tools.workItems.list(project);\n const results = items\n .filter(item => {\n const haystack = `${item.id} ${item.title}`.toLowerCase();\n return terms.every(term => haystack.includes(term));\n })\n .slice(0, 20)\n .map(item => ({\n id: item.id,\n title: item.title,\n url: workItemUrl(deps.publicOrigin, scope.factoryProjectId, item.id),\n }));\n return { results };\n },\n }),\n fetch: createTool({\n id: 'fetch',\n description: 'Fetch everything the Factory knows about one work item, by the id `search` returned.',\n inputSchema: z.object({ id: z.string().describe('Work item id.') }),\n execute: async ({ id }, context) => {\n if (!inspect?.execute) throw new Error('Work item details are unavailable.');\n const details = (await inspect.execute({ workItem: id }, context)) as { item?: { title?: string } };\n return {\n id,\n title: details.item?.title ?? id,\n text: JSON.stringify(details, null, 2),\n url: workItemUrl(deps.publicOrigin, scope.factoryProjectId, id),\n metadata: { factoryProjectId: scope.factoryProjectId },\n };\n },\n }),\n };\n}\n\n/** The tools a signed-in person's MCP client sees for one Factory. */\nexport function createFactoryMcpTools(\n scope: FactoryToolScope,\n deps: FactoryMcpRoutesDeps,\n options: { persistedSession?: boolean } = {},\n): Record<string, Tool<any, any, any, any>> {\n const factoryTools = createFactoryTools({ scope, deps: deps.tools }) as Record<string, Tool<any, any, any, any>>;\n const tools: Record<string, Tool<any, any, any, any>> = {};\n for (const [name, tool] of Object.entries(factoryTools)) {\n if (EXCLUDED_TOOLS.has(name)) continue;\n // Starting a sub-session needs a real parent session row to file it under.\n if (name === 'factory_start_session' && options.persistedSession === false) continue;\n tools[name] = tool;\n }\n Object.assign(tools, createSearchFetchTools(scope, deps, factoryTools.factory_inspect_work_item));\n for (const [name, tool] of Object.entries(tools)) {\n const annotations = FACTORY_TOOL_ANNOTATIONS[name];\n if (annotations) tool.mcp = { ...tool.mcp, annotations };\n }\n return tools;\n}\n\nfunction resourceMetadataUrl(deps: FactoryMcpRoutesDeps, mcpPath: string): string {\n return `${deps.publicOrigin}${PROTECTED_RESOURCE_PREFIX}${mcpPath}`;\n}\n\nfunction unauthorized(deps: FactoryMcpRoutesDeps, mcpPath: string, error = 'unauthorized'): Response {\n return new Response(JSON.stringify({ error }), {\n status: 401,\n headers: {\n 'Content-Type': 'application/json',\n 'WWW-Authenticate': `Bearer resource_metadata=\"${resourceMetadataUrl(deps, mcpPath)}\"`,\n },\n });\n}\n\nasync function handleMcp(deps: FactoryMcpRoutesDeps, c: Context): Promise<Response> {\n const factoryProjectId = c.req.param('projectId') ?? '';\n const mcpPath = new URL(c.req.url).pathname;\n\n await deps.auth.ensureUser(c);\n const tenant = deps.auth.tenant(c);\n if (!tenant) return unauthorized(deps, mcpPath);\n if (!tenant.orgId) return c.json({ error: 'organization_required' }, 403);\n\n const project = await deps.projects.get({ orgId: tenant.orgId, id: factoryProjectId });\n if (!project) return c.json({ error: 'not_found' }, 404);\n\n const { sessionId, persisted } = await ensureConnectorSession(deps, {\n orgId: tenant.orgId,\n userId: tenant.userId,\n factoryProjectId,\n });\n const actorProfile = auditActorProfile(getFactoryAuthUser(c));\n const scope: FactoryToolScope = {\n orgId: tenant.orgId,\n factoryProjectId,\n userId: tenant.userId,\n sessionId,\n resourceId: sessionId,\n threadId: sessionId,\n kind: 'user',\n // A signed-in person is driving the client: the same reach as a web UI turn.\n interactive: true,\n ...(actorProfile ? { actorProfile } : {}),\n };\n\n const server = new MCPServer({\n id: `factory-${factoryProjectId}`,\n name: `${project.name ?? 'Mastra'} Factory`,\n version: deps.version ?? '1.0.0',\n description: 'Manage work items, sessions and decisions in a Mastra Factory.',\n tools: createFactoryMcpTools(scope, deps, { persistedSession: persisted }),\n });\n\n const { req, res } = toReqRes(c.req.raw);\n const done = server\n .startHTTP({ url: new URL(c.req.url), httpPath: mcpPath, req, res })\n .catch(() => {\n if (!res.headersSent) {\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error' }, id: null }),\n );\n }\n })\n .finally(() => void server.close());\n const response = propagateClientDisconnect(await toFetchResponse(res), res);\n if (res.writableEnded) await done;\n return response;\n}\n\n/**\n * The Factory's MCP endpoint: Streamable HTTP at\n * `/web/factory/projects/:projectId/mcp`, plus its RFC 9728 protected-resource\n * metadata. Callers sign in with OAuth through the Factory's auth provider and\n * get the tools a web UI session would, under the same permissions.\n */\nexport function buildFactoryMcpRoutes(deps: FactoryMcpRoutesDeps): ApiRoute[] {\n const handler = (c: unknown) => handleMcp(deps, c as Context);\n return [\n ...(['POST', 'GET', 'DELETE'] as const).map(method =>\n registerApiRoute(FACTORY_MCP_PATH, { method, requiresAuth: false, handler }),\n ),\n registerApiRoute(`${PROTECTED_RESOURCE_PREFIX}${FACTORY_MCP_PATH}`, {\n method: 'GET',\n requiresAuth: false,\n handler: c => {\n const mcpPath = new URL(c.req.url).pathname.slice(PROTECTED_RESOURCE_PREFIX.length);\n return c.json({\n resource: `${deps.publicOrigin}${mcpPath}`,\n authorization_servers: deps.authorizationServers,\n bearer_methods_supported: ['header'],\n resource_name: 'Mastra Factory',\n });\n },\n }),\n ];\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"routes.js","names":[],"sources":["../../src/mcp/routes.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\n\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport { createTool } from '@mastra/core/tools';\nimport type { Tool } from '@mastra/core/tools';\nimport { MCPServer } from '@mastra/mcp';\nimport { toFetchResponse, toReqRes } from 'fetch-to-node';\nimport type { Context } from 'hono';\nimport { z } from 'zod';\n\nimport { getFactoryAuthUser } from '../auth.js';\nimport { PLATFORM_AUTH_PROVIDER } from '../platform-auth-host.js';\nimport type { RouteAuth } from '../routes/route.js';\nimport { ensureFactorySourceSession, FactorySourceSessionResolutionError } from '../session/factory-session.js';\nimport { auditActorProfile } from '../storage/domains/audit/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport { FACTORY_TOOL_ANNOTATIONS } from '../tools/annotations.js';\nimport { createFactoryTools } from '../tools/index.js';\nimport type { FactoryToolDependencies, FactoryToolScope } from '../tools/index.js';\nimport { propagateClientDisconnect } from './disconnect.js';\nimport { factoryActivityEvent } from './events.js';\n\nexport const FACTORY_MCP_PATH = '/web/factory/projects/:projectId/mcp';\nconst PROTECTED_RESOURCE_PREFIX = '/.well-known/oauth-protected-resource';\n\n/**\n * Tools an MCP client never gets. `factory_message_origin` answers the session\n * that started this one; a connector session is started by a person, not a session.\n */\nconst EXCLUDED_TOOLS = new Set(['factory_message_origin']);\n\nexport interface FactoryMcpConfig {\n /** Serve the MCP endpoint. Default `true`; it is never served without an auth provider. */\n enabled?: boolean;\n /**\n * OAuth authorization servers advertised in the protected-resource metadata\n * (RFC 9728) so clients like ChatGPT know where to sign in. Defaults to the\n * `MASTRA_FACTORY_MCP_AUTHORIZATION_SERVER` env var, then to Mastra Platform's\n * AuthKit when the Factory uses Platform auth. The endpoint isn't served\n * without one, since clients would have nowhere to sign in.\n */\n authorizationServers?: string[];\n}\n\n/**\n * MCP requires a server version. The Factory doesn't expose its package\n * version at runtime, so this versions the endpoint's interface instead.\n */\nconst FACTORY_MCP_SERVER_VERSION = '1.0.0';\n\n/** What a connected client is told the server is for and where to start. */\nconst FACTORY_MCP_INSTRUCTIONS = [\n 'A Mastra Factory tracks software work as work items on a board; agent sessions plan and build them.',\n 'Start with `factory_list_attention` for what needs a person, or `search` and `fetch` to find a work item.',\n 'Work item tools file, update, move and follow items; session tools start, message and follow the agent sessions working on them.',\n 'Every call runs as the signed-in user. When the Factory has a repository, session tools act from a private connector session of theirs.',\n].join(' ');\n\n/** Mastra Platform's WorkOS AuthKit, which issues tokens Platform's `/auth/verify` accepts. */\nexport const PLATFORM_MCP_AUTHORIZATION_SERVER = 'https://auth-kit.mastra.ai';\n\nexport function resolveMcpAuthorizationServers(\n providerName: string | undefined,\n config: FactoryMcpConfig | undefined,\n env: Record<string, string | undefined> = process.env,\n): string[] {\n if (config?.authorizationServers?.length) return config.authorizationServers;\n const fromEnv = env.MASTRA_FACTORY_MCP_AUTHORIZATION_SERVER?.trim();\n if (fromEnv) return [fromEnv];\n if (providerName === PLATFORM_AUTH_PROVIDER) return [PLATFORM_MCP_AUTHORIZATION_SERVER];\n return [];\n}\n\nexport interface FactoryMcpRoutesDeps {\n auth: RouteAuth;\n publicOrigin: string;\n authorizationServers: string[];\n projects: Pick<FactoryProjectsStorage, 'get'>;\n /** Source-control handles a connector session can be filed under, tried in order. */\n sourceControls: readonly SourceControlStorageHandle[];\n tools: FactoryToolDependencies;\n}\n\n/** The connector session a user's MCP client acts from: one per user and Factory, stable across requests. */\nexport function connectorSessionId(orgId: string, userId: string, factoryProjectId: string): string {\n const hex = createHash('sha256').update(`mcp\\0${orgId}\\0${userId}\\0${factoryProjectId}`).digest('hex');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-5${hex.slice(13, 16)}-8${hex.slice(17, 20)}-${hex.slice(20, 32)}`;\n}\n\n/**\n * Get or create the private user session behind a person's MCP client, so\n * session-scoped tools (start/signal sessions, follow work items) have a real\n * session to act from. A Factory without a connected repository has nowhere to\n * file one; its tools still run against the stable id.\n */\nasync function ensureConnectorSession(\n deps: FactoryMcpRoutesDeps,\n args: { orgId: string; userId: string; factoryProjectId: string },\n): Promise<{ sessionId: string; persisted: boolean }> {\n const sessionId = connectorSessionId(args.orgId, args.userId, args.factoryProjectId);\n if (await deps.tools.sessions.getBySessionId(sessionId)) return { sessionId, persisted: true };\n for (const sourceControl of deps.sourceControls) {\n try {\n await ensureFactorySourceSession({\n sourceControl,\n orgId: args.orgId,\n factoryProjectId: args.factoryProjectId,\n branch: `factory/mcp-${sessionId.slice(0, 8)}`,\n attributeToUserId: args.userId,\n sessionId,\n visibility: 'private',\n });\n return { sessionId, persisted: true };\n } catch (error) {\n if (!(error instanceof FactorySourceSessionResolutionError)) throw error;\n }\n }\n return { sessionId, persisted: false };\n}\n\nfunction workItemUrl(publicOrigin: string, factoryProjectId: string, workItemId: string): string {\n return `${publicOrigin}/factory/${encodeURIComponent(factoryProjectId)}/work-items/${encodeURIComponent(workItemId)}`;\n}\n\n/**\n * `search` and `fetch`, the two tools ChatGPT's connectors and deep research\n * look for, answered from the Factory's work items.\n */\nfunction createSearchFetchTools(\n scope: FactoryToolScope,\n deps: FactoryMcpRoutesDeps,\n inspect: Tool<any, any, any, any> | undefined,\n): Record<string, Tool<any, any, any, any>> {\n const project = { orgId: scope.orgId, factoryProjectId: scope.factoryProjectId };\n return {\n search: createTool({\n id: 'search',\n description:\n 'Search this Factory’s work items by title or id. Returns matching items with ids to pass to `fetch`.',\n inputSchema: z.object({ query: z.string().describe('Words to look for in work item titles.') }),\n execute: async ({ query }) => {\n const terms = query.toLowerCase().split(/\\s+/).filter(Boolean);\n const items = await deps.tools.workItems.list(project);\n const results = items\n .filter(item => {\n const haystack = `${item.id} ${item.title}`.toLowerCase();\n return terms.every(term => haystack.includes(term));\n })\n .slice(0, 20)\n .map(item => ({\n id: item.id,\n title: item.title,\n url: workItemUrl(deps.publicOrigin, scope.factoryProjectId, item.id),\n }));\n return { results };\n },\n }),\n fetch: createTool({\n id: 'fetch',\n description: 'Fetch everything the Factory knows about one work item, by the id `search` returned.',\n inputSchema: z.object({ id: z.string().describe('Work item id.') }),\n execute: async ({ id }, context) => {\n if (!inspect?.execute) throw new Error('Work item details are unavailable.');\n const details = (await inspect.execute({ workItem: id }, context)) as { item?: { title?: string } };\n return {\n id,\n title: details.item?.title ?? id,\n text: JSON.stringify(details, null, 2),\n url: workItemUrl(deps.publicOrigin, scope.factoryProjectId, id),\n metadata: { factoryProjectId: scope.factoryProjectId },\n };\n },\n }),\n };\n}\n\n/** The tools a signed-in person's MCP client sees for one Factory. */\nexport function createFactoryMcpTools(\n scope: FactoryToolScope,\n deps: FactoryMcpRoutesDeps,\n options: { persistedSession?: boolean } = {},\n): Record<string, Tool<any, any, any, any>> {\n const factoryTools = createFactoryTools({ scope, deps: deps.tools }) as Record<string, Tool<any, any, any, any>>;\n const tools: Record<string, Tool<any, any, any, any>> = {};\n for (const [name, tool] of Object.entries(factoryTools)) {\n if (EXCLUDED_TOOLS.has(name)) continue;\n // Starting a sub-session needs a real parent session row to file it under.\n if (name === 'factory_start_session' && options.persistedSession === false) continue;\n tools[name] = tool;\n }\n Object.assign(tools, createSearchFetchTools(scope, deps, factoryTools.factory_inspect_work_item));\n for (const [name, tool] of Object.entries(tools)) {\n const annotations = FACTORY_TOOL_ANNOTATIONS[name];\n if (annotations) tool.mcp = { ...tool.mcp, annotations };\n }\n return tools;\n}\n\nfunction resourceMetadataUrl(deps: FactoryMcpRoutesDeps, mcpPath: string): string {\n return `${deps.publicOrigin}${PROTECTED_RESOURCE_PREFIX}${mcpPath}`;\n}\n\nfunction unauthorized(deps: FactoryMcpRoutesDeps, mcpPath: string, error = 'unauthorized'): Response {\n return new Response(JSON.stringify({ error }), {\n status: 401,\n headers: {\n 'Content-Type': 'application/json',\n 'WWW-Authenticate': `Bearer resource_metadata=\"${resourceMetadataUrl(deps, mcpPath)}\"`,\n },\n });\n}\n\nasync function handleMcp(deps: FactoryMcpRoutesDeps, c: Context): Promise<Response> {\n const factoryProjectId = c.req.param('projectId') ?? '';\n const mcpPath = new URL(c.req.url).pathname;\n\n await deps.auth.ensureUser(c);\n const tenant = deps.auth.tenant(c);\n if (!tenant) return unauthorized(deps, mcpPath);\n if (!tenant.orgId) return c.json({ error: 'organization_required' }, 403);\n\n const project = await deps.projects.get({ orgId: tenant.orgId, id: factoryProjectId });\n if (!project) return c.json({ error: 'not_found' }, 404);\n\n const { sessionId, persisted } = await ensureConnectorSession(deps, {\n orgId: tenant.orgId,\n userId: tenant.userId,\n factoryProjectId,\n });\n const actorProfile = auditActorProfile(getFactoryAuthUser(c));\n const scope: FactoryToolScope = {\n orgId: tenant.orgId,\n factoryProjectId,\n userId: tenant.userId,\n sessionId,\n // A user session's memory lives under its owner.\n resourceId: tenant.userId,\n threadId: sessionId,\n kind: 'user',\n // A signed-in person is driving the client: the same reach as a web UI turn.\n interactive: true,\n ...(actorProfile ? { actorProfile } : {}),\n };\n\n const server = new MCPServer({\n id: `factory-${factoryProjectId}`,\n name: `${project.name ?? 'Mastra'} Factory`,\n version: FACTORY_MCP_SERVER_VERSION,\n description: 'Manage work items, sessions and decisions in a Mastra Factory.',\n instructions: FACTORY_MCP_INSTRUCTIONS,\n // VS Code and Cursor still open with 2025-era `initialize`; serve them statelessly.\n legacy: 'stateless',\n tools: createFactoryMcpTools(scope, deps, { persistedSession: persisted }),\n events: {\n 'factory.activity': factoryActivityEvent({\n orgId: tenant.orgId,\n factoryProjectId,\n audit: deps.tools.audit,\n workItems: deps.tools.workItems,\n sessions: deps.tools.sessions,\n viewer: { orgId: tenant.orgId, userId: tenant.userId },\n }),\n },\n });\n\n const { req, res } = toReqRes(c.req.raw);\n const done = server\n .startHTTP({ url: new URL(c.req.url), httpPath: mcpPath, req, res })\n .catch(error => {\n console.warn('[factory] MCP request failed:', error);\n if (!res.headersSent) {\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error' }, id: null }),\n );\n }\n })\n .finally(() => void server.close());\n const response = propagateClientDisconnect(await toFetchResponse(res), res);\n if (res.writableEnded) await done;\n return response;\n}\n\n/**\n * The Factory's MCP endpoint: Streamable HTTP at\n * `/web/factory/projects/:projectId/mcp`, plus its RFC 9728 protected-resource\n * metadata. Callers sign in with OAuth through the Factory's auth provider and\n * get the tools a web UI session would, under the same permissions.\n */\nexport function buildFactoryMcpRoutes(deps: FactoryMcpRoutesDeps): ApiRoute[] {\n const handler = (c: unknown) => handleMcp(deps, c as Context);\n return [\n ...(['POST', 'GET', 'DELETE'] as const).map(method =>\n registerApiRoute(FACTORY_MCP_PATH, { method, requiresAuth: false, handler }),\n ),\n registerApiRoute(`${PROTECTED_RESOURCE_PREFIX}${FACTORY_MCP_PATH}`, {\n method: 'GET',\n requiresAuth: false,\n handler: c => {\n const mcpPath = new URL(c.req.url).pathname.slice(PROTECTED_RESOURCE_PREFIX.length);\n return c.json({\n resource: `${deps.publicOrigin}${mcpPath}`,\n authorization_servers: deps.authorizationServers,\n bearer_methods_supported: ['header'],\n resource_name: 'Mastra Factory',\n });\n },\n }),\n ];\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import "@mastra/core/workspace";
|
|
2
|
+
import "path";
|
|
1
3
|
import { createHash } from "crypto";
|
|
2
4
|
//#region ../../packages/_internals/workspace/dist/index.js
|
|
3
5
|
/** Directory, relative to the template's build cwd, that holds every marker below. */
|
|
@@ -42,7 +44,22 @@ function normalizeSetupCommands(setupCommand) {
|
|
|
42
44
|
function setupMarkerContent(setupCommand) {
|
|
43
45
|
return `sha256:${createHash("sha256").update(normalizeSetupCommands(setupCommand).join("\n")).digest("hex")}`;
|
|
44
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Shell scripts for the file operations a sandbox can only do atomically (or
|
|
49
|
+
* without moving file bytes over the network) by running a command inside the
|
|
50
|
+
* VM. Each builder takes absolute paths and returns one POSIX `sh` script; the
|
|
51
|
+
* caller runs it with `sh -c` and maps the exit codes below to typed errors.
|
|
52
|
+
*/
|
|
53
|
+
/** Exit codes the scripts use for outcomes the caller turns into typed errors. Any other non-zero code is a plain failure. */
|
|
54
|
+
const FILE_COMMAND_EXIT = {
|
|
55
|
+
notFound: 20,
|
|
56
|
+
isDirectory: 21,
|
|
57
|
+
exists: 22,
|
|
58
|
+
notDirectory: 23,
|
|
59
|
+
destIsDirectory: 24
|
|
60
|
+
};
|
|
61
|
+
const { notFound, isDirectory, exists, notDirectory, destIsDirectory } = FILE_COMMAND_EXIT;
|
|
45
62
|
//#endregion
|
|
46
|
-
export { SETUP_FAILED_MARKER_PATH, SETUP_MARKER_DIR, SETUP_MARKER_PATH, WORKSPACE_SETUP_MARKER_PATH, normalizeSetupCommands, repoSetupMarkerPath, setupMarkerContent };
|
|
63
|
+
export { FILE_COMMAND_EXIT, SETUP_FAILED_MARKER_PATH, SETUP_MARKER_DIR, SETUP_MARKER_PATH, WORKSPACE_SETUP_MARKER_PATH, normalizeSetupCommands, repoSetupMarkerPath, setupMarkerContent };
|
|
47
64
|
|
|
48
65
|
//# sourceMappingURL=index.js.map
|