@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
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { readFactorySessionScope } from "../../rules/binding-context.js";
|
|
2
|
+
import { SESSION_LINK_LABEL, sessionLink } from "./channel-reply-processor.js";
|
|
3
|
+
import "./channel-session-link-processor.js";
|
|
4
|
+
import { postTarget, resolveRenderContext } from "./share-file-tool.js";
|
|
5
|
+
import "@mastra/core/channels";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { createTool } from "@mastra/core/tools";
|
|
8
|
+
//#region src/integrations/slack/reply-tool.ts
|
|
9
|
+
const REPLY_TOOL_NAME = "reply_in_thread";
|
|
10
|
+
/** Set once the first reply of a new session carried the session link. */
|
|
11
|
+
const LINKED_KEY = "factory__replyLinked";
|
|
12
|
+
const replyInput = z.object({ text: z.string().trim().min(1).max(12e3).describe("The message to post, in markdown.") }).strict();
|
|
13
|
+
/**
|
|
14
|
+
* The only way a channel run speaks when the adapter hides reply text
|
|
15
|
+
* (`textDisplay: 'hidden'`). Posting becomes a choice the agent makes, so it
|
|
16
|
+
* stops narrating each step into the thread and can say nothing on purpose.
|
|
17
|
+
* Offered only when the Slack adapter hides reply text and the run's thread is
|
|
18
|
+
* linked to Slack; with text shown the reply stream already posts and this
|
|
19
|
+
* tool would double every message.
|
|
20
|
+
*/
|
|
21
|
+
function createReplyTool(requestContext, deps = {}) {
|
|
22
|
+
return { [REPLY_TOOL_NAME]: createTool({
|
|
23
|
+
id: REPLY_TOOL_NAME,
|
|
24
|
+
description: "Post a message in this chat thread. Nothing you write outside this tool reaches the thread, so use it for anything the people there should see: a short note that you are starting a longer job, a question you need answered, and the outcome when you finish. Post when you have something new; do not repeat yourself or post that nothing changed. To stay quiet, do not call it.",
|
|
25
|
+
inputSchema: replyInput,
|
|
26
|
+
execute: async (input, context) => {
|
|
27
|
+
const link = firstReplyLink(requestContext);
|
|
28
|
+
const suffix = link ? `\n\n[${SESSION_LINK_LABEL}](${link})` : "";
|
|
29
|
+
const toolCallId = context?.agent?.toolCallId;
|
|
30
|
+
const streamed = toolCallId ? replyStreams(requestContext).get(toolCallId) : void 0;
|
|
31
|
+
if (toolCallId) replyStreams(requestContext).delete(toolCallId);
|
|
32
|
+
if (!streamed || !await streamed.finish(input.text, suffix)) await (await postTarget(requestContext, deps)).chatThread.post({ markdown: `${input.text}${suffix}` });
|
|
33
|
+
if (link) requestContext.set(LINKED_KEY, true);
|
|
34
|
+
return { posted: true };
|
|
35
|
+
}
|
|
36
|
+
}) };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The session link the first reply of a new session ends with. With reply
|
|
40
|
+
* text hidden, `FactoryChannelSessionLinkProcessor`'s text delta never
|
|
41
|
+
* reaches the thread, so the tool carries the link instead.
|
|
42
|
+
*/
|
|
43
|
+
function firstReplyLink(requestContext) {
|
|
44
|
+
if (!requestContext.get("factory__newChannelSession") || requestContext.get(LINKED_KEY)) return void 0;
|
|
45
|
+
const controller = requestContext.get("controller");
|
|
46
|
+
if (!controller?.threadId) return void 0;
|
|
47
|
+
return sessionLink({
|
|
48
|
+
threadId: controller.threadId,
|
|
49
|
+
sessionId: controller.sessionId,
|
|
50
|
+
resourceId: controller.resourceId,
|
|
51
|
+
factoryProjectId: readFactorySessionScope(requestContext)?.factoryProjectId
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** Live posts of `reply_in_thread` calls whose text is still streaming in, by tool call id. */
|
|
55
|
+
const STREAMS_KEY = "factory__replyStreams";
|
|
56
|
+
function replyStreams(requestContext) {
|
|
57
|
+
let streams = requestContext.get(STREAMS_KEY);
|
|
58
|
+
if (!streams) {
|
|
59
|
+
streams = /* @__PURE__ */ new Map();
|
|
60
|
+
requestContext.set(STREAMS_KEY, streams);
|
|
61
|
+
}
|
|
62
|
+
return streams;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* One streamed Slack post, opened on the first visible text so an abandoned
|
|
66
|
+
* call never leaves an empty message behind.
|
|
67
|
+
*/
|
|
68
|
+
function openReplyStream(chatThread) {
|
|
69
|
+
const queue = [];
|
|
70
|
+
let sent = "";
|
|
71
|
+
let closed = false;
|
|
72
|
+
let wake;
|
|
73
|
+
let done;
|
|
74
|
+
async function* chunks() {
|
|
75
|
+
while (true) {
|
|
76
|
+
while (queue.length > 0) yield queue.shift();
|
|
77
|
+
if (closed) return;
|
|
78
|
+
await new Promise((resolve) => wake = resolve);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const push = (text) => {
|
|
82
|
+
if (!text) return;
|
|
83
|
+
queue.push(text);
|
|
84
|
+
done ??= chatThread.post(chunks()).then(() => true, (error) => {
|
|
85
|
+
console.warn("[factory] streamed reply failed; posting it whole", { error });
|
|
86
|
+
return false;
|
|
87
|
+
});
|
|
88
|
+
wake?.();
|
|
89
|
+
};
|
|
90
|
+
return {
|
|
91
|
+
update(text) {
|
|
92
|
+
const visible = text.trimStart();
|
|
93
|
+
if (!visible.startsWith(sent)) return;
|
|
94
|
+
push(visible.slice(sent.length));
|
|
95
|
+
sent = visible;
|
|
96
|
+
},
|
|
97
|
+
async finish(text, suffix) {
|
|
98
|
+
if (text.startsWith(sent)) push(text.slice(sent.length));
|
|
99
|
+
if (!done) return false;
|
|
100
|
+
push(suffix);
|
|
101
|
+
closed = true;
|
|
102
|
+
wake?.();
|
|
103
|
+
return done;
|
|
104
|
+
},
|
|
105
|
+
async close() {
|
|
106
|
+
closed = true;
|
|
107
|
+
wake?.();
|
|
108
|
+
return done ?? false;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Streams a `reply_in_thread` call's `text` into the thread while the model is
|
|
114
|
+
* still writing it, so a long reply appears as it is written instead of all at
|
|
115
|
+
* once when the call completes. The tool finishes the post with the validated
|
|
116
|
+
* text. Runs whose adapter doesn't stream, or that show reply text, are left
|
|
117
|
+
* alone.
|
|
118
|
+
*/
|
|
119
|
+
var FactoryReplyStreamProcessor = class {
|
|
120
|
+
deps;
|
|
121
|
+
id = "factory-reply-stream";
|
|
122
|
+
constructor(deps = {}) {
|
|
123
|
+
this.deps = deps;
|
|
124
|
+
}
|
|
125
|
+
async processOutputStream({ part, state, requestContext }) {
|
|
126
|
+
if (!requestContext) return part;
|
|
127
|
+
const args = state.args ??= {};
|
|
128
|
+
if (part.type === "tool-call-input-streaming-start" && part.payload.toolName === "reply_in_thread") {
|
|
129
|
+
const render = await resolveRenderContext(requestContext, this.deps);
|
|
130
|
+
if (render?.textDisplay !== "hidden" || !render.streaming?.enabled) return part;
|
|
131
|
+
args[part.payload.toolCallId] = "";
|
|
132
|
+
replyStreams(requestContext).set(part.payload.toolCallId, openReplyStream(render.chatThread));
|
|
133
|
+
} else if (part.type === "tool-call-delta" && part.payload.toolCallId in args) {
|
|
134
|
+
const text = partialStringField(args[part.payload.toolCallId] += part.payload.argsTextDelta, "text");
|
|
135
|
+
if (text !== void 0) replyStreams(requestContext).get(part.payload.toolCallId)?.update(text);
|
|
136
|
+
} else if ((part.type === "tool-error" || part.type === "tool-result") && part.payload.toolCallId in args) {
|
|
137
|
+
delete args[part.payload.toolCallId];
|
|
138
|
+
await closeStream(requestContext, part.payload.toolCallId);
|
|
139
|
+
} else if (RUN_END.has(part.type)) {
|
|
140
|
+
for (const toolCallId of Object.keys(args)) await closeStream(requestContext, toolCallId);
|
|
141
|
+
state.args = {};
|
|
142
|
+
}
|
|
143
|
+
return part;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
/** Chunks after which no `reply_in_thread` call of this run will still execute. */
|
|
147
|
+
const RUN_END = /* @__PURE__ */ new Set([
|
|
148
|
+
"finish",
|
|
149
|
+
"abort",
|
|
150
|
+
"error",
|
|
151
|
+
"tripwire"
|
|
152
|
+
]);
|
|
153
|
+
/**
|
|
154
|
+
* Ends a streamed post whose call will never reach `execute`, keeping the text
|
|
155
|
+
* shown so far, so Slack isn't left with a message that never finishes.
|
|
156
|
+
*/
|
|
157
|
+
async function closeStream(requestContext, toolCallId) {
|
|
158
|
+
const streams = replyStreams(requestContext);
|
|
159
|
+
const stream = streams.get(toolCallId);
|
|
160
|
+
if (!stream) return;
|
|
161
|
+
streams.delete(toolCallId);
|
|
162
|
+
await stream.close();
|
|
163
|
+
}
|
|
164
|
+
const ESCAPES = {
|
|
165
|
+
"\"": "\"",
|
|
166
|
+
"\\": "\\",
|
|
167
|
+
"/": "/",
|
|
168
|
+
b: "\b",
|
|
169
|
+
f: "\f",
|
|
170
|
+
n: "\n",
|
|
171
|
+
r: "\r",
|
|
172
|
+
t: " "
|
|
173
|
+
};
|
|
174
|
+
/** The decoded value of a top-level string field in JSON that may still be cut off mid-string. */
|
|
175
|
+
function partialStringField(json, field) {
|
|
176
|
+
const start = new RegExp(`"${field}"\\s*:\\s*"`).exec(json);
|
|
177
|
+
if (!start) return void 0;
|
|
178
|
+
let out = "";
|
|
179
|
+
for (let i = start.index + start[0].length; i < json.length; i++) {
|
|
180
|
+
const ch = json[i];
|
|
181
|
+
if (ch === "\"") return out;
|
|
182
|
+
if (ch !== "\\") {
|
|
183
|
+
out += ch;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const next = json[i + 1];
|
|
187
|
+
if (next === void 0) return out;
|
|
188
|
+
if (next === "u") {
|
|
189
|
+
const hex = json.slice(i + 2, i + 6);
|
|
190
|
+
if (hex.length < 4) return out;
|
|
191
|
+
out += String.fromCharCode(parseInt(hex, 16));
|
|
192
|
+
i += 5;
|
|
193
|
+
} else {
|
|
194
|
+
out += ESCAPES[next] ?? next;
|
|
195
|
+
i += 1;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return out;
|
|
199
|
+
}
|
|
200
|
+
//#endregion
|
|
201
|
+
export { FactoryReplyStreamProcessor, REPLY_TOOL_NAME, createReplyTool, partialStringField };
|
|
202
|
+
|
|
203
|
+
//# sourceMappingURL=reply-tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reply-tool.js","names":[],"sources":["../../../src/integrations/slack/reply-tool.ts"],"sourcesContent":["import type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport { CHAT_CHANNEL_RENDER_CONTEXT_KEY } from '@mastra/core/channels';\nimport type { ChatChannelRenderContext } from '@mastra/core/channels';\nimport type { Processor, ProcessOutputStreamArgs } from '@mastra/core/processors';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport type { ChunkType } from '@mastra/core/stream';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport { readFactorySessionScope } from '../../rules/binding-context.js';\nimport type { IntegrationTools } from '../base.js';\nimport { SESSION_LINK_LABEL, sessionLink } from './channel-reply-processor.js';\nimport { NEW_CHANNEL_SESSION_KEY } from './channel-session-link-processor.js';\nimport { postTarget, resolveRenderContext } from './share-file-tool.js';\nimport type { ShareFileToolDeps } from './share-file-tool.js';\n\nexport const REPLY_TOOL_NAME = 'reply_in_thread';\n\n/** Set once the first reply of a new session carried the session link. */\nconst LINKED_KEY = 'factory__replyLinked';\n\nconst replyInput = z\n .object({\n text: z.string().trim().min(1).max(12_000).describe('The message to post, in markdown.'),\n })\n .strict();\n\n/**\n * The only way a channel run speaks when the adapter hides reply text\n * (`textDisplay: 'hidden'`). Posting becomes a choice the agent makes, so it\n * stops narrating each step into the thread and can say nothing on purpose.\n * Offered only when the Slack adapter hides reply text and the run's thread is\n * linked to Slack; with text shown the reply stream already posts and this\n * tool would double every message.\n */\nexport function createReplyTool(requestContext: RequestContext, deps: ShareFileToolDeps = {}): IntegrationTools {\n return {\n [REPLY_TOOL_NAME]: createTool({\n id: REPLY_TOOL_NAME,\n description:\n 'Post a message in this chat thread. Nothing you write outside this tool reaches the thread, so use it for anything the people there should see: a short note that you are starting a longer job, a question you need answered, and the outcome when you finish. Post when you have something new; do not repeat yourself or post that nothing changed. To stay quiet, do not call it.',\n inputSchema: replyInput,\n execute: async (input, context) => {\n const link = firstReplyLink(requestContext);\n const suffix = link ? `\\n\\n[${SESSION_LINK_LABEL}](${link})` : '';\n // FactoryReplyStreamProcessor may already be streaming this call's text\n // into the thread; finish that post rather than posting it again.\n const toolCallId = context?.agent?.toolCallId;\n const streamed = toolCallId ? replyStreams(requestContext).get(toolCallId) : undefined;\n if (toolCallId) replyStreams(requestContext).delete(toolCallId);\n if (!streamed || !(await streamed.finish(input.text, suffix))) {\n const render = await postTarget(requestContext, deps);\n await render.chatThread.post({ markdown: `${input.text}${suffix}` });\n }\n // Marked only once posted, so a failed first post still gets the link on the retry.\n if (link) requestContext.set(LINKED_KEY, true);\n return { posted: true };\n },\n }),\n };\n}\n\n/**\n * The session link the first reply of a new session ends with. With reply\n * text hidden, `FactoryChannelSessionLinkProcessor`'s text delta never\n * reaches the thread, so the tool carries the link instead.\n */\nfunction firstReplyLink(requestContext: RequestContext): string | undefined {\n if (!requestContext.get(NEW_CHANNEL_SESSION_KEY) || requestContext.get(LINKED_KEY)) return undefined;\n const controller = requestContext.get('controller') as AgentControllerRequestContext | undefined;\n if (!controller?.threadId) return undefined;\n return sessionLink({\n threadId: controller.threadId,\n sessionId: controller.sessionId,\n resourceId: controller.resourceId,\n factoryProjectId: readFactorySessionScope(requestContext)?.factoryProjectId,\n });\n}\n\n/** Live posts of `reply_in_thread` calls whose text is still streaming in, by tool call id. */\nconst STREAMS_KEY = 'factory__replyStreams';\n\ninterface ReplyStream {\n /** Show the text decoded from the args so far. */\n update(text: string): void;\n /** End the post with the validated text. Resolves false if the post failed. */\n finish(text: string, suffix: string): Promise<boolean>;\n /** End the post with whatever has been shown, for a call that never ran. */\n close(): Promise<boolean>;\n}\n\nfunction replyStreams(requestContext: RequestContext): Map<string, ReplyStream> {\n let streams = requestContext.get(STREAMS_KEY) as Map<string, ReplyStream> | undefined;\n if (!streams) {\n streams = new Map();\n requestContext.set(STREAMS_KEY, streams);\n }\n return streams;\n}\n\n/**\n * One streamed Slack post, opened on the first visible text so an abandoned\n * call never leaves an empty message behind.\n */\nfunction openReplyStream(chatThread: ChatChannelRenderContext['chatThread']): ReplyStream {\n const queue: string[] = [];\n let sent = '';\n let closed = false;\n let wake: (() => void) | undefined;\n let done: Promise<boolean> | undefined;\n\n async function* chunks(): AsyncGenerator<string> {\n while (true) {\n while (queue.length > 0) yield queue.shift()!;\n if (closed) return;\n await new Promise<void>(resolve => (wake = resolve));\n }\n }\n const push = (text: string) => {\n if (!text) return;\n queue.push(text);\n done ??= chatThread.post(chunks()).then(\n () => true,\n error => {\n console.warn('[factory] streamed reply failed; posting it whole', { error });\n return false;\n },\n );\n wake?.();\n };\n\n return {\n update(text) {\n const visible = text.trimStart();\n if (!visible.startsWith(sent)) return;\n push(visible.slice(sent.length));\n sent = visible;\n },\n async finish(text, suffix) {\n if (text.startsWith(sent)) push(text.slice(sent.length));\n if (!done) return false;\n push(suffix);\n closed = true;\n wake?.();\n return done;\n },\n async close() {\n closed = true;\n wake?.();\n return done ?? false;\n },\n };\n}\n\n/**\n * Streams a `reply_in_thread` call's `text` into the thread while the model is\n * still writing it, so a long reply appears as it is written instead of all at\n * once when the call completes. The tool finishes the post with the validated\n * text. Runs whose adapter doesn't stream, or that show reply text, are left\n * alone.\n */\nexport class FactoryReplyStreamProcessor implements Processor {\n readonly id = 'factory-reply-stream';\n\n constructor(private readonly deps: ShareFileToolDeps = {}) {}\n\n async processOutputStream({ part, state, requestContext }: ProcessOutputStreamArgs): Promise<ChunkType> {\n if (!requestContext) return part;\n const args = (state.args ??= {}) as Record<string, string>;\n if (part.type === 'tool-call-input-streaming-start' && part.payload.toolName === REPLY_TOOL_NAME) {\n // Resolve the thread the same way the tool does, so a run started from\n // the web UI or woken by a signal streams too, not only a Slack-started one.\n const render = await resolveRenderContext(requestContext, this.deps);\n // A static adapter (`streaming: false`) posts each reply whole, so the tool does too.\n if (render?.textDisplay !== 'hidden' || !render.streaming?.enabled) return part;\n args[part.payload.toolCallId] = '';\n replyStreams(requestContext).set(part.payload.toolCallId, openReplyStream(render.chatThread));\n } else if (part.type === 'tool-call-delta' && part.payload.toolCallId in args) {\n const json = (args[part.payload.toolCallId] += part.payload.argsTextDelta);\n const text = partialStringField(json, 'text');\n if (text !== undefined) replyStreams(requestContext).get(part.payload.toolCallId)?.update(text);\n } else if ((part.type === 'tool-error' || part.type === 'tool-result') && part.payload.toolCallId in args) {\n // A call that failed validation never reaches `execute`; a successful one already finished its post.\n delete args[part.payload.toolCallId];\n await closeStream(requestContext, part.payload.toolCallId);\n } else if (RUN_END.has(part.type)) {\n for (const toolCallId of Object.keys(args)) await closeStream(requestContext, toolCallId);\n state.args = {};\n }\n return part;\n }\n}\n\n/** Chunks after which no `reply_in_thread` call of this run will still execute. */\nconst RUN_END = new Set<string>(['finish', 'abort', 'error', 'tripwire']);\n\n/**\n * Ends a streamed post whose call will never reach `execute`, keeping the text\n * shown so far, so Slack isn't left with a message that never finishes.\n */\nasync function closeStream(requestContext: RequestContext, toolCallId: string): Promise<void> {\n const streams = replyStreams(requestContext);\n const stream = streams.get(toolCallId);\n if (!stream) return;\n streams.delete(toolCallId);\n await stream.close();\n}\n\nconst ESCAPES: Record<string, string> = { '\"': '\"', '\\\\': '\\\\', '/': '/', b: '\\b', f: '\\f', n: '\\n', r: '\\r', t: '\\t' };\n\n/** The decoded value of a top-level string field in JSON that may still be cut off mid-string. */\nexport function partialStringField(json: string, field: string): string | undefined {\n const start = new RegExp(`\"${field}\"\\\\s*:\\\\s*\"`).exec(json);\n if (!start) return undefined;\n let out = '';\n for (let i = start.index + start[0].length; i < json.length; i++) {\n const ch = json[i]!;\n if (ch === '\"') return out;\n if (ch !== '\\\\') {\n out += ch;\n continue;\n }\n const next = json[i + 1];\n if (next === undefined) return out;\n if (next === 'u') {\n const hex = json.slice(i + 2, i + 6);\n if (hex.length < 4) return out;\n out += String.fromCharCode(parseInt(hex, 16));\n i += 5;\n } else {\n out += ESCAPES[next] ?? next;\n i += 1;\n }\n }\n return out;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { AgentChannels, ChatChannelRenderContext } from '@mastra/core/channels';
|
|
2
|
+
import type { RequestContext } from '@mastra/core/request-context';
|
|
3
|
+
import type { IntegrationTools } from '../base.js';
|
|
4
|
+
/** Slack rejects larger uploads well before this; keep the sandbox read bounded too. */
|
|
5
|
+
export declare const SHARE_FILE_MAX_BYTES: number;
|
|
6
|
+
export interface ShareFileToolDeps {
|
|
7
|
+
/**
|
|
8
|
+
* The channels attached to the controller, used to rebuild the chat thread
|
|
9
|
+
* for runs that did not start from a platform message. Omit when the caller
|
|
10
|
+
* has no controller; the tool is then offered only on inbound-webhook runs.
|
|
11
|
+
*/
|
|
12
|
+
channels?: () => AgentChannels | null | undefined;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The chat thread this run replies into, if any.
|
|
16
|
+
*
|
|
17
|
+
* Inbound platform messages stash the render context on `requestContext`.
|
|
18
|
+
* A run woken by a signal (a subagent finishing, a scheduled fire) has no
|
|
19
|
+
* such entry even though its thread is still channel-backed — and that is
|
|
20
|
+
* exactly the run that ends a long job with a file to hand back. Rebuild the
|
|
21
|
+
* context from the thread's persisted channel coordinates the same way
|
|
22
|
+
* `ChatChannelOutputProcessor` does, so the tools post on both kinds of run.
|
|
23
|
+
* A forked subagent's thread carries no channel metadata, so forks get
|
|
24
|
+
* nothing here and never post to the platform.
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolveRenderContext(requestContext: RequestContext, deps: ShareFileToolDeps): Promise<ChatChannelRenderContext | undefined>;
|
|
27
|
+
/**
|
|
28
|
+
* Whether this run's thread is a Slack thread, so the channel tools have
|
|
29
|
+
* somewhere to post. A run started from Slack says so on `requestContext`; a
|
|
30
|
+
* web prompt or a woken run on a Slack-linked session costs one thread read.
|
|
31
|
+
* Plain web sessions get `false` and no channel tools.
|
|
32
|
+
*/
|
|
33
|
+
export declare function isSlackThread(requestContext: RequestContext, deps: ShareFileToolDeps): Promise<boolean>;
|
|
34
|
+
/** The thread a channel tool posts into, resolved when the tool runs. */
|
|
35
|
+
export declare function postTarget(requestContext: RequestContext, deps: ShareFileToolDeps): Promise<ChatChannelRenderContext>;
|
|
36
|
+
/**
|
|
37
|
+
* Lets a channel-triggered run hand a workspace file back to the thread it
|
|
38
|
+
* came from. The agent's reply stream can only carry text, so a report it
|
|
39
|
+
* generated in the sandbox would otherwise be described by path and left
|
|
40
|
+
* there. Offered only on runs whose thread is linked to a chat thread (see
|
|
41
|
+
* `isSlackThread`); the file is read through the session's workspace
|
|
42
|
+
* filesystem so sandbox confinement still applies.
|
|
43
|
+
*/
|
|
44
|
+
export declare function createShareFileTool(requestContext: RequestContext, deps?: ShareFileToolDeps): IntegrationTools;
|
|
45
|
+
//# sourceMappingURL=share-file-tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"share-file-tool.d.ts","sourceRoot":"","sources":["../../../src/integrations/slack/share-file-tool.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACrF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAInE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,wFAAwF;AACxF,eAAO,MAAM,oBAAoB,QAAmB,CAAC;AAErD,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,aAAa,GAAG,IAAI,GAAG,SAAS,CAAC;CACnD;AAmBD;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,CACxC,cAAc,EAAE,cAAc,EAC9B,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,wBAAwB,GAAG,SAAS,CAAC,CAO/C;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,cAAc,EAAE,cAAc,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,CAQ7G;AAED,yEAAyE;AACzE,wBAAsB,UAAU,CAC9B,cAAc,EAAE,cAAc,EAC9B,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,wBAAwB,CAAC,CAInC;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,cAAc,EAAE,cAAc,EAAE,IAAI,GAAE,iBAAsB,GAAG,gBAAgB,CA4ClH"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { CHAT_CHANNEL_RENDER_CONTEXT_KEY } from "@mastra/core/channels";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createTool } from "@mastra/core/tools";
|
|
4
|
+
//#region src/integrations/slack/share-file-tool.ts
|
|
5
|
+
/** Slack rejects larger uploads well before this; keep the sandbox read bounded too. */
|
|
6
|
+
const SHARE_FILE_MAX_BYTES = 52428800;
|
|
7
|
+
/**
|
|
8
|
+
* Uploads need a scope (`files:write` on Slack) that an app created before
|
|
9
|
+
* this tool existed will not have. `@slack/web-api` reports that as a
|
|
10
|
+
* platform error with `data.error === 'missing_scope'` and the scope in
|
|
11
|
+
* `data.needed`; say so plainly instead of surfacing the raw API error, so
|
|
12
|
+
* the model tells the user what to fix rather than retrying.
|
|
13
|
+
*/
|
|
14
|
+
function missingScopeError(error, platform) {
|
|
15
|
+
if (!error || typeof error !== "object") return void 0;
|
|
16
|
+
const data = error.data;
|
|
17
|
+
if (data?.error !== "missing_scope") return void 0;
|
|
18
|
+
const needed = typeof data.needed === "string" && data.needed ? data.needed : "files:write";
|
|
19
|
+
return /* @__PURE__ */ new Error(`The ${platform} app is missing the \`${needed}\` scope, so it cannot upload files. Add the scope to the app and reinstall it, then try again.`);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The chat thread this run replies into, if any.
|
|
23
|
+
*
|
|
24
|
+
* Inbound platform messages stash the render context on `requestContext`.
|
|
25
|
+
* A run woken by a signal (a subagent finishing, a scheduled fire) has no
|
|
26
|
+
* such entry even though its thread is still channel-backed — and that is
|
|
27
|
+
* exactly the run that ends a long job with a file to hand back. Rebuild the
|
|
28
|
+
* context from the thread's persisted channel coordinates the same way
|
|
29
|
+
* `ChatChannelOutputProcessor` does, so the tools post on both kinds of run.
|
|
30
|
+
* A forked subagent's thread carries no channel metadata, so forks get
|
|
31
|
+
* nothing here and never post to the platform.
|
|
32
|
+
*/
|
|
33
|
+
async function resolveRenderContext(requestContext, deps) {
|
|
34
|
+
const stashed = requestContext.get(CHAT_CHANNEL_RENDER_CONTEXT_KEY);
|
|
35
|
+
if (stashed) return stashed;
|
|
36
|
+
const threadId = requestContext.get("controller")?.threadId;
|
|
37
|
+
if (!threadId) return void 0;
|
|
38
|
+
return await deps.channels?.()?.buildRenderContextForThread(threadId) ?? void 0;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Whether this run's thread is a Slack thread, so the channel tools have
|
|
42
|
+
* somewhere to post. A run started from Slack says so on `requestContext`; a
|
|
43
|
+
* web prompt or a woken run on a Slack-linked session costs one thread read.
|
|
44
|
+
* Plain web sessions get `false` and no channel tools.
|
|
45
|
+
*/
|
|
46
|
+
async function isSlackThread(requestContext, deps) {
|
|
47
|
+
const channel = requestContext.get("channel");
|
|
48
|
+
if (channel) return channel.platform === "slack";
|
|
49
|
+
const threadId = requestContext.get("controller")?.threadId;
|
|
50
|
+
if (!threadId) return false;
|
|
51
|
+
return (await deps.channels?.()?.buildChannelContextForThread(threadId))?.channel.platform === "slack";
|
|
52
|
+
}
|
|
53
|
+
/** The thread a channel tool posts into, resolved when the tool runs. */
|
|
54
|
+
async function postTarget(requestContext, deps) {
|
|
55
|
+
const render = await resolveRenderContext(requestContext, deps);
|
|
56
|
+
if (!render) throw new Error("This session is no longer linked to a chat thread.");
|
|
57
|
+
return render;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Lets a channel-triggered run hand a workspace file back to the thread it
|
|
61
|
+
* came from. The agent's reply stream can only carry text, so a report it
|
|
62
|
+
* generated in the sandbox would otherwise be described by path and left
|
|
63
|
+
* there. Offered only on runs whose thread is linked to a chat thread (see
|
|
64
|
+
* `isSlackThread`); the file is read through the session's workspace
|
|
65
|
+
* filesystem so sandbox confinement still applies.
|
|
66
|
+
*/
|
|
67
|
+
function createShareFileTool(requestContext, deps = {}) {
|
|
68
|
+
return { attach_file: createTool({
|
|
69
|
+
id: "attach_file",
|
|
70
|
+
description: "Upload a file from the workspace into this chat thread as an attachment. This is the only way the person you are talking to can receive a file you produced (a report, PDF, export, screenshot, image); they cannot open workspace paths. Call it whenever you have a file to hand over, then describe it briefly in your reply. The attachment posts on its own.",
|
|
71
|
+
inputSchema: z.object({ path: z.string().min(1).describe("Absolute path of the file inside the workspace") }),
|
|
72
|
+
execute: async ({ path }, context) => {
|
|
73
|
+
const fs = (requestContext.get("controller")?.workspace ?? context?.workspace)?.filesystem;
|
|
74
|
+
if (!fs) throw new Error("No workspace filesystem is attached to this session.");
|
|
75
|
+
const stat = await fs.stat(path);
|
|
76
|
+
if (stat.type === "directory") throw new Error(`${path} is a directory; attach a single file.`);
|
|
77
|
+
if (stat.size > 52428800) throw new Error(`${path} is ${stat.size} bytes; the limit is ${SHARE_FILE_MAX_BYTES} bytes.`);
|
|
78
|
+
const raw = await fs.readFile(path);
|
|
79
|
+
const data = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
|
|
80
|
+
if (data.length > 52428800) throw new Error(`${path} is ${data.length} bytes; the limit is ${SHARE_FILE_MAX_BYTES} bytes.`);
|
|
81
|
+
const filename = path.split("/").pop() || "file";
|
|
82
|
+
const render = await postTarget(requestContext, deps);
|
|
83
|
+
try {
|
|
84
|
+
await render.chatThread.post({
|
|
85
|
+
markdown: "",
|
|
86
|
+
files: [{
|
|
87
|
+
data,
|
|
88
|
+
filename,
|
|
89
|
+
...stat.mimeType ? { mimeType: stat.mimeType } : {}
|
|
90
|
+
}]
|
|
91
|
+
});
|
|
92
|
+
} catch (error) {
|
|
93
|
+
throw missingScopeError(error, render.platform) ?? error;
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
attached: true,
|
|
97
|
+
filename,
|
|
98
|
+
bytes: data.length
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}) };
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
export { SHARE_FILE_MAX_BYTES, createShareFileTool, isSlackThread, postTarget, resolveRenderContext };
|
|
105
|
+
|
|
106
|
+
//# sourceMappingURL=share-file-tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"share-file-tool.js","names":[],"sources":["../../../src/integrations/slack/share-file-tool.ts"],"sourcesContent":["import type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport { CHAT_CHANNEL_RENDER_CONTEXT_KEY } from '@mastra/core/channels';\nimport type { AgentChannels, ChatChannelRenderContext } from '@mastra/core/channels';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport type { IntegrationTools } from '../base.js';\n\n/** Slack rejects larger uploads well before this; keep the sandbox read bounded too. */\nexport const SHARE_FILE_MAX_BYTES = 50 * 1024 * 1024;\n\nexport interface ShareFileToolDeps {\n /**\n * The channels attached to the controller, used to rebuild the chat thread\n * for runs that did not start from a platform message. Omit when the caller\n * has no controller; the tool is then offered only on inbound-webhook runs.\n */\n channels?: () => AgentChannels | null | undefined;\n}\n\n/**\n * Uploads need a scope (`files:write` on Slack) that an app created before\n * this tool existed will not have. `@slack/web-api` reports that as a\n * platform error with `data.error === 'missing_scope'` and the scope in\n * `data.needed`; say so plainly instead of surfacing the raw API error, so\n * the model tells the user what to fix rather than retrying.\n */\nfunction missingScopeError(error: unknown, platform: string): Error | undefined {\n if (!error || typeof error !== 'object') return undefined;\n const data = (error as { data?: { error?: unknown; needed?: unknown } }).data;\n if (data?.error !== 'missing_scope') return undefined;\n const needed = typeof data.needed === 'string' && data.needed ? data.needed : 'files:write';\n return new Error(\n `The ${platform} app is missing the \\`${needed}\\` scope, so it cannot upload files. Add the scope to the app and reinstall it, then try again.`,\n );\n}\n\n/**\n * The chat thread this run replies into, if any.\n *\n * Inbound platform messages stash the render context on `requestContext`.\n * A run woken by a signal (a subagent finishing, a scheduled fire) has no\n * such entry even though its thread is still channel-backed — and that is\n * exactly the run that ends a long job with a file to hand back. Rebuild the\n * context from the thread's persisted channel coordinates the same way\n * `ChatChannelOutputProcessor` does, so the tools post on both kinds of run.\n * A forked subagent's thread carries no channel metadata, so forks get\n * nothing here and never post to the platform.\n */\nexport async function resolveRenderContext(\n requestContext: RequestContext,\n deps: ShareFileToolDeps,\n): Promise<ChatChannelRenderContext | undefined> {\n const stashed = requestContext.get(CHAT_CHANNEL_RENDER_CONTEXT_KEY) as ChatChannelRenderContext | undefined;\n if (stashed) return stashed;\n const controller = requestContext.get('controller') as AgentControllerRequestContext | undefined;\n const threadId = controller?.threadId;\n if (!threadId) return undefined;\n return (await deps.channels?.()?.buildRenderContextForThread(threadId)) ?? undefined;\n}\n\n/**\n * Whether this run's thread is a Slack thread, so the channel tools have\n * somewhere to post. A run started from Slack says so on `requestContext`; a\n * web prompt or a woken run on a Slack-linked session costs one thread read.\n * Plain web sessions get `false` and no channel tools.\n */\nexport async function isSlackThread(requestContext: RequestContext, deps: ShareFileToolDeps): Promise<boolean> {\n const channel = requestContext.get('channel') as { platform?: string } | undefined;\n if (channel) return channel.platform === 'slack';\n const controller = requestContext.get('controller') as AgentControllerRequestContext | undefined;\n const threadId = controller?.threadId;\n if (!threadId) return false;\n const linked = await deps.channels?.()?.buildChannelContextForThread(threadId);\n return linked?.channel.platform === 'slack';\n}\n\n/** The thread a channel tool posts into, resolved when the tool runs. */\nexport async function postTarget(\n requestContext: RequestContext,\n deps: ShareFileToolDeps,\n): Promise<ChatChannelRenderContext> {\n const render = await resolveRenderContext(requestContext, deps);\n if (!render) throw new Error('This session is no longer linked to a chat thread.');\n return render;\n}\n\n/**\n * Lets a channel-triggered run hand a workspace file back to the thread it\n * came from. The agent's reply stream can only carry text, so a report it\n * generated in the sandbox would otherwise be described by path and left\n * there. Offered only on runs whose thread is linked to a chat thread (see\n * `isSlackThread`); the file is read through the session's workspace\n * filesystem so sandbox confinement still applies.\n */\nexport function createShareFileTool(requestContext: RequestContext, deps: ShareFileToolDeps = {}): IntegrationTools {\n return {\n attach_file: createTool({\n id: 'attach_file',\n description:\n 'Upload a file from the workspace into this chat thread as an attachment. This is the only way the person you are talking to can receive a file you produced (a report, PDF, export, screenshot, image); they cannot open workspace paths. Call it whenever you have a file to hand over, then describe it briefly in your reply. The attachment posts on its own.',\n inputSchema: z.object({\n path: z.string().min(1).describe('Absolute path of the file inside the workspace'),\n }),\n execute: async ({ path }, context) => {\n const controller = requestContext.get('controller') as AgentControllerRequestContext | undefined;\n const workspace = controller?.workspace ?? context?.workspace;\n const fs = workspace?.filesystem;\n if (!fs) throw new Error('No workspace filesystem is attached to this session.');\n const stat = await fs.stat(path);\n if (stat.type === 'directory') throw new Error(`${path} is a directory; attach a single file.`);\n // The stat check keeps us from reading something enormous; the check on\n // the bytes we actually hold is the one that bounds the upload, since the\n // file can change between the two calls.\n if (stat.size > SHARE_FILE_MAX_BYTES) {\n throw new Error(`${path} is ${stat.size} bytes; the limit is ${SHARE_FILE_MAX_BYTES} bytes.`);\n }\n const raw = await fs.readFile(path);\n const data = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);\n if (data.length > SHARE_FILE_MAX_BYTES) {\n throw new Error(`${path} is ${data.length} bytes; the limit is ${SHARE_FILE_MAX_BYTES} bytes.`);\n }\n const filename = path.split('/').pop() || 'file';\n const render = await postTarget(requestContext, deps);\n // File only. The Slack adapter posts any non-empty text as a second\n // message after the upload, so a comment would land as its own bubble\n // between the reply and the file; an empty body returns after the upload.\n try {\n await render.chatThread.post({\n markdown: '',\n files: [{ data, filename, ...(stat.mimeType ? { mimeType: stat.mimeType } : {}) }],\n });\n } catch (error) {\n throw missingScopeError(error, render.platform) ?? error;\n }\n return { attached: true, filename, bytes: data.length };\n },\n }),\n };\n}\n"],"mappings":";;;;;AAUA,MAAa,uBAAuB;;;;;;;;AAkBpC,SAAS,kBAAkB,OAAgB,UAAqC;CAC9E,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO,KAAA;CAChD,MAAM,OAAQ,MAA2D;CACzE,IAAI,MAAM,UAAU,iBAAiB,OAAO,KAAA;CAC5C,MAAM,SAAS,OAAO,KAAK,WAAW,YAAY,KAAK,SAAS,KAAK,SAAS;CAC9E,uBAAO,IAAI,MACT,OAAO,SAAS,wBAAwB,OAAO,gGACjD;AACF;;;;;;;;;;;;;AAcA,eAAsB,qBACpB,gBACA,MAC+C;CAC/C,MAAM,UAAU,eAAe,IAAI,+BAA+B;CAClE,IAAI,SAAS,OAAO;CAEpB,MAAM,WADa,eAAe,IAAI,YACZ,CAAC,EAAE;CAC7B,IAAI,CAAC,UAAU,OAAO,KAAA;CACtB,OAAQ,MAAM,KAAK,WAAW,CAAC,EAAE,4BAA4B,QAAQ,KAAM,KAAA;AAC7E;;;;;;;AAQA,eAAsB,cAAc,gBAAgC,MAA2C;CAC7G,MAAM,UAAU,eAAe,IAAI,SAAS;CAC5C,IAAI,SAAS,OAAO,QAAQ,aAAa;CAEzC,MAAM,WADa,eAAe,IAAI,YACZ,CAAC,EAAE;CAC7B,IAAI,CAAC,UAAU,OAAO;CAEtB,QAAO,MADc,KAAK,WAAW,CAAC,EAAE,6BAA6B,QAAQ,EAAA,EAC9D,QAAQ,aAAa;AACtC;;AAGA,eAAsB,WACpB,gBACA,MACmC;CACnC,MAAM,SAAS,MAAM,qBAAqB,gBAAgB,IAAI;CAC9D,IAAI,CAAC,QAAQ,MAAM,IAAI,MAAM,oDAAoD;CACjF,OAAO;AACT;;;;;;;;;AAUA,SAAgB,oBAAoB,gBAAgC,OAA0B,CAAC,GAAqB;CAClH,OAAO,EACL,aAAa,WAAW;EACtB,IAAI;EACJ,aACE;EACF,aAAa,EAAE,OAAO,EACpB,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,gDAAgD,EACnF,CAAC;EACD,SAAS,OAAO,EAAE,QAAQ,YAAY;GAGpC,MAAM,MAFa,eAAe,IAAI,YACX,CAAC,EAAE,aAAa,SAAS,UAAA,EAC9B;GACtB,IAAI,CAAC,IAAI,MAAM,IAAI,MAAM,sDAAsD;GAC/E,MAAM,OAAO,MAAM,GAAG,KAAK,IAAI;GAC/B,IAAI,KAAK,SAAS,aAAa,MAAM,IAAI,MAAM,GAAG,KAAK,uCAAuC;GAI9F,IAAI,KAAK,OAAA,UACP,MAAM,IAAI,MAAM,GAAG,KAAK,MAAM,KAAK,KAAK,uBAAuB,qBAAqB,QAAQ;GAE9F,MAAM,MAAM,MAAM,GAAG,SAAS,IAAI;GAClC,MAAM,OAAO,OAAO,SAAS,GAAG,IAAI,MAAM,OAAO,KAAK,GAAG;GACzD,IAAI,KAAK,SAAA,UACP,MAAM,IAAI,MAAM,GAAG,KAAK,MAAM,KAAK,OAAO,uBAAuB,qBAAqB,QAAQ;GAEhG,MAAM,WAAW,KAAK,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK;GAC1C,MAAM,SAAS,MAAM,WAAW,gBAAgB,IAAI;GAIpD,IAAI;IACF,MAAM,OAAO,WAAW,KAAK;KAC3B,UAAU;KACV,OAAO,CAAC;MAAE;MAAM;MAAU,GAAI,KAAK,WAAW,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;KAAG,CAAC;IACnF,CAAC;GACH,SAAS,OAAO;IACd,MAAM,kBAAkB,OAAO,OAAO,QAAQ,KAAK;GACrD;GACA,OAAO;IAAE,UAAU;IAAM;IAAU,OAAO,KAAK;GAAO;EACxD;CACF,CAAC,EACH;AACF"}
|
|
@@ -121,9 +121,20 @@ export declare function resolveFactoryForLink({ thread, link, key, accountLinks,
|
|
|
121
121
|
accountLinks: ChannelIdentityStorage;
|
|
122
122
|
projects?: FactoryProjectsStorage;
|
|
123
123
|
}): Promise<FactoryRouteResult>;
|
|
124
|
+
/**
|
|
125
|
+
* Session ids of repo-backed Slack threads, keyed by the message that started
|
|
126
|
+
* them. The resourceId resolver records the session it found or created; the
|
|
127
|
+
* thread-id resolver, which core runs next for the same message, names the new
|
|
128
|
+
* thread after it.
|
|
129
|
+
*/
|
|
130
|
+
export type SlackStartedSessions = WeakMap<object, string>;
|
|
124
131
|
/**
|
|
125
132
|
* Resolve the resourceId for a NEW Slack channel thread. A linked sender whose
|
|
126
|
-
* factory has a repository gets a Factory
|
|
133
|
+
* factory has a repository gets a Factory session. A channel thread belongs to
|
|
134
|
+
* the Factory: its resourceId is the Factory id, so the thread shares the
|
|
135
|
+
* Factory's memory. A DM is private to the sender, so it runs under the
|
|
136
|
+
* sender's user id and shares memory with their own sessions. The session id is recorded in `startedSessions` for the
|
|
137
|
+
* thread-id resolver. The controller
|
|
127
138
|
* session then materializes the repo sandbox via the factory's dynamic
|
|
128
139
|
* workspace (clone + PAT), the session shows up in the web Sessions list, and
|
|
129
140
|
* View Session deep-links land on the normal workspace route.
|
|
@@ -147,17 +158,17 @@ export declare function resolveFactoryForLink({ thread, link, key, accountLinks,
|
|
|
147
158
|
* Pure lookups only — cards for unlinked/unrouted senders are the dispatch
|
|
148
159
|
* gate's job; this hook must never post.
|
|
149
160
|
*/
|
|
150
|
-
export declare function createChannelResourceIdResolver(deps: SlackChannelDeps): ResolveResourceId;
|
|
161
|
+
export declare function createChannelResourceIdResolver(deps: SlackChannelDeps, startedSessions?: SlackStartedSessions): ResolveResourceId;
|
|
151
162
|
/**
|
|
152
163
|
* Thread id for a NEW Slack channel thread. Repo-backed threads take the
|
|
153
|
-
*
|
|
164
|
+
* session id AS their thread id, matching the web convention
|
|
154
165
|
* (FactoryStartCoordinator seeds threads with threadId = sessionId) so
|
|
155
166
|
* `/workspaces/{sessionId}/threads/{sessionId}` resolves Slack-created
|
|
156
|
-
* sessions exactly like web-created ones — no `?
|
|
167
|
+
* sessions exactly like web-created ones — no `?sessionId=` override needed.
|
|
157
168
|
* Chat-only threads keep the default random id: their `channel:...`
|
|
158
169
|
* resourceId is a memory key, not a unique thread id.
|
|
159
170
|
*/
|
|
160
|
-
export declare
|
|
171
|
+
export declare function createChannelThreadIdResolver(startedSessions: SlackStartedSessions): ResolveThreadId;
|
|
161
172
|
/** Create channel sessions with their Factory ownership present in initial controller state. */
|
|
162
173
|
export declare function createChannelSessionResolver(deps: SlackChannelDeps): ChannelSessionResolve;
|
|
163
174
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../../src/integrations/slack/slack.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,cAAc,EAEd,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,EAChB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../../src/integrations/slack/slack.ts"],"names":[],"mappings":"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"}
|