@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
|
@@ -5,6 +5,7 @@ import { effectiveModelScope } from "../storage/domains/projects/base.js";
|
|
|
5
5
|
import { ensureFactorySourceSession, hydrateFactorySession, resolveFactorySessionModel } from "../session/factory-session.js";
|
|
6
6
|
import { resolveSessionThread } from "./deliver.js";
|
|
7
7
|
import { findWorkItem, workItemNotFound, workItemNumber, workItemRefSchema } from "./read-tools.js";
|
|
8
|
+
import { sessionModel } from "./side-sessions.js";
|
|
8
9
|
import { resolveFactoryModelPolicy } from "../session/credential-policy.js";
|
|
9
10
|
import { buildModelCatalog, listSessionScopedCredentials } from "../routes/config.js";
|
|
10
11
|
import { USER_SESSION_BRANCH_PREFIX } from "../routes/source-control-sessions.js";
|
|
@@ -38,7 +39,7 @@ function titleFrom(prompt) {
|
|
|
38
39
|
/** Tools that start sessions on the caller's behalf, or switch their models. User sessions only. */
|
|
39
40
|
function createFactorySessionTools(scope, deps) {
|
|
40
41
|
/** The models a session can switch to, resolved from the target session's owner and project policy. */
|
|
41
|
-
async function sessionModelCatalog(controller, sessionId, threadId) {
|
|
42
|
+
async function sessionModelCatalog(controller, sessionId, threadId, resourceId) {
|
|
42
43
|
const models = await controller.listAvailableModels();
|
|
43
44
|
const known = new Set(models.flatMap((model) => typeof model.id === "string" ? [model.id] : []));
|
|
44
45
|
const tenantCatalog = deps.sessionModel.tenantCatalog;
|
|
@@ -53,7 +54,8 @@ function createFactorySessionTools(scope, deps) {
|
|
|
53
54
|
organizationId: scope.orgId
|
|
54
55
|
});
|
|
55
56
|
requestContext.set("controller", {
|
|
56
|
-
|
|
57
|
+
sessionId,
|
|
58
|
+
resourceId,
|
|
57
59
|
threadId
|
|
58
60
|
});
|
|
59
61
|
const policy = await resolveFactoryModelPolicy(requestContext, {
|
|
@@ -86,12 +88,16 @@ function createFactorySessionTools(scope, deps) {
|
|
|
86
88
|
scopeLabel: modelScope === "factory" ? "Factory" : "session owner"
|
|
87
89
|
};
|
|
88
90
|
}
|
|
91
|
+
function unrunnableModelError(modelId, catalog) {
|
|
92
|
+
const options = catalog.usable.length ? ` Models available to it: ${catalog.usable.slice(0, LISTED_MODELS).join(", ")}${catalog.usable.length > LISTED_MODELS ? ", …" : ""}.` : " No models are available to it; connect a provider credential first.";
|
|
93
|
+
return /* @__PURE__ */ new Error(catalog.known.has(modelId) ? `Model ${modelId} exists, but this session's credential scope (${catalog.scopeLabel}) has no credential for its provider.${options}` : `Unknown model ${modelId}.${options}`);
|
|
94
|
+
}
|
|
89
95
|
const switchModel = createTool({
|
|
90
96
|
id: "factory_switch_session_model",
|
|
91
|
-
description: "
|
|
97
|
+
description: "Look up or switch the model of a session you own in this Factory (side, work item, or review session). When someone describes a model by purpose, family, or preference rather than an exact ID, call with sessionId alone first to list models the target session can actually use. Interpret their intent against that list, then call with the chosen exact modelId to switch; do not use their words as a guessed model ID. A running session switches immediately; a stopped one uses the model on its next run. User-scoped sessions use their owner credentials; Factory-scoped sessions use Factory credentials. Use a session ID from the Factory session or work item tools.",
|
|
92
98
|
inputSchema: z.object({
|
|
93
99
|
sessionId: z.string().trim().min(1).max(200),
|
|
94
|
-
modelId: z.string().trim().min(1).max(200)
|
|
100
|
+
modelId: z.string().trim().min(1).max(200).optional().describe("Omit to list usable models without switching.")
|
|
95
101
|
}).strict(),
|
|
96
102
|
execute: async ({ sessionId, modelId }) => {
|
|
97
103
|
const row = await deps.sessions.getBySessionId(sessionId);
|
|
@@ -106,13 +112,15 @@ function createFactorySessionTools(scope, deps) {
|
|
|
106
112
|
sessionId
|
|
107
113
|
});
|
|
108
114
|
if (!threadId) throw new Error("Session has no conversation thread yet.");
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
const resourceId = (await messaging.controller.queryThreadById({ threadId }))?.resourceId ?? sessionId;
|
|
116
|
+
const catalog = await sessionModelCatalog(messaging.controller, sessionId, threadId, resourceId);
|
|
117
|
+
if (modelId === void 0) return {
|
|
118
|
+
sessionId,
|
|
119
|
+
models: catalog.usable
|
|
120
|
+
};
|
|
121
|
+
if (!catalog.usable.includes(modelId)) throw unrunnableModelError(modelId, catalog);
|
|
114
122
|
const { live } = await messaging.controller.setThreadModel({
|
|
115
|
-
resourceId
|
|
123
|
+
resourceId,
|
|
116
124
|
threadId,
|
|
117
125
|
modelId
|
|
118
126
|
});
|
|
@@ -128,6 +136,7 @@ function createFactorySessionTools(scope, deps) {
|
|
|
128
136
|
id: "factory_start_session",
|
|
129
137
|
description: [
|
|
130
138
|
"Start a side session in this Factory with a prompt. It runs on its own sandbox and branch as you, and reports back to this session with `factory-session` notifications.",
|
|
139
|
+
"A side session uses this session’s model. Pass `model` to start it on another model the session credentials can run.",
|
|
131
140
|
"Use it for self-contained investigations; when the result should land as a pull request, file a work item instead.",
|
|
132
141
|
"With `workItem` and `role` it starts that role on the work item instead, without moving the card, and this session follows the item.",
|
|
133
142
|
"A role that already has a session on the item (current or past) can't be started again from here: message that session with factory_signal_session, or ask the person to use the board."
|
|
@@ -135,13 +144,14 @@ function createFactorySessionTools(scope, deps) {
|
|
|
135
144
|
inputSchema: z.object({
|
|
136
145
|
prompt: z.string().trim().min(1).max(8e3).describe("The first message the new session receives."),
|
|
137
146
|
title: z.string().trim().min(1).max(TITLE_LENGTH).optional(),
|
|
147
|
+
model: z.string().trim().min(1).max(200).optional().describe("Model id for a side session. Omit it to inherit this session’s model. The session credentials must be able to run it."),
|
|
138
148
|
workItem: workItemRefSchema.optional(),
|
|
139
149
|
role: z.string().trim().min(1).max(64).optional()
|
|
140
150
|
}).strict().refine((input) => input.role === void 0 || input.workItem !== void 0, {
|
|
141
151
|
message: "Pass workItem together with role.",
|
|
142
152
|
path: ["role"]
|
|
143
153
|
}),
|
|
144
|
-
execute: async ({ prompt, title, workItem, role }) => {
|
|
154
|
+
execute: async ({ prompt, title, model, workItem, role }) => {
|
|
145
155
|
if (workItem !== void 0) {
|
|
146
156
|
if (!scope.interactive) throw new Error("Starting a role on a work item needs a person in this turn; start a side session, or ask the person to use the board.");
|
|
147
157
|
return startWorkItemRole({
|
|
@@ -156,6 +166,17 @@ function createFactorySessionTools(scope, deps) {
|
|
|
156
166
|
if (!parent || !sourceControl) throw new Error("This session has no Factory session row to start from.");
|
|
157
167
|
const messaging = await deps.messaging();
|
|
158
168
|
if (!messaging) throw new Error("Starting sessions is not available on this server.");
|
|
169
|
+
const parentThreadId = await resolveSessionThread({
|
|
170
|
+
controller: messaging.controller,
|
|
171
|
+
sessions: deps.sessions,
|
|
172
|
+
workItems: deps.workItems,
|
|
173
|
+
sessionId: scope.sessionId
|
|
174
|
+
});
|
|
175
|
+
const parentResourceId = parentThreadId ? (await messaging.controller.queryThreadById({ threadId: parentThreadId }))?.resourceId ?? scope.sessionId : scope.sessionId;
|
|
176
|
+
const catalog = await sessionModelCatalog(messaging.controller, scope.sessionId, parentThreadId ?? scope.threadId, parentResourceId);
|
|
177
|
+
const inherited = model ? null : await sessionModel(messaging.controller, deps.sessions, deps.workItems, scope.sessionId);
|
|
178
|
+
const requestedModel = model ?? (inherited && catalog.usable.includes(inherited) ? inherited : void 0);
|
|
179
|
+
if (requestedModel && !catalog.usable.includes(requestedModel)) throw unrunnableModelError(requestedModel, catalog);
|
|
159
180
|
const sessionId = globalThis.crypto.randomUUID();
|
|
160
181
|
const ensured = await ensureFactorySourceSession({
|
|
161
182
|
sourceControl,
|
|
@@ -171,7 +192,7 @@ function createFactorySessionTools(scope, deps) {
|
|
|
171
192
|
threadId: scope.threadId
|
|
172
193
|
}
|
|
173
194
|
});
|
|
174
|
-
const
|
|
195
|
+
const resolvedModel = await resolveFactorySessionModel({
|
|
175
196
|
...deps.sessionModel,
|
|
176
197
|
factoryProjectId: scope.factoryProjectId,
|
|
177
198
|
orgId: scope.orgId,
|
|
@@ -190,7 +211,7 @@ function createFactorySessionTools(scope, deps) {
|
|
|
190
211
|
const session = await messaging.controller.createSession({
|
|
191
212
|
id: ensured.sessionId,
|
|
192
213
|
ownerId: scope.userId,
|
|
193
|
-
resourceId:
|
|
214
|
+
resourceId: parent.visibility === "private" ? scope.userId : scope.factoryProjectId,
|
|
194
215
|
threadId: ensured.sessionId,
|
|
195
216
|
requestContext: kickoffContext,
|
|
196
217
|
tags
|
|
@@ -198,9 +219,9 @@ function createFactorySessionTools(scope, deps) {
|
|
|
198
219
|
await session.state.set(tags);
|
|
199
220
|
await hydrateFactorySession(session, {
|
|
200
221
|
orgId: scope.orgId,
|
|
201
|
-
modelScope:
|
|
222
|
+
modelScope: resolvedModel.modelScope,
|
|
202
223
|
ownerUserId: scope.userId,
|
|
203
|
-
defaultModelId:
|
|
224
|
+
defaultModelId: requestedModel ?? resolvedModel.modelId
|
|
204
225
|
});
|
|
205
226
|
const threadId = session.thread.requireId();
|
|
206
227
|
const threadTitle = title ?? titleFrom(prompt);
|
|
@@ -219,7 +240,7 @@ function createFactorySessionTools(scope, deps) {
|
|
|
219
240
|
await primeTenantCredentialsForTenant({
|
|
220
241
|
orgId: scope.orgId,
|
|
221
242
|
userId: scope.userId,
|
|
222
|
-
credentialScope:
|
|
243
|
+
credentialScope: resolvedModel.modelScope
|
|
223
244
|
});
|
|
224
245
|
await session.sendSignal({
|
|
225
246
|
id: globalThis.crypto.randomUUID(),
|
|
@@ -269,7 +290,7 @@ function createFactorySessionTools(scope, deps) {
|
|
|
269
290
|
branch: `${USER_SESSION_BRANCH_PREFIX}${sessionId}`,
|
|
270
291
|
projectRepositoryId,
|
|
271
292
|
attributeToUserId: scope.userId,
|
|
272
|
-
visibility:
|
|
293
|
+
visibility: "org"
|
|
273
294
|
});
|
|
274
295
|
const followScope = {
|
|
275
296
|
orgId: scope.orgId,
|
|
@@ -324,7 +345,7 @@ function createFactorySessionTools(scope, deps) {
|
|
|
324
345
|
throw error;
|
|
325
346
|
}
|
|
326
347
|
const replaced = (await deps.workItems.listRunBindings(scope.orgId, scope.factoryProjectId, item.id)).find((binding) => binding.role === role && binding.id !== prepared.bindingId && binding.revokedAt !== null && binding.revokedAt >= startedAt);
|
|
327
|
-
const session = await messaging.controller.
|
|
348
|
+
const session = await messaging.controller.getSession(prepared.sessionId);
|
|
328
349
|
if (!session) throw new Error(`The ${role} session was bound but is not running; open it from the board.`);
|
|
329
350
|
const kickoffId = globalThis.crypto.randomUUID();
|
|
330
351
|
const heartbeat = heartbeatOpenRunUntilEnd(session, kickoffId);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-tools.js","names":[],"sources":["../../src/tools/session-tools.ts"],"sourcesContent":["import { RequestContext } from '@mastra/core/request-context';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport { boardForWorkItem } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/registry.js';\nimport type { IntegrationTools } from '../integrations/base.js';\nimport { buildModelCatalog, listSessionScopedCredentials } from '../routes/config.js';\nimport { USER_SESSION_BRANCH_PREFIX } from '../routes/source-control-sessions.js';\nimport { primeTenantCredentialsForTenant } from '../routes/tenant-credentials.js';\nimport { FactoryStartTransitionError, WorkItemRepositoryError } from '../rules/start-coordinator.js';\nimport type { FactoryStartCoordinator } from '../rules/start-coordinator.js';\nimport { resolveFactoryModelPolicy } from '../session/credential-policy.js';\nimport type { SourceControlSessionLookup } from '../session/factory-session.js';\nimport {\n ensureFactorySourceSession,\n hydrateFactorySession,\n resolveFactorySessionModel,\n} from '../session/factory-session.js';\nimport { recordSessionRunStart } from '../session/run-audit.js';\nimport { resolveWorkItemRepository } from '../session/work-item-repository.js';\nimport type { AuditRecorder } from '../storage/domains/audit/domain.js';\nimport type { ModelCredentialsStorage } from '../storage/domains/credentials/base.js';\nimport type { CustomProvidersStorage } from '../storage/domains/custom-providers/base.js';\nimport type { IntakeStorage } from '../storage/domains/intake/base.js';\nimport type { ModelDefaultsStorage } from '../storage/domains/model-defaults/base.js';\nimport { effectiveModelScope } from '../storage/domains/projects/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { deliverFactorySessionNotification, resolveSessionThread } from './deliver.js';\nimport type { FactoryMessaging } from './messaging-tools.js';\nimport { findWorkItem, workItemNotFound, workItemNumber, workItemRefSchema } from './read-tools.js';\nimport { heartbeatOpenRunUntilEnd } from './run-watch.js';\nimport type { FactoryToolScope } from './scope.js';\n\nconst TITLE_LENGTH = 120;\n\n/** Who renews the open-run entries this process writes; one id per process, like the dispatcher's. */\nexport const FACTORY_TOOLS_OWNER_ID = `factory-tools:${globalThis.crypto.randomUUID()}`;\n\nexport interface FactorySessionToolDependencies {\n sessions: Pick<SourceControlSessionLookup, 'getBySessionId' | 'getSourceControlBySessionId'>;\n sessionModel: {\n projects: FactoryProjectsStorage;\n modelDefaults: ModelDefaultsStorage;\n /**\n * Tenant credential stores, present when auth is on. Model switches then\n * check the target session's credential scope, like the Settings picker;\n * without them the server's own keys decide.\n */\n tenantCatalog?: {\n credentials: Pick<ModelCredentialsStorage, 'listCredentials'>;\n customProviders?: Pick<CustomProvidersStorage, 'ensureReady' | 'list'>;\n deploymentProviders?: ReadonlySet<string>;\n };\n };\n /** Resolved lazily: the controller only exists once the mount is up. */\n messaging: () => Promise<FactoryMessaging | null>;\n workItems: Pick<\n WorkItemsStorage,\n | 'list'\n | 'getForProject'\n | 'listRunBindings'\n | 'findActiveRunBindingForSession'\n | 'findRunBindingByThread'\n | 'subscribeToWorkItem'\n | 'unsubscribeFromWorkItem'\n | 'listSessionSubscriptions'\n | 'listWorkItemSubscribers'\n >;\n boards: BoardRegistry;\n auditRecorder: AuditRecorder;\n intake?: Pick<IntakeStorage, 'getConfig'>;\n /** The browser Start's coordinator; absent until the Factory runtime is ready. */\n startCoordinator: () => Pick<FactoryStartCoordinator, 'prepare'> | undefined;\n}\n\nconst LISTED_MODELS = 8;\n\nfunction ambiguousRepository(candidates: readonly string[]): string {\n return `This item has several repositories (${candidates.join(', ')}); v1 can't choose one.`;\n}\n\nfunction startErrorMessage(error: unknown): string | null {\n if (error instanceof WorkItemRepositoryError) {\n return error.code === 'repository_ambiguous' ? ambiguousRepository(error.candidates) : error.message;\n }\n if (error instanceof FactoryStartTransitionError) return `The start was refused: ${error.result.reason}`;\n return null;\n}\n\nfunction workItemLabel(item: WorkItemRow): string {\n const number = workItemNumber(item);\n return number === null ? `\"${item.title}\"` : `#${number}`;\n}\n\nfunction titleFrom(prompt: string): string {\n const firstLine = prompt.split('\\n').find(line => line.trim()) ?? prompt;\n return firstLine.trim().slice(0, TITLE_LENGTH);\n}\n\n/** Tools that start sessions on the caller's behalf, or switch their models. User sessions only. */\nexport function createFactorySessionTools(\n scope: FactoryToolScope,\n deps: FactorySessionToolDependencies,\n): IntegrationTools {\n /** The models a session can switch to, resolved from the target session's owner and project policy. */\n async function sessionModelCatalog(\n controller: FactoryMessaging['controller'],\n sessionId: string,\n threadId: string,\n ): Promise<{ usable: string[]; known: Set<string>; scopeLabel: string }> {\n const models = await controller.listAvailableModels();\n const known = new Set(models.flatMap(model => (typeof model.id === 'string' ? [model.id] : [])));\n const tenantCatalog = deps.sessionModel.tenantCatalog;\n if (!tenantCatalog) {\n const usable = (await buildModelCatalog({ controller })).map(model => model.id);\n return { usable, known, scopeLabel: 'server' };\n }\n const requestContext = new RequestContext();\n requestContext.set('user', { id: scope.userId, organizationId: scope.orgId });\n requestContext.set('controller', { resourceId: sessionId, threadId });\n const policy = await resolveFactoryModelPolicy(requestContext, {\n projects: deps.sessionModel.projects,\n sourceControl: deps.sessions,\n workItems: deps.workItems,\n });\n if (!policy) throw new Error('Session model policy is unavailable.');\n const modelScope = policy.tenant.credentialScope ?? 'user';\n const orgId = policy.tenant.orgId ?? scope.orgId;\n const customProviders = tenantCatalog.customProviders;\n const usable = (\n await buildModelCatalog({\n controller,\n tenantCredentials: await listSessionScopedCredentials({\n credentials: tenantCatalog.credentials,\n orgId,\n userId: policy.tenant.userId,\n modelScope,\n }),\n deploymentProviders: tenantCatalog.deploymentProviders,\n listCustomProviders: customProviders\n ? async () => {\n await customProviders.ensureReady();\n return customProviders.list({ orgId });\n }\n : undefined,\n })\n ).map(model => model.id);\n for (const id of usable) known.add(id);\n return { usable, known, scopeLabel: modelScope === 'factory' ? 'Factory' : 'session owner' };\n }\n\n const switchModel = createTool({\n id: 'factory_switch_session_model',\n description:\n 'Switch the model of a session you own in this Factory (side, work item, or review session) without recreating it. A running session switches immediately; a stopped one uses the model on its next run. The model must be one the session credentials can run: the owner credentials for a user-scoped session, the Factory credentials for a Factory-scoped one. Use a session ID from the Factory session or work item tools.',\n inputSchema: z\n .object({\n sessionId: z.string().trim().min(1).max(200),\n modelId: z.string().trim().min(1).max(200),\n })\n .strict(),\n execute: async ({ sessionId, modelId }) => {\n const row = await deps.sessions.getBySessionId(sessionId);\n if (\n !row ||\n row.orgId !== scope.orgId ||\n row.factoryProjectId !== scope.factoryProjectId ||\n row.userId !== scope.userId\n ) {\n throw new Error('Session not found or not owned by you in this Factory.');\n }\n // A turn nobody signed in started reaches only this session and the side sessions it started.\n if (!scope.interactive && sessionId !== scope.sessionId && row.originSessionId !== scope.sessionId) {\n throw new Error(\n 'Without a person in this turn, only this session and the side sessions it started can switch models.',\n );\n }\n const messaging = await deps.messaging();\n if (!messaging) throw new Error('Session model switching is not available on this server.');\n const threadId = await resolveSessionThread({\n controller: messaging.controller,\n sessions: deps.sessions,\n workItems: deps.workItems,\n sessionId,\n });\n if (!threadId) throw new Error('Session has no conversation thread yet.');\n const catalog = await sessionModelCatalog(messaging.controller, sessionId, threadId);\n if (!catalog.usable.includes(modelId)) {\n const options = catalog.usable.length\n ? ` Models available to it: ${catalog.usable.slice(0, LISTED_MODELS).join(', ')}${catalog.usable.length > LISTED_MODELS ? ', …' : ''}.`\n : ' No models are available to it; connect a provider credential first.';\n throw new Error(\n catalog.known.has(modelId)\n ? `Model ${modelId} exists, but this session's credential scope (${catalog.scopeLabel}) has no credential for its provider.${options}`\n : `Unknown model ${modelId}.${options}`,\n );\n }\n const { live } = await messaging.controller.setThreadModel({ resourceId: sessionId, threadId, modelId });\n return { sessionId, threadId, modelId, live };\n },\n });\n\n const start = createTool({\n id: 'factory_start_session',\n description: [\n 'Start a side session in this Factory with a prompt. It runs on its own sandbox and branch as you, and reports back to this session with `factory-session` notifications.',\n 'Use it for self-contained investigations; when the result should land as a pull request, file a work item instead.',\n 'With `workItem` and `role` it starts that role on the work item instead, without moving the card, and this session follows the item.',\n \"A role that already has a session on the item (current or past) can't be started again from here: message that session with factory_signal_session, or ask the person to use the board.\",\n ].join(' '),\n inputSchema: z\n .object({\n prompt: z.string().trim().min(1).max(8000).describe('The first message the new session receives.'),\n title: z.string().trim().min(1).max(TITLE_LENGTH).optional(),\n workItem: workItemRefSchema.optional(),\n role: z.string().trim().min(1).max(64).optional(),\n })\n .strict()\n .refine(input => input.role === undefined || input.workItem !== undefined, {\n message: 'Pass workItem together with role.',\n path: ['role'],\n }),\n execute: async ({ prompt, title, workItem, role }) => {\n if (workItem !== undefined) {\n // A role start records a run as the person, so it waits for a turn the person started.\n if (!scope.interactive) {\n throw new Error(\n 'Starting a role on a work item needs a person in this turn; start a side session, or ask the person to use the board.',\n );\n }\n return startWorkItemRole({ prompt, title, workItem, role });\n }\n\n const parent = await deps.sessions.getBySessionId(scope.sessionId);\n const sourceControl = await deps.sessions.getSourceControlBySessionId(scope.sessionId);\n if (!parent || !sourceControl) throw new Error('This session has no Factory session row to start from.');\n const messaging = await deps.messaging();\n if (!messaging) throw new Error('Starting sessions is not available on this server.');\n\n const sessionId = globalThis.crypto.randomUUID();\n const ensured = await ensureFactorySourceSession({\n sourceControl,\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n sessionId,\n branch: `${USER_SESSION_BRANCH_PREFIX}${sessionId}`,\n // Bills the caller: the credential tenant follows the row's user.\n attributeToUserId: scope.userId,\n // Same repository as the session that started it.\n ...(parent.projectRepositoryId ? { projectRepositoryId: parent.projectRepositoryId } : {}),\n visibility: parent.visibility,\n origin: { sessionId: scope.sessionId, threadId: scope.threadId },\n });\n const sessionModel = await resolveFactorySessionModel({\n ...deps.sessionModel,\n factoryProjectId: scope.factoryProjectId,\n orgId: scope.orgId,\n ownerUserId: scope.userId,\n // No work item, so an interactive session: a hybrid Factory bills it as one.\n context: 'session',\n });\n\n // Only the new row's owner as `user`. The tool call's context carries the\n // interactive marker and the parent's controller entry; neither may reach the child.\n const kickoffContext = new RequestContext();\n kickoffContext.set('user', { workosId: scope.userId, organizationId: scope.orgId });\n\n const tags = { factoryProjectId: scope.factoryProjectId, projectRepositoryId: ensured.projectRepositoryId };\n const session = await messaging.controller.createSession({\n id: ensured.sessionId,\n ownerId: scope.userId,\n resourceId: ensured.sessionId,\n threadId: ensured.sessionId,\n requestContext: kickoffContext,\n tags,\n });\n await session.state.set(tags);\n await hydrateFactorySession(session, {\n orgId: scope.orgId,\n modelScope: sessionModel.modelScope,\n ownerUserId: scope.userId,\n defaultModelId: sessionModel.modelId,\n });\n\n const threadId = session.thread.requireId();\n const threadTitle = title ?? titleFrom(prompt);\n await session.thread.rename({ title: threadTitle, pin: false });\n // On the row too, so its run-end reports name it without waiting for the title mirror.\n await sourceControl.sessions.rename({ sessionId: ensured.sessionId, title: threadTitle });\n await session.thread.setSetting({ key: 'factorySessionId', value: ensured.sessionId });\n\n await primeTenantCredentialsForTenant({\n orgId: scope.orgId,\n userId: scope.userId,\n credentialScope: sessionModel.modelScope,\n });\n // How its runs end reaches this session through the listener every Factory\n // session gets (`observeSessionRunReports`), which follows the row's origin.\n // `requireDelivery` makes `accepted` reject when the wake fails; the run itself is not awaited.\n await session.sendSignal(\n { id: globalThis.crypto.randomUUID(), type: 'user', tagName: 'user', contents: prompt },\n { requestContext: kickoffContext, requireDelivery: true },\n ).accepted;\n\n return { mode: 'side' as const, sessionId: ensured.sessionId, threadId, title: threadTitle };\n },\n });\n\n async function startWorkItemRole({\n prompt,\n title,\n workItem,\n role,\n }: {\n prompt: string;\n title?: string;\n workItem: string;\n role?: string;\n }) {\n if (!role) throw new Error('Pass the role to start on the work item.');\n const coordinator = deps.startCoordinator();\n if (!coordinator) throw new Error(\"Factory sessions can't be started yet; try again shortly.\");\n const item = await findWorkItem(deps.workItems, scope, workItem);\n if (!item) throw workItemNotFound(workItem);\n const label = workItemLabel(item);\n const board = deps.boards.get(boardForWorkItem(item));\n if (!board?.phaseForRole(role)) throw new Error(`${label}'s board has no ${role} role.`);\n const seated = item.sessions[role];\n if (seated) {\n throw new Error(\n `${label} already has a ${role} session ${seated.sessionId}; message it with factory_signal_session.`,\n );\n }\n\n const parent = await deps.sessions.getBySessionId(scope.sessionId);\n const sourceControl = await deps.sessions.getSourceControlBySessionId(scope.sessionId);\n if (!parent || !sourceControl) throw new Error('This session has no Factory session row to start from.');\n const messaging = await deps.messaging();\n if (!messaging) throw new Error('Starting sessions is not available on this server.');\n const project = await deps.sessionModel.projects.get({ orgId: scope.orgId, id: scope.factoryProjectId });\n if (!project) throw new Error('This Factory no longer exists.');\n // A work-item start is a run, so a hybrid Factory uses the Factory's scope (as the Start route does).\n const modelScope = effectiveModelScope(project.modelScope, 'run');\n const projectRepositoryId = await resolveRepository(sourceControl, item);\n\n const sessionId = globalThis.crypto.randomUUID();\n const ensured = await ensureFactorySourceSession({\n sourceControl,\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n sessionId,\n branch: `${USER_SESSION_BRANCH_PREFIX}${sessionId}`,\n projectRepositoryId,\n attributeToUserId: scope.userId,\n visibility: parent.visibility,\n });\n\n // Follow first, so a fast run's first report already has somewhere to go.\n const followScope = { orgId: scope.orgId, factoryProjectId: scope.factoryProjectId };\n const alreadyFollowing = (\n await deps.workItems.listSessionSubscriptions({ ...followScope, sessionId: scope.sessionId })\n ).some(subscription => subscription.workItemId === item.id);\n await deps.workItems.subscribeToWorkItem({\n ...followScope,\n workItemId: item.id,\n sessionId: scope.sessionId,\n threadId: scope.threadId,\n resourceId: scope.resourceId,\n userId: scope.userId,\n reason: 'started',\n });\n\n const startedAt = new Date();\n let prepared;\n try {\n prepared = await coordinator.prepare({\n orgId: scope.orgId,\n userId: scope.userId,\n factoryProjectId: scope.factoryProjectId,\n sessionId: ensured.sessionId,\n threadTitle: title ?? `${label} ${role}`,\n kickoffKey: globalThis.crypto.randomUUID(),\n modelScope,\n defaultModelId: modelScope === 'factory' ? (project.defaultModelId ?? undefined) : undefined,\n workItem: {\n id: item.id,\n role,\n input: {\n externalSource: item.externalSource,\n parentWorkItemId: item.parentWorkItemId,\n title: item.title,\n stages: item.stages,\n sessions: item.sessions,\n metadata: item.metadata,\n },\n },\n });\n } catch (error) {\n if (!alreadyFollowing) {\n await deps.workItems.unsubscribeFromWorkItem({\n orgId: scope.orgId,\n workItemId: item.id,\n sessionId: scope.sessionId,\n });\n }\n const message = startErrorMessage(error);\n if (message) throw new Error(message);\n throw error;\n }\n\n const bindings = await deps.workItems.listRunBindings(scope.orgId, scope.factoryProjectId, item.id);\n const replaced = bindings.find(\n binding =>\n binding.role === role &&\n binding.id !== prepared.bindingId &&\n binding.revokedAt !== null &&\n binding.revokedAt >= startedAt,\n );\n\n const session = await messaging.controller.getSessionByResource(prepared.resourceId);\n if (!session) throw new Error(`The ${role} session was bound but is not running; open it from the board.`);\n const kickoffId = globalThis.crypto.randomUUID();\n // Heartbeat from before the kickoff so a fast run's end is seen; the open-run\n // entry and audit follow the accepted kickoff, as the dispatcher records them.\n // The run's end reaches this item's followers through the session's listener.\n const heartbeat = heartbeatOpenRunUntilEnd(session, kickoffId);\n const kickoffContext = new RequestContext();\n kickoffContext.set('user', { workosId: scope.userId, organizationId: scope.orgId });\n try {\n await session.sendSignal(\n { id: kickoffId, type: 'user', tagName: 'user', contents: prompt },\n { requestContext: kickoffContext, requireDelivery: true },\n ).accepted;\n } catch (error) {\n heartbeat.stop();\n throw error;\n }\n\n await recordSessionRunStart(session, {\n audit: deps.auditRecorder,\n actorType: 'human',\n observedEnd: heartbeat.endReason,\n run: {\n kickoffId,\n ownerId: FACTORY_TOOLS_OWNER_ID,\n heartbeatAt: Date.now(),\n bindingId: prepared.bindingId,\n role,\n startedBy: scope.userId,\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n workItemId: item.id,\n workItemName: item.title,\n sessionId: prepared.sessionId,\n threadId: prepared.threadId,\n branch: prepared.branch,\n },\n });\n\n const number = workItemNumber(item);\n return {\n mode: 'work-item' as const,\n workItem: { id: item.id, ...(number === null ? {} : { number }) },\n role,\n sessionId: prepared.sessionId,\n threadId: prepared.threadId,\n bindingId: prepared.bindingId,\n ...(replaced ? { replacedSessionId: replaced.sessionId } : {}),\n };\n }\n\n async function resolveRepository(sourceControl: SourceControlStorageHandle, item: WorkItemRow): Promise<string> {\n const intakeConfig = await deps.intake?.getConfig({ orgId: scope.orgId, integrationIds: ['linear'] });\n const repository = await resolveWorkItemRepository({\n sourceControl,\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n item,\n linearRepositoryMap: intakeConfig?.linear?.repositoryByLinearProject,\n });\n if (repository.status === 'ambiguous') {\n throw new Error(ambiguousRepository(repository.candidates));\n }\n if (repository.status === 'unlinked') throw new Error(repository.hint);\n return repository.projectRepositoryId;\n }\n\n return { factory_start_session: start, factory_switch_session_model: switchModel };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"session-tools.js","names":[],"sources":["../../src/tools/session-tools.ts"],"sourcesContent":["import { RequestContext } from '@mastra/core/request-context';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport { boardForWorkItem } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/registry.js';\nimport type { IntegrationTools } from '../integrations/base.js';\nimport { buildModelCatalog, listSessionScopedCredentials } from '../routes/config.js';\nimport { USER_SESSION_BRANCH_PREFIX } from '../routes/source-control-sessions.js';\nimport { primeTenantCredentialsForTenant } from '../routes/tenant-credentials.js';\nimport { FactoryStartTransitionError, WorkItemRepositoryError } from '../rules/start-coordinator.js';\nimport type { FactoryStartCoordinator } from '../rules/start-coordinator.js';\nimport { resolveFactoryModelPolicy } from '../session/credential-policy.js';\nimport type { SourceControlSessionLookup } from '../session/factory-session.js';\nimport {\n ensureFactorySourceSession,\n hydrateFactorySession,\n resolveFactorySessionModel,\n} from '../session/factory-session.js';\nimport { recordSessionRunStart } from '../session/run-audit.js';\nimport { resolveWorkItemRepository } from '../session/work-item-repository.js';\nimport type { AuditRecorder } from '../storage/domains/audit/domain.js';\nimport type { ModelCredentialsStorage } from '../storage/domains/credentials/base.js';\nimport type { CustomProvidersStorage } from '../storage/domains/custom-providers/base.js';\nimport type { IntakeStorage } from '../storage/domains/intake/base.js';\nimport type { ModelDefaultsStorage } from '../storage/domains/model-defaults/base.js';\nimport { effectiveModelScope } from '../storage/domains/projects/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { deliverFactorySessionNotification, resolveSessionThread } from './deliver.js';\nimport type { FactoryMessaging } from './messaging-tools.js';\nimport { findWorkItem, workItemNotFound, workItemNumber, workItemRefSchema } from './read-tools.js';\nimport { heartbeatOpenRunUntilEnd } from './run-watch.js';\nimport type { FactoryToolScope } from './scope.js';\nimport { sessionModel } from './side-sessions.js';\n\nconst TITLE_LENGTH = 120;\n\n/** Who renews the open-run entries this process writes; one id per process, like the dispatcher's. */\nexport const FACTORY_TOOLS_OWNER_ID = `factory-tools:${globalThis.crypto.randomUUID()}`;\n\nexport interface FactorySessionToolDependencies {\n sessions: Pick<SourceControlSessionLookup, 'getBySessionId' | 'getSourceControlBySessionId'>;\n sessionModel: {\n projects: FactoryProjectsStorage;\n modelDefaults: ModelDefaultsStorage;\n /**\n * Tenant credential stores, present when auth is on. Model switches then\n * check the target session's credential scope, like the Settings picker;\n * without them the server's own keys decide.\n */\n tenantCatalog?: {\n credentials: Pick<ModelCredentialsStorage, 'listCredentials'>;\n customProviders?: Pick<CustomProvidersStorage, 'ensureReady' | 'list'>;\n deploymentProviders?: ReadonlySet<string>;\n };\n };\n /** Resolved lazily: the controller only exists once the mount is up. */\n messaging: () => Promise<FactoryMessaging | null>;\n workItems: Pick<\n WorkItemsStorage,\n | 'list'\n | 'getForProject'\n | 'listRunBindings'\n | 'findActiveRunBindingForSession'\n | 'findRunBindingByThread'\n | 'hasRunBindingForSession'\n | 'subscribeToWorkItem'\n | 'unsubscribeFromWorkItem'\n | 'listSessionSubscriptions'\n | 'listWorkItemSubscribers'\n >;\n boards: BoardRegistry;\n auditRecorder: AuditRecorder;\n intake?: Pick<IntakeStorage, 'getConfig'>;\n /** The browser Start's coordinator; absent until the Factory runtime is ready. */\n startCoordinator: () => Pick<FactoryStartCoordinator, 'prepare'> | undefined;\n}\n\nconst LISTED_MODELS = 8;\n\nfunction ambiguousRepository(candidates: readonly string[]): string {\n return `This item has several repositories (${candidates.join(', ')}); v1 can't choose one.`;\n}\n\nfunction startErrorMessage(error: unknown): string | null {\n if (error instanceof WorkItemRepositoryError) {\n return error.code === 'repository_ambiguous' ? ambiguousRepository(error.candidates) : error.message;\n }\n if (error instanceof FactoryStartTransitionError) return `The start was refused: ${error.result.reason}`;\n return null;\n}\n\nfunction workItemLabel(item: WorkItemRow): string {\n const number = workItemNumber(item);\n return number === null ? `\"${item.title}\"` : `#${number}`;\n}\n\nfunction titleFrom(prompt: string): string {\n const firstLine = prompt.split('\\n').find(line => line.trim()) ?? prompt;\n return firstLine.trim().slice(0, TITLE_LENGTH);\n}\n\n/** Tools that start sessions on the caller's behalf, or switch their models. User sessions only. */\nexport function createFactorySessionTools(\n scope: FactoryToolScope,\n deps: FactorySessionToolDependencies,\n): IntegrationTools {\n /** The models a session can switch to, resolved from the target session's owner and project policy. */\n async function sessionModelCatalog(\n controller: FactoryMessaging['controller'],\n sessionId: string,\n threadId: string,\n resourceId: string,\n ): Promise<{ usable: string[]; known: Set<string>; scopeLabel: string }> {\n const models = await controller.listAvailableModels();\n const known = new Set(models.flatMap(model => (typeof model.id === 'string' ? [model.id] : [])));\n const tenantCatalog = deps.sessionModel.tenantCatalog;\n if (!tenantCatalog) {\n const usable = (await buildModelCatalog({ controller })).map(model => model.id);\n return { usable, known, scopeLabel: 'server' };\n }\n const requestContext = new RequestContext();\n requestContext.set('user', { id: scope.userId, organizationId: scope.orgId });\n requestContext.set('controller', { sessionId, resourceId, threadId });\n const policy = await resolveFactoryModelPolicy(requestContext, {\n projects: deps.sessionModel.projects,\n sourceControl: deps.sessions,\n workItems: deps.workItems,\n });\n if (!policy) throw new Error('Session model policy is unavailable.');\n const modelScope = policy.tenant.credentialScope ?? 'user';\n const orgId = policy.tenant.orgId ?? scope.orgId;\n const customProviders = tenantCatalog.customProviders;\n const usable = (\n await buildModelCatalog({\n controller,\n tenantCredentials: await listSessionScopedCredentials({\n credentials: tenantCatalog.credentials,\n orgId,\n userId: policy.tenant.userId,\n modelScope,\n }),\n deploymentProviders: tenantCatalog.deploymentProviders,\n listCustomProviders: customProviders\n ? async () => {\n await customProviders.ensureReady();\n return customProviders.list({ orgId });\n }\n : undefined,\n })\n ).map(model => model.id);\n for (const id of usable) known.add(id);\n return { usable, known, scopeLabel: modelScope === 'factory' ? 'Factory' : 'session owner' };\n }\n\n function unrunnableModelError(\n modelId: string,\n catalog: { usable: string[]; known: Set<string>; scopeLabel: string },\n ) {\n const options = catalog.usable.length\n ? ` Models available to it: ${catalog.usable.slice(0, LISTED_MODELS).join(', ')}${catalog.usable.length > LISTED_MODELS ? ', …' : ''}.`\n : ' No models are available to it; connect a provider credential first.';\n return new Error(\n catalog.known.has(modelId)\n ? `Model ${modelId} exists, but this session's credential scope (${catalog.scopeLabel}) has no credential for its provider.${options}`\n : `Unknown model ${modelId}.${options}`,\n );\n }\n\n const switchModel = createTool({\n id: 'factory_switch_session_model',\n description:\n 'Look up or switch the model of a session you own in this Factory (side, work item, or review session). When someone describes a model by purpose, family, or preference rather than an exact ID, call with sessionId alone first to list models the target session can actually use. Interpret their intent against that list, then call with the chosen exact modelId to switch; do not use their words as a guessed model ID. A running session switches immediately; a stopped one uses the model on its next run. User-scoped sessions use their owner credentials; Factory-scoped sessions use Factory credentials. Use a session ID from the Factory session or work item tools.',\n inputSchema: z\n .object({\n sessionId: z.string().trim().min(1).max(200),\n modelId: z.string().trim().min(1).max(200).optional().describe('Omit to list usable models without switching.'),\n })\n .strict(),\n execute: async ({ sessionId, modelId }) => {\n const row = await deps.sessions.getBySessionId(sessionId);\n if (\n !row ||\n row.orgId !== scope.orgId ||\n row.factoryProjectId !== scope.factoryProjectId ||\n row.userId !== scope.userId\n ) {\n throw new Error('Session not found or not owned by you in this Factory.');\n }\n // A turn nobody signed in started reaches only this session and the side sessions it started.\n if (!scope.interactive && sessionId !== scope.sessionId && row.originSessionId !== scope.sessionId) {\n throw new Error(\n 'Without a person in this turn, only this session and the side sessions it started can switch models.',\n );\n }\n const messaging = await deps.messaging();\n if (!messaging) throw new Error('Session model switching is not available on this server.');\n const threadId = await resolveSessionThread({\n controller: messaging.controller,\n sessions: deps.sessions,\n workItems: deps.workItems,\n sessionId,\n });\n if (!threadId) throw new Error('Session has no conversation thread yet.');\n // The thread's resource, not the session id: sessions share their Factory's or owner's resource.\n const resourceId = (await messaging.controller.queryThreadById({ threadId }))?.resourceId ?? sessionId;\n const catalog = await sessionModelCatalog(messaging.controller, sessionId, threadId, resourceId);\n if (modelId === undefined) return { sessionId, models: catalog.usable };\n if (!catalog.usable.includes(modelId)) throw unrunnableModelError(modelId, catalog);\n const { live } = await messaging.controller.setThreadModel({ resourceId, threadId, modelId });\n return { sessionId, threadId, modelId, live };\n },\n });\n\n const start = createTool({\n id: 'factory_start_session',\n description: [\n 'Start a side session in this Factory with a prompt. It runs on its own sandbox and branch as you, and reports back to this session with `factory-session` notifications.',\n 'A side session uses this session’s model. Pass `model` to start it on another model the session credentials can run.',\n 'Use it for self-contained investigations; when the result should land as a pull request, file a work item instead.',\n 'With `workItem` and `role` it starts that role on the work item instead, without moving the card, and this session follows the item.',\n \"A role that already has a session on the item (current or past) can't be started again from here: message that session with factory_signal_session, or ask the person to use the board.\",\n ].join(' '),\n inputSchema: z\n .object({\n prompt: z.string().trim().min(1).max(8000).describe('The first message the new session receives.'),\n title: z.string().trim().min(1).max(TITLE_LENGTH).optional(),\n model: z\n .string()\n .trim()\n .min(1)\n .max(200)\n .optional()\n .describe(\n 'Model id for a side session. Omit it to inherit this session’s model. The session credentials must be able to run it.',\n ),\n workItem: workItemRefSchema.optional(),\n role: z.string().trim().min(1).max(64).optional(),\n })\n .strict()\n .refine(input => input.role === undefined || input.workItem !== undefined, {\n message: 'Pass workItem together with role.',\n path: ['role'],\n }),\n execute: async ({ prompt, title, model, workItem, role }) => {\n if (workItem !== undefined) {\n // A role start records a run as the person, so it waits for a turn the person started.\n if (!scope.interactive) {\n throw new Error(\n 'Starting a role on a work item needs a person in this turn; start a side session, or ask the person to use the board.',\n );\n }\n return startWorkItemRole({ prompt, title, workItem, role });\n }\n\n const parent = await deps.sessions.getBySessionId(scope.sessionId);\n const sourceControl = await deps.sessions.getSourceControlBySessionId(scope.sessionId);\n if (!parent || !sourceControl) throw new Error('This session has no Factory session row to start from.');\n const messaging = await deps.messaging();\n if (!messaging) throw new Error('Starting sessions is not available on this server.');\n\n // The child runs with the same credentials as this session, so validate against those.\n const parentThreadId = await resolveSessionThread({\n controller: messaging.controller,\n sessions: deps.sessions,\n workItems: deps.workItems,\n sessionId: scope.sessionId,\n });\n const parentResourceId = parentThreadId\n ? ((await messaging.controller.queryThreadById({ threadId: parentThreadId }))?.resourceId ?? scope.sessionId)\n : scope.sessionId;\n const catalog = await sessionModelCatalog(\n messaging.controller,\n scope.sessionId,\n parentThreadId ?? scope.threadId,\n parentResourceId,\n );\n const inherited = model\n ? null\n : await sessionModel(messaging.controller, deps.sessions, deps.workItems, scope.sessionId);\n const requestedModel = model ?? (inherited && catalog.usable.includes(inherited) ? inherited : undefined);\n if (requestedModel && !catalog.usable.includes(requestedModel))\n throw unrunnableModelError(requestedModel, catalog);\n\n const sessionId = globalThis.crypto.randomUUID();\n const ensured = await ensureFactorySourceSession({\n sourceControl,\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n sessionId,\n branch: `${USER_SESSION_BRANCH_PREFIX}${sessionId}`,\n // Bills the caller: the credential tenant follows the row's user.\n attributeToUserId: scope.userId,\n // Same repository as the session that started it.\n ...(parent.projectRepositoryId ? { projectRepositoryId: parent.projectRepositoryId } : {}),\n visibility: parent.visibility,\n origin: { sessionId: scope.sessionId, threadId: scope.threadId },\n });\n const resolvedModel = await resolveFactorySessionModel({\n ...deps.sessionModel,\n factoryProjectId: scope.factoryProjectId,\n orgId: scope.orgId,\n ownerUserId: scope.userId,\n // No work item, so an interactive session: a hybrid Factory bills it as one.\n context: 'session',\n });\n\n // Only the new row's owner as `user`. The tool call's context carries the\n // interactive marker and the parent's controller entry; neither may reach the child.\n const kickoffContext = new RequestContext();\n kickoffContext.set('user', { workosId: scope.userId, organizationId: scope.orgId });\n\n const tags = { factoryProjectId: scope.factoryProjectId, projectRepositoryId: ensured.projectRepositoryId };\n const session = await messaging.controller.createSession({\n id: ensured.sessionId,\n ownerId: scope.userId,\n // A side session works for the Factory, so it shares the Factory's memory\n // resource; a private one stays with its owner, like a Slack DM.\n resourceId: parent.visibility === 'private' ? scope.userId : scope.factoryProjectId,\n threadId: ensured.sessionId,\n requestContext: kickoffContext,\n tags,\n });\n await session.state.set(tags);\n await hydrateFactorySession(session, {\n orgId: scope.orgId,\n modelScope: resolvedModel.modelScope,\n ownerUserId: scope.userId,\n defaultModelId: requestedModel ?? resolvedModel.modelId,\n });\n\n const threadId = session.thread.requireId();\n const threadTitle = title ?? titleFrom(prompt);\n await session.thread.rename({ title: threadTitle, pin: false });\n // On the row too, so its run-end reports name it without waiting for the title mirror.\n await sourceControl.sessions.rename({ sessionId: ensured.sessionId, title: threadTitle });\n await session.thread.setSetting({ key: 'factorySessionId', value: ensured.sessionId });\n\n await primeTenantCredentialsForTenant({\n orgId: scope.orgId,\n userId: scope.userId,\n credentialScope: resolvedModel.modelScope,\n });\n // How its runs end reaches this session through the listener every Factory\n // session gets (`observeSessionRunReports`), which follows the row's origin.\n // `requireDelivery` makes `accepted` reject when the wake fails; the run itself is not awaited.\n await session.sendSignal(\n { id: globalThis.crypto.randomUUID(), type: 'user', tagName: 'user', contents: prompt },\n { requestContext: kickoffContext, requireDelivery: true },\n ).accepted;\n\n return { mode: 'side' as const, sessionId: ensured.sessionId, threadId, title: threadTitle };\n },\n });\n\n async function startWorkItemRole({\n prompt,\n title,\n workItem,\n role,\n }: {\n prompt: string;\n title?: string;\n workItem: string;\n role?: string;\n }) {\n if (!role) throw new Error('Pass the role to start on the work item.');\n const coordinator = deps.startCoordinator();\n if (!coordinator) throw new Error(\"Factory sessions can't be started yet; try again shortly.\");\n const item = await findWorkItem(deps.workItems, scope, workItem);\n if (!item) throw workItemNotFound(workItem);\n const label = workItemLabel(item);\n const board = deps.boards.get(boardForWorkItem(item));\n if (!board?.phaseForRole(role)) throw new Error(`${label}'s board has no ${role} role.`);\n const seated = item.sessions[role];\n if (seated) {\n throw new Error(\n `${label} already has a ${role} session ${seated.sessionId}; message it with factory_signal_session.`,\n );\n }\n\n const parent = await deps.sessions.getBySessionId(scope.sessionId);\n const sourceControl = await deps.sessions.getSourceControlBySessionId(scope.sessionId);\n if (!parent || !sourceControl) throw new Error('This session has no Factory session row to start from.');\n const messaging = await deps.messaging();\n if (!messaging) throw new Error('Starting sessions is not available on this server.');\n const project = await deps.sessionModel.projects.get({ orgId: scope.orgId, id: scope.factoryProjectId });\n if (!project) throw new Error('This Factory no longer exists.');\n // A work-item start is a run, so a hybrid Factory uses the Factory's scope (as the Start route does).\n const modelScope = effectiveModelScope(project.modelScope, 'run');\n const projectRepositoryId = await resolveRepository(sourceControl, item);\n\n const sessionId = globalThis.crypto.randomUUID();\n const ensured = await ensureFactorySourceSession({\n sourceControl,\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n sessionId,\n branch: `${USER_SESSION_BRANCH_PREFIX}${sessionId}`,\n projectRepositoryId,\n attributeToUserId: scope.userId,\n // A run works a shared card, so the org can open it and answer its approvals even when the session that\n // started it is private.\n visibility: 'org',\n });\n\n // Follow first, so a fast run's first report already has somewhere to go.\n const followScope = { orgId: scope.orgId, factoryProjectId: scope.factoryProjectId };\n const alreadyFollowing = (\n await deps.workItems.listSessionSubscriptions({ ...followScope, sessionId: scope.sessionId })\n ).some(subscription => subscription.workItemId === item.id);\n await deps.workItems.subscribeToWorkItem({\n ...followScope,\n workItemId: item.id,\n sessionId: scope.sessionId,\n threadId: scope.threadId,\n resourceId: scope.resourceId,\n userId: scope.userId,\n reason: 'started',\n });\n\n const startedAt = new Date();\n let prepared;\n try {\n prepared = await coordinator.prepare({\n orgId: scope.orgId,\n userId: scope.userId,\n factoryProjectId: scope.factoryProjectId,\n sessionId: ensured.sessionId,\n threadTitle: title ?? `${label} ${role}`,\n kickoffKey: globalThis.crypto.randomUUID(),\n modelScope,\n defaultModelId: modelScope === 'factory' ? (project.defaultModelId ?? undefined) : undefined,\n workItem: {\n id: item.id,\n role,\n input: {\n externalSource: item.externalSource,\n parentWorkItemId: item.parentWorkItemId,\n title: item.title,\n stages: item.stages,\n sessions: item.sessions,\n metadata: item.metadata,\n },\n },\n });\n } catch (error) {\n if (!alreadyFollowing) {\n await deps.workItems.unsubscribeFromWorkItem({\n orgId: scope.orgId,\n workItemId: item.id,\n sessionId: scope.sessionId,\n });\n }\n const message = startErrorMessage(error);\n if (message) throw new Error(message);\n throw error;\n }\n\n const bindings = await deps.workItems.listRunBindings(scope.orgId, scope.factoryProjectId, item.id);\n const replaced = bindings.find(\n binding =>\n binding.role === role &&\n binding.id !== prepared.bindingId &&\n binding.revokedAt !== null &&\n binding.revokedAt >= startedAt,\n );\n\n const session = await messaging.controller.getSession(prepared.sessionId);\n if (!session) throw new Error(`The ${role} session was bound but is not running; open it from the board.`);\n const kickoffId = globalThis.crypto.randomUUID();\n // Heartbeat from before the kickoff so a fast run's end is seen; the open-run\n // entry and audit follow the accepted kickoff, as the dispatcher records them.\n // The run's end reaches this item's followers through the session's listener.\n const heartbeat = heartbeatOpenRunUntilEnd(session, kickoffId);\n const kickoffContext = new RequestContext();\n kickoffContext.set('user', { workosId: scope.userId, organizationId: scope.orgId });\n try {\n await session.sendSignal(\n { id: kickoffId, type: 'user', tagName: 'user', contents: prompt },\n { requestContext: kickoffContext, requireDelivery: true },\n ).accepted;\n } catch (error) {\n heartbeat.stop();\n throw error;\n }\n\n await recordSessionRunStart(session, {\n audit: deps.auditRecorder,\n actorType: 'human',\n observedEnd: heartbeat.endReason,\n run: {\n kickoffId,\n ownerId: FACTORY_TOOLS_OWNER_ID,\n heartbeatAt: Date.now(),\n bindingId: prepared.bindingId,\n role,\n startedBy: scope.userId,\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n workItemId: item.id,\n workItemName: item.title,\n sessionId: prepared.sessionId,\n threadId: prepared.threadId,\n branch: prepared.branch,\n },\n });\n\n const number = workItemNumber(item);\n return {\n mode: 'work-item' as const,\n workItem: { id: item.id, ...(number === null ? {} : { number }) },\n role,\n sessionId: prepared.sessionId,\n threadId: prepared.threadId,\n bindingId: prepared.bindingId,\n ...(replaced ? { replacedSessionId: replaced.sessionId } : {}),\n };\n }\n\n async function resolveRepository(sourceControl: SourceControlStorageHandle, item: WorkItemRow): Promise<string> {\n const intakeConfig = await deps.intake?.getConfig({ orgId: scope.orgId, integrationIds: ['linear'] });\n const repository = await resolveWorkItemRepository({\n sourceControl,\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n item,\n linearRepositoryMap: intakeConfig?.linear?.repositoryByLinearProject,\n });\n if (repository.status === 'ambiguous') {\n throw new Error(ambiguousRepository(repository.candidates));\n }\n if (repository.status === 'unlinked') throw new Error(repository.hint);\n return repository.projectRepositoryId;\n }\n\n return { factory_start_session: start, factory_switch_session_model: switchModel };\n}\n"],"mappings":"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"}
|
|
@@ -6,11 +6,11 @@ import { createHash } from "crypto";
|
|
|
6
6
|
//#region src/tools/sessions-state-processor.ts
|
|
7
7
|
const STATE_ID = "factory-sessions";
|
|
8
8
|
/** Finished side sessions and items that reached a terminal stage drop out after this long. */
|
|
9
|
-
const FACTORY_SESSIONS_MAX_AGE_MS =
|
|
9
|
+
const FACTORY_SESSIONS_MAX_AGE_MS = 864e5;
|
|
10
10
|
/** Asks a long run to show it is still moving; a silent half hour reads the same as a stuck one. */
|
|
11
11
|
const PROGRESS_ASK = "On long work, send a progress report after each plan step.";
|
|
12
12
|
/** A running worker whose thread has not moved for this long is shown as quiet, so a stuck run stands out. */
|
|
13
|
-
const FACTORY_SESSIONS_QUIET_MS =
|
|
13
|
+
const FACTORY_SESSIONS_QUIET_MS = 6e5;
|
|
14
14
|
/** Rows shown before the rest collapse into "+N more". */
|
|
15
15
|
const FACTORY_SESSIONS_MAX_ROWS = 20;
|
|
16
16
|
const EMPTY_CACHE_KEY = "empty";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessions-state-processor.js","names":["#deps","#workerLine","#signal","#userState","#render","newest","report"],"sources":["../../src/tools/sessions-state-processor.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport type { NotificationsStorage } from '@mastra/core/notifications';\nimport type { ComputeStateSignalArgs, ComputeStateSignalResult, Processor } from '@mastra/core/processors';\n\nimport { boardForWorkItem } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/registry.js';\nimport type { SourceControlSessionLookup } from '../session/factory-session.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { resolveFactoryToolScope } from './scope.js';\nimport type { FactoryToolScope, ResolveFactoryToolScopeOptions } from './scope.js';\nimport {\n itemLabel,\n lastReport as report,\n listSessionNotifications,\n listSideSessions,\n newestNotification as newest,\n} from './side-sessions.js';\nimport type { FactorySessionRunState, LastReport } from './side-sessions.js';\n\nconst STATE_ID = 'factory-sessions';\n/** Finished side sessions and items that reached a terminal stage drop out after this long. */\nexport const FACTORY_SESSIONS_MAX_AGE_MS = 24 * 60 * 60 * 1000;\n/** Asks a long run to show it is still moving; a silent half hour reads the same as a stuck one. */\nconst PROGRESS_ASK = 'On long work, send a progress report after each plan step.';\n\n/** A running worker whose thread has not moved for this long is shown as quiet, so a stuck run stands out. */\nexport const FACTORY_SESSIONS_QUIET_MS = 10 * 60 * 1000;\n/** Rows shown before the rest collapse into \"+N more\". */\nexport const FACTORY_SESSIONS_MAX_ROWS = 20;\nconst EMPTY_CACHE_KEY = 'empty';\n\nexport type { FactorySessionRunState } from './side-sessions.js';\n\nexport interface FactorySessionsStateDependencies {\n authEnabled: boolean;\n sessions: Pick<SourceControlSessionLookup, 'getBySessionId' | 'listByOrigin'>;\n projects: ResolveFactoryToolScopeOptions['projects'];\n workItems: Pick<\n WorkItemsStorage,\n | 'hasRunBindingForSession'\n | 'listSessionSubscriptions'\n | 'getForProject'\n | 'listRunBindings'\n | 'findActiveRunBindingForSession'\n | 'listWorkItemSubscribers'\n >;\n boards: BoardRegistry;\n /** Whether the session's agent is mid-run in this process; `idle` when it isn't live here. */\n isRunning: (sessionId: string) => Promise<boolean>;\n /** When a thread last changed; tells a long run that is still moving from one that stopped. */\n lastActivity?: (threadId: string) => Promise<Date | null>;\n notifications: () => Promise<Pick<NotificationsStorage, 'listNotifications'> | null>;\n now?: () => number;\n}\n\ninterface SideSessionRow {\n sessionId: string;\n title: string;\n state: FactorySessionRunState;\n lastReport: LastReport | null;\n muted: boolean;\n}\n\ninterface FollowedRow {\n workItemId: string;\n label: string;\n title: string;\n stage: string;\n worker: { role: string; sessionId: string; state: FactorySessionRunState; quietSince: string | null } | null;\n lastReport: LastReport | null;\n}\n\ninterface SessionsState {\n sideSessions: SideSessionRow[];\n following: FollowedRow[];\n more: number;\n}\n\nfunction escapeText(value: string): string {\n return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');\n}\n\nfunction cacheKeyFor(value: unknown): string {\n return `sessions:${createHash('sha256').update(JSON.stringify(value)).digest('hex').slice(0, 16)}`;\n}\n\n/**\n * The `<factory-sessions>` state signal. A user session sees the side sessions\n * it started and the work items it follows; a side session or item worker sees\n * who it reports to. Sessions it only messaged never appear. Silent until there\n * is something to show; when the last entry ages out it sends one final empty\n * snapshot, then stays silent.\n */\nexport class FactorySessionsStateProcessor implements Processor<'factory-sessions'> {\n readonly id = STATE_ID;\n readonly stateId = STATE_ID;\n readonly #deps: FactorySessionsStateDependencies;\n\n constructor(deps: FactorySessionsStateDependencies) {\n this.#deps = deps;\n }\n\n async computeStateSignal(args: ComputeStateSignalArgs): Promise<ComputeStateSignalResult> {\n const scope = await resolveFactoryToolScope({\n requestContext: args.requestContext!,\n authEnabled: this.#deps.authEnabled,\n sessions: this.#deps.sessions,\n projects: this.#deps.projects,\n workItems: this.#deps.workItems,\n });\n if (!scope) return;\n\n const hasBase = Boolean(args.lastSnapshot) && args.contextWindow.hasSnapshot;\n const previousKey = hasBase\n ? (args.tracking?.currentCacheKey ?? (args.lastSnapshot?.metadata?.state as { cacheKey?: string })?.cacheKey)\n : undefined;\n const unchanged = (cacheKey: string) => previousKey === cacheKey;\n // Silence rule: one final empty snapshot replaces a non-empty one, then nothing.\n const cleared = () => hasBase && previousKey !== undefined && previousKey !== EMPTY_CACHE_KEY;\n\n if (scope.kind === 'worker') {\n const contents = await this.#workerLine(scope);\n if (!contents) {\n return cleared()\n ? this.#signal(EMPTY_CACHE_KEY, 'This session is not reporting on any work now.', {}, { entries: 0 })\n : undefined;\n }\n const cacheKey = cacheKeyFor(contents);\n if (unchanged(cacheKey)) return;\n return this.#signal(cacheKey, escapeText(contents), { worker: contents }, { entries: 1 });\n }\n\n const state = await this.#userState(scope);\n if (state.sideSessions.length === 0 && state.following.length === 0) {\n if (!cleared()) return;\n return this.#signal(EMPTY_CACHE_KEY, 'No sub-sessions or followed work items.', state, { entries: 0 });\n }\n const cacheKey = cacheKeyFor(state);\n if (unchanged(cacheKey)) return;\n return this.#signal(cacheKey, this.#render(state), state, {\n sideSessions: state.sideSessions.length,\n following: state.following.length,\n });\n }\n\n #signal(cacheKey: string, contents: string, value: unknown, attributes: Record<string, number>) {\n return {\n id: STATE_ID,\n cacheKey,\n mode: 'snapshot' as const,\n tagName: 'factory-sessions',\n contents,\n value: { sessions: value },\n attributes,\n metadata: { value: { sessions: value } },\n };\n }\n\n async #workerLine(scope: FactoryToolScope): Promise<string | null> {\n if (scope.origin) {\n return `Started by session ${scope.origin.sessionId} (thread ${scope.origin.threadId}). Report back with factory_message_origin. ${PROGRESS_ASK}`;\n }\n const binding = await this.#deps.workItems.findActiveRunBindingForSession({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n sessionId: scope.sessionId,\n });\n if (!binding) return null;\n const item = await this.#deps.workItems.getForProject(scope.orgId, scope.factoryProjectId, binding.workItemId);\n const subscribers = await this.#deps.workItems.listWorkItemSubscribers({\n orgId: scope.orgId,\n workItemId: binding.workItemId,\n });\n const following = subscribers.filter(subscriber => subscriber.sessionId !== scope.sessionId).length;\n // Nobody to report to: nothing worth showing (most board-started workers).\n if (following === 0) return null;\n const label = item ? itemLabel(item) : binding.workItemId;\n return `Working on ${label} as ${binding.role}. Reports go to ${following} following session${following === 1 ? '' : 's'}. ${PROGRESS_ASK}`;\n }\n\n async #userState(scope: FactoryToolScope): Promise<SessionsState> {\n const now = this.#deps.now?.() ?? Date.now();\n const notifications = await this.#deps.notifications();\n const records = await listSessionNotifications(notifications, scope.threadId);\n\n const sideSessions: Array<SideSessionRow & { active: boolean }> = [];\n const listed = await listSideSessions(\n {\n sessions: this.#deps.sessions,\n workItems: this.#deps.workItems,\n isRunning: this.#deps.isRunning,\n notifications,\n },\n scope,\n { records },\n );\n for (const row of listed) {\n if (row.state !== 'running' && now - Date.parse(row.lastActivityAt) > FACTORY_SESSIONS_MAX_AGE_MS) continue;\n sideSessions.push({\n sessionId: row.sessionId,\n title: row.title,\n state: row.state,\n lastReport: row.lastReport,\n muted: row.mutedAt !== null,\n active: row.state !== 'idle',\n });\n }\n\n const subscriptions = await this.#deps.workItems.listSessionSubscriptions({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n sessionId: scope.sessionId,\n });\n const following: Array<FollowedRow & { active: boolean }> = [];\n for (const subscription of subscriptions) {\n const item = await this.#deps.workItems.getForProject(\n scope.orgId,\n scope.factoryProjectId,\n subscription.workItemId,\n );\n if (!item) continue;\n const stage = item.stages[0] ?? 'unknown';\n const terminal = this.#deps.boards.get(boardForWorkItem(item))?.isTerminal(stage) ?? false;\n if (terminal && now - item.updatedAt.getTime() > FACTORY_SESSIONS_MAX_AGE_MS) continue;\n const bindings = await this.#deps.workItems.listRunBindings(scope.orgId, scope.factoryProjectId, item.id);\n const active = bindings.find(binding => binding.status === 'active');\n const label = itemLabel(item);\n const last = newest(\n records,\n record =>\n record.attributes?.['work-item'] === label || (record.kind === 'stage' && record.sourceId === item.id),\n );\n const running = active ? await this.#deps.isRunning(active.sessionId) : false;\n const lastActivity = active && running ? await this.#deps.lastActivity?.(active.threadId) : null;\n // Only a quiet run is shown with its time; a moving one would change the snapshot on every step.\n const quiet = lastActivity && now - lastActivity.getTime() >= FACTORY_SESSIONS_QUIET_MS ? lastActivity : null;\n const lastFromWorker = active\n ? newest(records, record => record.attributes?.['from-session'] === active.sessionId)\n : undefined;\n following.push({\n workItemId: item.id,\n label,\n title: item.title,\n stage,\n worker: active\n ? {\n role: active.role,\n sessionId: active.sessionId,\n state: running ? 'running' : lastFromWorker?.kind === 'blocked' ? 'parked' : 'idle',\n quietSince: quiet ? quiet.toISOString() : null,\n }\n : null,\n lastReport: report(last),\n active: running && !terminal,\n });\n }\n\n // Active entries first, then within the cap; the rest collapse into \"+N more\".\n const ordered = [\n ...sideSessions.map(row => ({ section: 'side' as const, row })),\n ...following.map(row => ({ section: 'following' as const, row })),\n ].sort((a, b) => Number(b.row.active) - Number(a.row.active));\n const shown = ordered.slice(0, FACTORY_SESSIONS_MAX_ROWS);\n const strip = <T extends { active: boolean }>({ active: _active, ...row }: T) => row;\n return {\n sideSessions: shown.flatMap(entry =>\n entry.section === 'side' ? [strip(entry.row as SideSessionRow & { active: boolean })] : [],\n ),\n following: shown.flatMap(entry =>\n entry.section === 'following' ? [strip(entry.row as FollowedRow & { active: boolean })] : [],\n ),\n more: ordered.length - shown.length,\n };\n }\n\n #render(state: SessionsState): string {\n const lastReport = (last: LastReport | null) => (last ? `; last report ${last.kind} at ${last.at}` : '');\n const lines: string[] = [];\n if (state.sideSessions.length > 0) {\n lines.push('Side sessions you started:');\n for (const row of state.sideSessions) {\n lines.push(\n `- \"${escapeText(row.title)}\" (session ${row.sessionId}): ${row.state}${row.muted ? ' (muted)' : ''}${lastReport(row.lastReport)}`,\n );\n }\n }\n if (state.following.length > 0) {\n lines.push('Work items you follow:');\n for (const row of state.following) {\n const worker = row.worker\n ? `; ${escapeText(row.worker.role)} session ${row.worker.sessionId} ${row.worker.state}${row.worker.quietSince ? ` but quiet since ${row.worker.quietSince}` : ''}`\n : '';\n lines.push(\n `- ${escapeText(row.label)} \"${escapeText(row.title)}\": ${escapeText(row.stage)}${worker}${lastReport(row.lastReport)}`,\n );\n }\n }\n if (state.more > 0) lines.push(`+${state.more} more`);\n lines.push(\n 'Message any of these sessions with factory_signal_session; read full reports with notification_inbox; list side sessions with factory_list_side_sessions; on a turn a person started, mute one with factory_unsubscribe_session.',\n );\n return lines.join('\\n');\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sessions-state-processor.js","names":["newest","report"],"sources":["../../src/tools/sessions-state-processor.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport type { NotificationsStorage } from '@mastra/core/notifications';\nimport type { ComputeStateSignalArgs, ComputeStateSignalResult, Processor } from '@mastra/core/processors';\n\nimport { boardForWorkItem } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/registry.js';\nimport type { SourceControlSessionLookup } from '../session/factory-session.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { resolveFactoryToolScope } from './scope.js';\nimport type { FactoryToolScope, ResolveFactoryToolScopeOptions } from './scope.js';\nimport {\n itemLabel,\n lastReport as report,\n listSessionNotifications,\n listSideSessions,\n newestNotification as newest,\n} from './side-sessions.js';\nimport type { FactorySessionRunState, LastReport } from './side-sessions.js';\n\nconst STATE_ID = 'factory-sessions';\n/** Finished side sessions and items that reached a terminal stage drop out after this long. */\nexport const FACTORY_SESSIONS_MAX_AGE_MS = 24 * 60 * 60 * 1000;\n/** Asks a long run to show it is still moving; a silent half hour reads the same as a stuck one. */\nconst PROGRESS_ASK = 'On long work, send a progress report after each plan step.';\n\n/** A running worker whose thread has not moved for this long is shown as quiet, so a stuck run stands out. */\nexport const FACTORY_SESSIONS_QUIET_MS = 10 * 60 * 1000;\n/** Rows shown before the rest collapse into \"+N more\". */\nexport const FACTORY_SESSIONS_MAX_ROWS = 20;\nconst EMPTY_CACHE_KEY = 'empty';\n\nexport type { FactorySessionRunState } from './side-sessions.js';\n\nexport interface FactorySessionsStateDependencies {\n authEnabled: boolean;\n sessions: Pick<SourceControlSessionLookup, 'getBySessionId' | 'listByOrigin'>;\n projects: ResolveFactoryToolScopeOptions['projects'];\n workItems: Pick<\n WorkItemsStorage,\n | 'hasRunBindingForSession'\n | 'listSessionSubscriptions'\n | 'getForProject'\n | 'listRunBindings'\n | 'findActiveRunBindingForSession'\n | 'listWorkItemSubscribers'\n >;\n boards: BoardRegistry;\n /** Whether the session's agent is mid-run in this process; `idle` when it isn't live here. */\n isRunning: (sessionId: string) => Promise<boolean>;\n /** When a thread last changed; tells a long run that is still moving from one that stopped. */\n lastActivity?: (threadId: string) => Promise<Date | null>;\n notifications: () => Promise<Pick<NotificationsStorage, 'listNotifications'> | null>;\n now?: () => number;\n}\n\ninterface SideSessionRow {\n sessionId: string;\n title: string;\n state: FactorySessionRunState;\n lastReport: LastReport | null;\n muted: boolean;\n}\n\ninterface FollowedRow {\n workItemId: string;\n label: string;\n title: string;\n stage: string;\n worker: { role: string; sessionId: string; state: FactorySessionRunState; quietSince: string | null } | null;\n lastReport: LastReport | null;\n}\n\ninterface SessionsState {\n sideSessions: SideSessionRow[];\n following: FollowedRow[];\n more: number;\n}\n\nfunction escapeText(value: string): string {\n return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');\n}\n\nfunction cacheKeyFor(value: unknown): string {\n return `sessions:${createHash('sha256').update(JSON.stringify(value)).digest('hex').slice(0, 16)}`;\n}\n\n/**\n * The `<factory-sessions>` state signal. A user session sees the side sessions\n * it started and the work items it follows; a side session or item worker sees\n * who it reports to. Sessions it only messaged never appear. Silent until there\n * is something to show; when the last entry ages out it sends one final empty\n * snapshot, then stays silent.\n */\nexport class FactorySessionsStateProcessor implements Processor<'factory-sessions'> {\n readonly id = STATE_ID;\n readonly stateId = STATE_ID;\n readonly #deps: FactorySessionsStateDependencies;\n\n constructor(deps: FactorySessionsStateDependencies) {\n this.#deps = deps;\n }\n\n async computeStateSignal(args: ComputeStateSignalArgs): Promise<ComputeStateSignalResult> {\n const scope = await resolveFactoryToolScope({\n requestContext: args.requestContext!,\n authEnabled: this.#deps.authEnabled,\n sessions: this.#deps.sessions,\n projects: this.#deps.projects,\n workItems: this.#deps.workItems,\n });\n if (!scope) return;\n\n const hasBase = Boolean(args.lastSnapshot) && args.contextWindow.hasSnapshot;\n const previousKey = hasBase\n ? (args.tracking?.currentCacheKey ?? (args.lastSnapshot?.metadata?.state as { cacheKey?: string })?.cacheKey)\n : undefined;\n const unchanged = (cacheKey: string) => previousKey === cacheKey;\n // Silence rule: one final empty snapshot replaces a non-empty one, then nothing.\n const cleared = () => hasBase && previousKey !== undefined && previousKey !== EMPTY_CACHE_KEY;\n\n if (scope.kind === 'worker') {\n const contents = await this.#workerLine(scope);\n if (!contents) {\n return cleared()\n ? this.#signal(EMPTY_CACHE_KEY, 'This session is not reporting on any work now.', {}, { entries: 0 })\n : undefined;\n }\n const cacheKey = cacheKeyFor(contents);\n if (unchanged(cacheKey)) return;\n return this.#signal(cacheKey, escapeText(contents), { worker: contents }, { entries: 1 });\n }\n\n const state = await this.#userState(scope);\n if (state.sideSessions.length === 0 && state.following.length === 0) {\n if (!cleared()) return;\n return this.#signal(EMPTY_CACHE_KEY, 'No sub-sessions or followed work items.', state, { entries: 0 });\n }\n const cacheKey = cacheKeyFor(state);\n if (unchanged(cacheKey)) return;\n return this.#signal(cacheKey, this.#render(state), state, {\n sideSessions: state.sideSessions.length,\n following: state.following.length,\n });\n }\n\n #signal(cacheKey: string, contents: string, value: unknown, attributes: Record<string, number>) {\n return {\n id: STATE_ID,\n cacheKey,\n mode: 'snapshot' as const,\n tagName: 'factory-sessions',\n contents,\n value: { sessions: value },\n attributes,\n metadata: { value: { sessions: value } },\n };\n }\n\n async #workerLine(scope: FactoryToolScope): Promise<string | null> {\n if (scope.origin) {\n return `Started by session ${scope.origin.sessionId} (thread ${scope.origin.threadId}). Report back with factory_message_origin. ${PROGRESS_ASK}`;\n }\n const binding = await this.#deps.workItems.findActiveRunBindingForSession({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n sessionId: scope.sessionId,\n });\n if (!binding) return null;\n const item = await this.#deps.workItems.getForProject(scope.orgId, scope.factoryProjectId, binding.workItemId);\n const subscribers = await this.#deps.workItems.listWorkItemSubscribers({\n orgId: scope.orgId,\n workItemId: binding.workItemId,\n });\n const following = subscribers.filter(subscriber => subscriber.sessionId !== scope.sessionId).length;\n // Nobody to report to: nothing worth showing (most board-started workers).\n if (following === 0) return null;\n const label = item ? itemLabel(item) : binding.workItemId;\n return `Working on ${label} as ${binding.role}. Reports go to ${following} following session${following === 1 ? '' : 's'}. ${PROGRESS_ASK}`;\n }\n\n async #userState(scope: FactoryToolScope): Promise<SessionsState> {\n const now = this.#deps.now?.() ?? Date.now();\n const notifications = await this.#deps.notifications();\n const records = await listSessionNotifications(notifications, scope.threadId);\n\n const sideSessions: Array<SideSessionRow & { active: boolean }> = [];\n const listed = await listSideSessions(\n {\n sessions: this.#deps.sessions,\n workItems: this.#deps.workItems,\n isRunning: this.#deps.isRunning,\n notifications,\n },\n scope,\n { records },\n );\n for (const row of listed) {\n if (row.state !== 'running' && now - Date.parse(row.lastActivityAt) > FACTORY_SESSIONS_MAX_AGE_MS) continue;\n sideSessions.push({\n sessionId: row.sessionId,\n title: row.title,\n state: row.state,\n lastReport: row.lastReport,\n muted: row.mutedAt !== null,\n active: row.state !== 'idle',\n });\n }\n\n const subscriptions = await this.#deps.workItems.listSessionSubscriptions({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n sessionId: scope.sessionId,\n });\n const following: Array<FollowedRow & { active: boolean }> = [];\n for (const subscription of subscriptions) {\n const item = await this.#deps.workItems.getForProject(\n scope.orgId,\n scope.factoryProjectId,\n subscription.workItemId,\n );\n if (!item) continue;\n const stage = item.stages[0] ?? 'unknown';\n const terminal = this.#deps.boards.get(boardForWorkItem(item))?.isTerminal(stage) ?? false;\n if (terminal && now - item.updatedAt.getTime() > FACTORY_SESSIONS_MAX_AGE_MS) continue;\n const bindings = await this.#deps.workItems.listRunBindings(scope.orgId, scope.factoryProjectId, item.id);\n const active = bindings.find(binding => binding.status === 'active');\n const label = itemLabel(item);\n const last = newest(\n records,\n record =>\n record.attributes?.['work-item'] === label || (record.kind === 'stage' && record.sourceId === item.id),\n );\n const running = active ? await this.#deps.isRunning(active.sessionId) : false;\n const lastActivity = active && running ? await this.#deps.lastActivity?.(active.threadId) : null;\n // Only a quiet run is shown with its time; a moving one would change the snapshot on every step.\n const quiet = lastActivity && now - lastActivity.getTime() >= FACTORY_SESSIONS_QUIET_MS ? lastActivity : null;\n const lastFromWorker = active\n ? newest(records, record => record.attributes?.['from-session'] === active.sessionId)\n : undefined;\n following.push({\n workItemId: item.id,\n label,\n title: item.title,\n stage,\n worker: active\n ? {\n role: active.role,\n sessionId: active.sessionId,\n state: running ? 'running' : lastFromWorker?.kind === 'blocked' ? 'parked' : 'idle',\n quietSince: quiet ? quiet.toISOString() : null,\n }\n : null,\n lastReport: report(last),\n active: running && !terminal,\n });\n }\n\n // Active entries first, then within the cap; the rest collapse into \"+N more\".\n const ordered = [\n ...sideSessions.map(row => ({ section: 'side' as const, row })),\n ...following.map(row => ({ section: 'following' as const, row })),\n ].sort((a, b) => Number(b.row.active) - Number(a.row.active));\n const shown = ordered.slice(0, FACTORY_SESSIONS_MAX_ROWS);\n const strip = <T extends { active: boolean }>({ active: _active, ...row }: T) => row;\n return {\n sideSessions: shown.flatMap(entry =>\n entry.section === 'side' ? [strip(entry.row as SideSessionRow & { active: boolean })] : [],\n ),\n following: shown.flatMap(entry =>\n entry.section === 'following' ? [strip(entry.row as FollowedRow & { active: boolean })] : [],\n ),\n more: ordered.length - shown.length,\n };\n }\n\n #render(state: SessionsState): string {\n const lastReport = (last: LastReport | null) => (last ? `; last report ${last.kind} at ${last.at}` : '');\n const lines: string[] = [];\n if (state.sideSessions.length > 0) {\n lines.push('Side sessions you started:');\n for (const row of state.sideSessions) {\n lines.push(\n `- \"${escapeText(row.title)}\" (session ${row.sessionId}): ${row.state}${row.muted ? ' (muted)' : ''}${lastReport(row.lastReport)}`,\n );\n }\n }\n if (state.following.length > 0) {\n lines.push('Work items you follow:');\n for (const row of state.following) {\n const worker = row.worker\n ? `; ${escapeText(row.worker.role)} session ${row.worker.sessionId} ${row.worker.state}${row.worker.quietSince ? ` but quiet since ${row.worker.quietSince}` : ''}`\n : '';\n lines.push(\n `- ${escapeText(row.label)} \"${escapeText(row.title)}\": ${escapeText(row.stage)}${worker}${lastReport(row.lastReport)}`,\n );\n }\n }\n if (state.more > 0) lines.push(`+${state.more} more`);\n lines.push(\n 'Message any of these sessions with factory_signal_session; read full reports with notification_inbox; list side sessions with factory_list_side_sessions; on a turn a person started, mute one with factory_unsubscribe_session.',\n );\n return lines.join('\\n');\n }\n}\n"],"mappings":"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"}
|
|
@@ -49,5 +49,5 @@ export declare function listSideSessions(deps: SideSessionDependencies, scope: P
|
|
|
49
49
|
records?: NotificationRecord[];
|
|
50
50
|
}): Promise<SideSession[]>;
|
|
51
51
|
/** The model a session's thread uses: the live session's selection, else the persisted one. */
|
|
52
|
-
export declare function sessionModel(controller: Pick<FactoryDeliveryController, '
|
|
52
|
+
export declare function sessionModel(controller: Pick<FactoryDeliveryController, 'getSession' | 'queryThreads' | 'queryThreadById'>, sessions: Parameters<typeof resolveSessionThread>[0]['sessions'], workItems: Parameters<typeof resolveSessionThread>[0]['workItems'], sessionId: string): Promise<string | null>;
|
|
53
53
|
//# sourceMappingURL=side-sessions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"side-sessions.d.ts","sourceRoot":"","sources":["../../src/tools/side-sessions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAE3F,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAChF,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC3F,OAAO,EAAuC,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACzF,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAE9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,4EAA4E;AAC5E,eAAO,MAAM,8BAA8B,MAAM,CAAC;AAElD,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEnE,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,sBAAsB,CAAC;IAC9B,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,oFAAoF;IACpF,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,kEAAkE;IAClE,QAAQ,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9D,uDAAuD;IACvD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,6EAA6E;IAC7E,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,IAAI,CAAC,0BAA0B,EAAE,cAAc,CAAC,CAAC;IAC3D,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,gCAAgC,GAAG,eAAe,CAAC,CAAC;IACtF,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,aAAa,EAAE,IAAI,CAAC,oBAAoB,EAAE,mBAAmB,CAAC,GAAG,IAAI,CAAC;IACtE,0EAA0E;IAC1E,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACvD;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,kBAAkB,GAAG,SAAS,GAAG,UAAU,GAAG,IAAI,CAEpF;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,kBAAkB,EAAE,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,OAAO,kCAM/G;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAGnD;AAED,wBAAsB,wBAAwB,CAC5C,aAAa,EAAE,IAAI,CAAC,oBAAoB,EAAE,mBAAmB,CAAC,GAAG,IAAI,EACrE,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAQ/B;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,uBAAuB,EAC7B,KAAK,EAAE,IAAI,CAAC,gBAAgB,EAAE,OAAO,GAAG,kBAAkB,GAAG,WAAW,GAAG,UAAU,CAAC,EACtF,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAA;CAAO,GAC/C,OAAO,CAAC,WAAW,EAAE,CAAC,CA6BxB;AAED,+FAA+F;AAC/F,wBAAsB,YAAY,CAChC,UAAU,EAAE,IAAI,CAAC,yBAAyB,EAAE,
|
|
1
|
+
{"version":3,"file":"side-sessions.d.ts","sourceRoot":"","sources":["../../src/tools/side-sessions.ts"],"names":[],"mappings":"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"}
|
|
@@ -65,7 +65,7 @@ async function listSideSessions(deps, scope, options = {}) {
|
|
|
65
65
|
}
|
|
66
66
|
/** The model a session's thread uses: the live session's selection, else the persisted one. */
|
|
67
67
|
async function sessionModel(controller, sessions, workItems, sessionId) {
|
|
68
|
-
const liveModel = (await controller.
|
|
68
|
+
const liveModel = (await controller.getSession(sessionId))?.model.get();
|
|
69
69
|
if (liveModel) return liveModel;
|
|
70
70
|
const threadId = await resolveSessionThread({
|
|
71
71
|
controller,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"side-sessions.js","names":[],"sources":["../../src/tools/side-sessions.ts"],"sourcesContent":["import type { NotificationRecord, NotificationsStorage } from '@mastra/core/notifications';\n\nimport type { SourceControlSessionLookup } from '../session/factory-session.js';\nimport type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { FACTORY_SESSION_NOTIFICATION_SOURCE, resolveSessionThread } from './deliver.js';\nimport type { FactoryDeliveryController } from './deliver.js';\nimport { workItemNumber } from './read-tools.js';\nimport type { FactoryToolScope } from './scope.js';\n\n/** Recent notifications scanned to find each side session's last report. */\nexport const SIDE_SESSION_NOTIFICATION_SCAN = 100;\n\nexport type FactorySessionRunState = 'running' | 'idle' | 'parked';\n\nexport interface LastReport {\n kind: string;\n at: string;\n}\n\nexport interface SideSession {\n sessionId: string;\n title: string;\n state: FactorySessionRunState;\n lastReport: LastReport | null;\n /** When the origin muted this session's reports; null while it reports normally. */\n mutedAt: string | null;\n /** The work item this session is bound to as a worker, if any. */\n workItem: { id: string; label: string; stage: string } | null;\n /** The model the session's thread uses, when known. */\n model: string | null;\n /** Last report time, or the session's last update when it never reported. */\n lastActivityAt: string;\n}\n\nexport interface SideSessionDependencies {\n sessions: Pick<SourceControlSessionLookup, 'listByOrigin'>;\n workItems: Pick<WorkItemsStorage, 'findActiveRunBindingForSession' | 'getForProject'>;\n isRunning: (sessionId: string) => Promise<boolean>;\n notifications: Pick<NotificationsStorage, 'listNotifications'> | null;\n /** Resolves the model a side session uses; omit to leave `model` null. */\n model?: (sessionId: string) => Promise<string | null>;\n}\n\nexport function lastReport(record: NotificationRecord | undefined): LastReport | null {\n return record ? { kind: record.kind, at: record.createdAt.toISOString() } : null;\n}\n\nexport function newestNotification(records: NotificationRecord[], match: (record: NotificationRecord) => boolean) {\n let found: NotificationRecord | undefined;\n for (const record of records) {\n if (match(record) && (!found || record.createdAt > found.createdAt)) found = record;\n }\n return found;\n}\n\nexport function itemLabel(item: WorkItemRow): string {\n const number = workItemNumber(item);\n return number === null ? item.id : `#${number}`;\n}\n\nexport async function listSessionNotifications(\n notifications: Pick<NotificationsStorage, 'listNotifications'> | null,\n threadId: string,\n): Promise<NotificationRecord[]> {\n return notifications\n ? notifications.listNotifications({\n threadId,\n source: FACTORY_SESSION_NOTIFICATION_SOURCE,\n limit: SIDE_SESSION_NOTIFICATION_SCAN,\n })\n : [];\n}\n\n/**\n * Every side session the scope's session started in its Factory project,\n * newest activity first. Callers apply their own age and row caps.\n */\nexport async function listSideSessions(\n deps: SideSessionDependencies,\n scope: Pick<FactoryToolScope, 'orgId' | 'factoryProjectId' | 'sessionId' | 'threadId'>,\n options: { records?: NotificationRecord[] } = {},\n): Promise<SideSession[]> {\n const records = options.records ?? (await listSessionNotifications(deps.notifications, scope.threadId));\n const rows = await deps.sessions.listByOrigin({ orgId: scope.orgId, originSessionId: scope.sessionId });\n const sideSessions: SideSession[] = [];\n for (const row of rows) {\n if (row.factoryProjectId !== scope.factoryProjectId) continue;\n const last = newestNotification(records, record => record.attributes?.['from-session'] === row.sessionId);\n const running = await deps.isRunning(row.sessionId);\n const state: FactorySessionRunState = running ? 'running' : last?.kind === 'blocked' ? 'parked' : 'idle';\n const binding = await deps.workItems.findActiveRunBindingForSession({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n sessionId: row.sessionId,\n });\n const item = binding\n ? await deps.workItems.getForProject(scope.orgId, scope.factoryProjectId, binding.workItemId)\n : null;\n sideSessions.push({\n sessionId: row.sessionId,\n title: row.title ?? row.sessionId,\n state,\n lastReport: lastReport(last),\n mutedAt: row.originMutedAt?.toISOString() ?? null,\n workItem: item ? { id: item.id, label: itemLabel(item), stage: item.stages[0] ?? 'unknown' } : null,\n model: deps.model ? await deps.model(row.sessionId) : null,\n lastActivityAt: (last?.createdAt ?? row.updatedAt).toISOString(),\n });\n }\n return sideSessions.sort((left, right) => right.lastActivityAt.localeCompare(left.lastActivityAt));\n}\n\n/** The model a session's thread uses: the live session's selection, else the persisted one. */\nexport async function sessionModel(\n controller: Pick<FactoryDeliveryController, '
|
|
1
|
+
{"version":3,"file":"side-sessions.js","names":[],"sources":["../../src/tools/side-sessions.ts"],"sourcesContent":["import type { NotificationRecord, NotificationsStorage } from '@mastra/core/notifications';\n\nimport type { SourceControlSessionLookup } from '../session/factory-session.js';\nimport type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { FACTORY_SESSION_NOTIFICATION_SOURCE, resolveSessionThread } from './deliver.js';\nimport type { FactoryDeliveryController } from './deliver.js';\nimport { workItemNumber } from './read-tools.js';\nimport type { FactoryToolScope } from './scope.js';\n\n/** Recent notifications scanned to find each side session's last report. */\nexport const SIDE_SESSION_NOTIFICATION_SCAN = 100;\n\nexport type FactorySessionRunState = 'running' | 'idle' | 'parked';\n\nexport interface LastReport {\n kind: string;\n at: string;\n}\n\nexport interface SideSession {\n sessionId: string;\n title: string;\n state: FactorySessionRunState;\n lastReport: LastReport | null;\n /** When the origin muted this session's reports; null while it reports normally. */\n mutedAt: string | null;\n /** The work item this session is bound to as a worker, if any. */\n workItem: { id: string; label: string; stage: string } | null;\n /** The model the session's thread uses, when known. */\n model: string | null;\n /** Last report time, or the session's last update when it never reported. */\n lastActivityAt: string;\n}\n\nexport interface SideSessionDependencies {\n sessions: Pick<SourceControlSessionLookup, 'listByOrigin'>;\n workItems: Pick<WorkItemsStorage, 'findActiveRunBindingForSession' | 'getForProject'>;\n isRunning: (sessionId: string) => Promise<boolean>;\n notifications: Pick<NotificationsStorage, 'listNotifications'> | null;\n /** Resolves the model a side session uses; omit to leave `model` null. */\n model?: (sessionId: string) => Promise<string | null>;\n}\n\nexport function lastReport(record: NotificationRecord | undefined): LastReport | null {\n return record ? { kind: record.kind, at: record.createdAt.toISOString() } : null;\n}\n\nexport function newestNotification(records: NotificationRecord[], match: (record: NotificationRecord) => boolean) {\n let found: NotificationRecord | undefined;\n for (const record of records) {\n if (match(record) && (!found || record.createdAt > found.createdAt)) found = record;\n }\n return found;\n}\n\nexport function itemLabel(item: WorkItemRow): string {\n const number = workItemNumber(item);\n return number === null ? item.id : `#${number}`;\n}\n\nexport async function listSessionNotifications(\n notifications: Pick<NotificationsStorage, 'listNotifications'> | null,\n threadId: string,\n): Promise<NotificationRecord[]> {\n return notifications\n ? notifications.listNotifications({\n threadId,\n source: FACTORY_SESSION_NOTIFICATION_SOURCE,\n limit: SIDE_SESSION_NOTIFICATION_SCAN,\n })\n : [];\n}\n\n/**\n * Every side session the scope's session started in its Factory project,\n * newest activity first. Callers apply their own age and row caps.\n */\nexport async function listSideSessions(\n deps: SideSessionDependencies,\n scope: Pick<FactoryToolScope, 'orgId' | 'factoryProjectId' | 'sessionId' | 'threadId'>,\n options: { records?: NotificationRecord[] } = {},\n): Promise<SideSession[]> {\n const records = options.records ?? (await listSessionNotifications(deps.notifications, scope.threadId));\n const rows = await deps.sessions.listByOrigin({ orgId: scope.orgId, originSessionId: scope.sessionId });\n const sideSessions: SideSession[] = [];\n for (const row of rows) {\n if (row.factoryProjectId !== scope.factoryProjectId) continue;\n const last = newestNotification(records, record => record.attributes?.['from-session'] === row.sessionId);\n const running = await deps.isRunning(row.sessionId);\n const state: FactorySessionRunState = running ? 'running' : last?.kind === 'blocked' ? 'parked' : 'idle';\n const binding = await deps.workItems.findActiveRunBindingForSession({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n sessionId: row.sessionId,\n });\n const item = binding\n ? await deps.workItems.getForProject(scope.orgId, scope.factoryProjectId, binding.workItemId)\n : null;\n sideSessions.push({\n sessionId: row.sessionId,\n title: row.title ?? row.sessionId,\n state,\n lastReport: lastReport(last),\n mutedAt: row.originMutedAt?.toISOString() ?? null,\n workItem: item ? { id: item.id, label: itemLabel(item), stage: item.stages[0] ?? 'unknown' } : null,\n model: deps.model ? await deps.model(row.sessionId) : null,\n lastActivityAt: (last?.createdAt ?? row.updatedAt).toISOString(),\n });\n }\n return sideSessions.sort((left, right) => right.lastActivityAt.localeCompare(left.lastActivityAt));\n}\n\n/** The model a session's thread uses: the live session's selection, else the persisted one. */\nexport async function sessionModel(\n controller: Pick<FactoryDeliveryController, 'getSession' | 'queryThreads' | 'queryThreadById'>,\n sessions: Parameters<typeof resolveSessionThread>[0]['sessions'],\n workItems: Parameters<typeof resolveSessionThread>[0]['workItems'],\n sessionId: string,\n): Promise<string | null> {\n const live = await controller.getSession(sessionId);\n const liveModel = live?.model.get();\n if (liveModel) return liveModel;\n const threadId = await resolveSessionThread({ controller, sessions, workItems, sessionId });\n if (!threadId) return null;\n const thread = await controller.queryThreadById({ threadId });\n const persisted = thread?.metadata?.currentModelId;\n return typeof persisted === 'string' && persisted ? persisted : null;\n}\n"],"mappings":";;;;AAUA,MAAa,iCAAiC;AAiC9C,SAAgB,WAAW,QAA2D;CACpF,OAAO,SAAS;EAAE,MAAM,OAAO;EAAM,IAAI,OAAO,UAAU,YAAY;CAAE,IAAI;AAC9E;AAEA,SAAgB,mBAAmB,SAA+B,OAAgD;CAChH,IAAI;CACJ,KAAK,MAAM,UAAU,SACnB,IAAI,MAAM,MAAM,MAAM,CAAC,SAAS,OAAO,YAAY,MAAM,YAAY,QAAQ;CAE/E,OAAO;AACT;AAEA,SAAgB,UAAU,MAA2B;CACnD,MAAM,SAAS,eAAe,IAAI;CAClC,OAAO,WAAW,OAAO,KAAK,KAAK,IAAI;AACzC;AAEA,eAAsB,yBACpB,eACA,UAC+B;CAC/B,OAAO,gBACH,cAAc,kBAAkB;EAC9B;EACA,QAAQ;EACR,OAAA;CACF,CAAC,IACD,CAAC;AACP;;;;;AAMA,eAAsB,iBACpB,MACA,OACA,UAA8C,CAAC,GACvB;CACxB,MAAM,UAAU,QAAQ,WAAY,MAAM,yBAAyB,KAAK,eAAe,MAAM,QAAQ;CACrG,MAAM,OAAO,MAAM,KAAK,SAAS,aAAa;EAAE,OAAO,MAAM;EAAO,iBAAiB,MAAM;CAAU,CAAC;CACtG,MAAM,eAA8B,CAAC;CACrC,KAAK,MAAM,OAAO,MAAM;EACtB,IAAI,IAAI,qBAAqB,MAAM,kBAAkB;EACrD,MAAM,OAAO,mBAAmB,UAAS,WAAU,OAAO,aAAa,oBAAoB,IAAI,SAAS;EAExG,MAAM,QAAgC,MADhB,KAAK,UAAU,IAAI,SAAS,IACF,YAAY,MAAM,SAAS,YAAY,WAAW;EAClG,MAAM,UAAU,MAAM,KAAK,UAAU,+BAA+B;GAClE,OAAO,MAAM;GACb,kBAAkB,MAAM;GACxB,WAAW,IAAI;EACjB,CAAC;EACD,MAAM,OAAO,UACT,MAAM,KAAK,UAAU,cAAc,MAAM,OAAO,MAAM,kBAAkB,QAAQ,UAAU,IAC1F;EACJ,aAAa,KAAK;GAChB,WAAW,IAAI;GACf,OAAO,IAAI,SAAS,IAAI;GACxB;GACA,YAAY,WAAW,IAAI;GAC3B,SAAS,IAAI,eAAe,YAAY,KAAK;GAC7C,UAAU,OAAO;IAAE,IAAI,KAAK;IAAI,OAAO,UAAU,IAAI;IAAG,OAAO,KAAK,OAAO,MAAM;GAAU,IAAI;GAC/F,OAAO,KAAK,QAAQ,MAAM,KAAK,MAAM,IAAI,SAAS,IAAI;GACtD,iBAAiB,MAAM,aAAa,IAAI,UAAA,CAAW,YAAY;EACjE,CAAC;CACH;CACA,OAAO,aAAa,MAAM,MAAM,UAAU,MAAM,eAAe,cAAc,KAAK,cAAc,CAAC;AACnG;;AAGA,eAAsB,aACpB,YACA,UACA,WACA,WACwB;CAExB,MAAM,aAAY,MADC,WAAW,WAAW,SAAS,EAAA,EAC1B,MAAM,IAAI;CAClC,IAAI,WAAW,OAAO;CACtB,MAAM,WAAW,MAAM,qBAAqB;EAAE;EAAY;EAAU;EAAW;CAAU,CAAC;CAC1F,IAAI,CAAC,UAAU,OAAO;CAEtB,MAAM,aAAY,MADG,WAAW,gBAAgB,EAAE,SAAS,CAAC,EAAA,EAClC,UAAU;CACpC,OAAO,OAAO,cAAc,YAAY,YAAY,YAAY;AAClE"}
|
|
@@ -22,11 +22,19 @@ export declare function workItemThreadTitle(item: Pick<WorkItemBranchInput, 'sou
|
|
|
22
22
|
* The git branch an item's runs and sessions share, one grammar for both sides
|
|
23
23
|
* of the wire: the dispatcher names autonomous run branches with it and the
|
|
24
24
|
* board opens card sessions on it, so both converge on one checkout per item.
|
|
25
|
-
*
|
|
26
|
-
*
|
|
25
|
+
* Provider numbers are only unique within a repository, so GitHub and GitLab
|
|
26
|
+
* branches carry the item suffix as well. Cards without a provider identity
|
|
27
|
+
* (manual, Slack) and cards whose metadata lost their identifier fall back to
|
|
28
|
+
* the id-derived branch.
|
|
27
29
|
*/
|
|
28
30
|
export declare function workItemBranch(item: WorkItemBranchInput): string;
|
|
29
|
-
/** The
|
|
31
|
+
/** The pre-suffix GitHub branch, for reusing sessions opened before card names were unique. */
|
|
32
|
+
export declare function legacyGithubWorkItemBranch(branch: string): string | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* The pull request a `factory/pr-<number>-<item suffix>` branch was named after, the
|
|
35
|
+
* inverse of {@link workItemBranch}. Branches from before the suffix (`factory/pr-<number>`)
|
|
36
|
+
* still parse so existing checkouts can resolve their PR.
|
|
37
|
+
*/
|
|
30
38
|
export declare function pullRequestNumberFromBranch(branch: string): number | undefined;
|
|
31
39
|
/** The GitLab MR IID encoded in Factory's collision-resistant review branch. */
|
|
32
40
|
export declare function mergeRequestNumberFromBranch(branch: string): number | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"work-item-branch.d.ts","sourceRoot":"","sources":["../src/work-item-branch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAEnF;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAC5B,cAAc,GACd,WAAW,GACX,cAAc,GACd,WAAW,GACX,cAAc,GACd,YAAY,GACZ,sBAAsB,GACtB,cAAc,GACd,QAAQ,CAAC;AAEb,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,oBAAoB,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC3C;AAED,qFAAqF;AACrF,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,sBAAsB,GAAG,IAAI,GAAG,SAAS,GAAG,oBAAoB,CAYpH;AAOD,mGAAmG;AACnG,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,mBAAmB,EAAE,QAAQ,GAAG,UAAU,CAAC,GAAG,MAAM,GAAG,SAAS,CAOzG;AAED,yHAAyH;AACzH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,IAAI,CAAC,mBAAmB,EAAE,QAAQ,GAAG,UAAU,CAAC,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GACzE,MAAM,CAKR;
|
|
1
|
+
{"version":3,"file":"work-item-branch.d.ts","sourceRoot":"","sources":["../src/work-item-branch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAEnF;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAC5B,cAAc,GACd,WAAW,GACX,cAAc,GACd,WAAW,GACX,cAAc,GACd,YAAY,GACZ,sBAAsB,GACtB,cAAc,GACd,QAAQ,CAAC;AAEb,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,oBAAoB,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC3C;AAED,qFAAqF;AACrF,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,sBAAsB,GAAG,IAAI,GAAG,SAAS,GAAG,oBAAoB,CAYpH;AAOD,mGAAmG;AACnG,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,mBAAmB,EAAE,QAAQ,GAAG,UAAU,CAAC,GAAG,MAAM,GAAG,SAAS,CAOzG;AAED,yHAAyH;AACzH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,IAAI,CAAC,mBAAmB,EAAE,QAAQ,GAAG,UAAU,CAAC,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GACzE,MAAM,CAKR;AAgBD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,mBAAmB,GAAG,MAAM,CA+BhE;AAED,+FAA+F;AAC/F,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG7E;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI9E;AAED,gFAAgF;AAChF,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI/E"}
|