@mastra/factory 0.24.0-alpha.1 → 0.24.0-alpha.10
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 +106 -4
- package/dist/auth.d.ts +14 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +45 -9
- package/dist/auth.js.map +1 -1
- package/dist/boards/review.d.ts.map +1 -1
- package/dist/boards/review.js +21 -1
- package/dist/boards/review.js.map +1 -1
- package/dist/boards/work-transition-policy.d.ts.map +1 -1
- package/dist/boards/work-transition-policy.js +12 -1
- package/dist/boards/work-transition-policy.js.map +1 -1
- package/dist/boards/work.d.ts +14 -0
- package/dist/boards/work.d.ts.map +1 -1
- package/dist/boards/work.js +70 -7
- package/dist/boards/work.js.map +1 -1
- package/dist/capabilities/version-control.d.ts +26 -0
- package/dist/capabilities/version-control.d.ts.map +1 -1
- package/dist/factory.d.ts +8 -0
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +354 -27
- package/dist/factory.js.map +1 -1
- package/dist/integrations/base.d.ts +1 -1
- package/dist/integrations/base.d.ts.map +1 -1
- package/dist/integrations/github/default-rules.d.ts.map +1 -1
- package/dist/integrations/github/default-rules.js +1 -16
- package/dist/integrations/github/default-rules.js.map +1 -1
- package/dist/integrations/github/integration.d.ts.map +1 -1
- package/dist/integrations/github/integration.js +37 -1
- package/dist/integrations/github/integration.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 +19 -11
- 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 +26 -22
- package/dist/integrations/github/rules.js.map +1 -1
- package/dist/integrations/github/sandbox-release.d.ts +15 -3
- package/dist/integrations/github/sandbox-release.d.ts.map +1 -1
- package/dist/integrations/github/sandbox-release.js +20 -3
- package/dist/integrations/github/sandbox-release.js.map +1 -1
- package/dist/integrations/github/sandbox.d.ts +2 -3
- package/dist/integrations/github/sandbox.d.ts.map +1 -1
- package/dist/integrations/github/sandbox.js +78 -7
- 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/github/webhook.d.ts +4 -2
- package/dist/integrations/github/webhook.d.ts.map +1 -1
- package/dist/integrations/github/webhook.js +59 -36
- package/dist/integrations/github/webhook.js.map +1 -1
- package/dist/integrations/gitlab/api.d.ts +16 -1
- package/dist/integrations/gitlab/api.d.ts.map +1 -1
- package/dist/integrations/gitlab/api.js +9 -0
- package/dist/integrations/gitlab/api.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.d.ts.map +1 -1
- package/dist/integrations/gitlab/rules.js +28 -21
- 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/gitlab/version-control.d.ts.map +1 -1
- package/dist/integrations/gitlab/version-control.js +37 -3
- package/dist/integrations/gitlab/version-control.js.map +1 -1
- package/dist/integrations/incidentio/intake.d.ts +1 -1
- package/dist/integrations/incidentio/intake.d.ts.map +1 -1
- package/dist/integrations/incidentio/intake.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/platform/connect/routes.d.ts +1 -1
- package/dist/integrations/platform/connect/routes.d.ts.map +1 -1
- package/dist/integrations/platform/connect/routes.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +27 -1
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/integrations/platform/incidentio/integration.d.ts +1 -1
- package/dist/integrations/platform/incidentio/integration.d.ts.map +1 -1
- package/dist/integrations/platform/incidentio/integration.js.map +1 -1
- package/dist/integrations/platform/jira/integration.d.ts +1 -1
- package/dist/integrations/platform/jira/integration.d.ts.map +1 -1
- package/dist/integrations/platform/jira/integration.js.map +1 -1
- package/dist/integrations/platform/linear/event-worker.d.ts +1 -1
- package/dist/integrations/platform/linear/event-worker.d.ts.map +1 -1
- package/dist/integrations/platform/linear/event-worker.js.map +1 -1
- package/dist/integrations/slack/integration.d.ts.map +1 -1
- package/dist/integrations/slack/integration.js +33 -6
- package/dist/integrations/slack/integration.js.map +1 -1
- 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 +137 -64
- package/dist/integrations/slack/slack.js.map +1 -1
- package/dist/integrations/subscription-session.d.ts +14 -0
- package/dist/integrations/subscription-session.d.ts.map +1 -1
- package/dist/integrations/subscription-session.js +30 -16
- package/dist/integrations/subscription-session.js.map +1 -1
- package/dist/mcp/disconnect.d.ts +10 -0
- package/dist/mcp/disconnect.d.ts.map +1 -0
- package/dist/mcp/disconnect.js +44 -0
- package/dist/mcp/disconnect.js.map +1 -0
- package/dist/mcp/routes.d.ts +46 -0
- package/dist/mcp/routes.d.ts.map +1 -0
- package/dist/mcp/routes.js +243 -0
- package/dist/mcp/routes.js.map +1 -0
- package/dist/provider-display-name.d.ts +2 -0
- package/dist/provider-display-name.d.ts.map +1 -0
- package/dist/provider-display-name.js +16 -0
- package/dist/provider-display-name.js.map +1 -0
- package/dist/review-verdict.d.ts +11 -0
- package/dist/review-verdict.d.ts.map +1 -1
- package/dist/review-verdict.js +16 -1
- package/dist/review-verdict.js.map +1 -1
- package/dist/routes/attention-activity.d.ts.map +1 -1
- package/dist/routes/attention-activity.js +5 -2
- package/dist/routes/attention-activity.js.map +1 -1
- package/dist/routes/attention-providers.d.ts +0 -1
- package/dist/routes/attention-providers.d.ts.map +1 -1
- package/dist/routes/attention-providers.js +5 -6
- package/dist/routes/attention-providers.js.map +1 -1
- package/dist/routes/config.d.ts +44 -5
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +181 -58
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/contracts.d.ts +4 -0
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/contracts.js +3 -1
- package/dist/routes/contracts.js.map +1 -1
- package/dist/routes/custom-provider-source.d.ts +8 -0
- package/dist/routes/custom-provider-source.d.ts.map +1 -1
- package/dist/routes/custom-provider-source.js +22 -1
- package/dist/routes/custom-provider-source.js.map +1 -1
- package/dist/routes/oauth.js +1 -1
- 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 +1 -1
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/provider-credentials.d.ts +7 -2
- package/dist/routes/provider-credentials.d.ts.map +1 -1
- package/dist/routes/provider-credentials.js +30 -8
- package/dist/routes/provider-credentials.js.map +1 -1
- package/dist/routes/skills.d.ts +2 -4
- package/dist/routes/skills.d.ts.map +1 -1
- package/dist/routes/skills.js +19 -65
- package/dist/routes/skills.js.map +1 -1
- package/dist/routes/source-control-sessions.d.ts +9 -1
- package/dist/routes/source-control-sessions.d.ts.map +1 -1
- package/dist/routes/source-control-sessions.js +131 -7
- package/dist/routes/source-control-sessions.js.map +1 -1
- package/dist/routes/surface.d.ts +3 -2
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +7 -3
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +60 -120
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/binding-context.js +7 -7
- package/dist/rules/binding-context.js.map +1 -1
- package/dist/rules/dispatch-errors.d.ts.map +1 -1
- package/dist/rules/dispatch-errors.js +12 -0
- package/dist/rules/dispatch-errors.js.map +1 -1
- package/dist/rules/dispatcher.d.ts +8 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +117 -12
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/processor.d.ts.map +1 -1
- package/dist/rules/processor.js +2 -1
- 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.d.ts.map +1 -1
- package/dist/rules/tools.js +82 -3
- package/dist/rules/tools.js.map +1 -1
- package/dist/rules/transition-service.d.ts +17 -0
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +25 -0
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/rules/types.d.ts +2 -4
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/types.js +1 -4
- package/dist/rules/types.js.map +1 -1
- package/dist/sandbox/local-factory-sandbox.d.ts.map +1 -1
- package/dist/sandbox/local-factory-sandbox.js +1 -0
- package/dist/sandbox/local-factory-sandbox.js.map +1 -1
- package/dist/sandbox/session-retirement.d.ts +27 -3
- package/dist/sandbox/session-retirement.d.ts.map +1 -1
- package/dist/sandbox/session-retirement.js +158 -20
- package/dist/sandbox/session-retirement.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/channel-reply-processor.d.ts +36 -0
- package/dist/session/channel-reply-processor.d.ts.map +1 -0
- package/dist/session/channel-reply-processor.js +46 -0
- package/dist/session/channel-reply-processor.js.map +1 -0
- package/dist/session/channel-session-link-processor.d.ts +39 -0
- package/dist/session/channel-session-link-processor.d.ts.map +1 -0
- package/dist/session/channel-session-link-processor.js +100 -0
- package/dist/session/channel-session-link-processor.js.map +1 -0
- package/dist/session/credential-policy.d.ts +1 -1
- package/dist/session/credential-policy.d.ts.map +1 -1
- package/dist/session/credential-policy.js +7 -6
- 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 +19 -1
- package/dist/session/factory-session.d.ts.map +1 -1
- package/dist/session/factory-session.js +5 -2
- 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 +1 -1
- package/dist/session/memory-settings-hydration.js.map +1 -1
- package/dist/session/reply-credential-processor.d.ts +15 -0
- package/dist/session/reply-credential-processor.d.ts.map +1 -0
- package/dist/session/reply-credential-processor.js +38 -0
- package/dist/session/reply-credential-processor.js.map +1 -0
- package/dist/session/resource-migration.d.ts +87 -0
- package/dist/session/resource-migration.d.ts.map +1 -0
- package/dist/session/resource-migration.js +163 -0
- package/dist/session/resource-migration.js.map +1 -0
- package/dist/session/run-audit.d.ts +24 -0
- package/dist/session/run-audit.d.ts.map +1 -1
- package/dist/session/run-audit.js +93 -7
- package/dist/session/run-audit.js.map +1 -1
- package/dist/session/session-threads.d.ts +49 -0
- package/dist/session/session-threads.d.ts.map +1 -0
- package/dist/session/session-threads.js +70 -0
- package/dist/session/session-threads.js.map +1 -0
- package/dist/session/source-control-tools.d.ts +19 -1
- package/dist/session/source-control-tools.d.ts.map +1 -1
- package/dist/session/source-control-tools.js +78 -11
- package/dist/session/source-control-tools.js.map +1 -1
- package/dist/session/thinking-default.d.ts.map +1 -1
- package/dist/session/thinking-default.js +3 -4
- 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/work-item-repository.d.ts +1 -1
- package/dist/session/work-item-repository.d.ts.map +1 -1
- package/dist/session/work-item-repository.js.map +1 -1
- package/dist/skills/service.d.ts +6 -8
- package/dist/skills/service.d.ts.map +1 -1
- package/dist/skills/service.js +3 -3
- package/dist/skills/service.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/domain.js +1 -1
- package/dist/storage/domains/filesystem/base.d.ts +4 -0
- package/dist/storage/domains/filesystem/base.d.ts.map +1 -1
- package/dist/storage/domains/filesystem/base.js +5 -0
- package/dist/storage/domains/filesystem/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/projects/base.d.ts +6 -0
- package/dist/storage/domains/projects/base.d.ts.map +1 -1
- package/dist/storage/domains/projects/base.js +12 -0
- package/dist/storage/domains/projects/base.js.map +1 -1
- package/dist/storage/domains/source-control/base.d.ts +28 -0
- package/dist/storage/domains/source-control/base.d.ts.map +1 -1
- package/dist/storage/domains/source-control/base.js +48 -0
- package/dist/storage/domains/source-control/base.js.map +1 -1
- package/dist/storage/domains/source-control/inmemory.d.ts +8 -0
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/source-control/inmemory.js +13 -1
- package/dist/storage/domains/source-control/inmemory.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +79 -7
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +172 -30
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/timing.d.ts +23 -0
- package/dist/timing.d.ts.map +1 -1
- package/dist/timing.js +85 -6
- package/dist/timing.js.map +1 -1
- package/dist/tools/annotations.d.ts +7 -0
- package/dist/tools/annotations.d.ts.map +1 -0
- package/dist/tools/annotations.js +102 -0
- package/dist/tools/annotations.js.map +1 -0
- package/dist/tools/approval-tools.d.ts +15 -0
- package/dist/tools/approval-tools.d.ts.map +1 -0
- package/dist/tools/approval-tools.js +79 -0
- package/dist/tools/approval-tools.js.map +1 -0
- package/dist/tools/deliver.d.ts +71 -0
- package/dist/tools/deliver.d.ts.map +1 -0
- package/dist/tools/deliver.js +129 -0
- package/dist/tools/deliver.js.map +1 -0
- package/dist/tools/index.d.ts +30 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +36 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/messaging-tools.d.ts +28 -0
- package/dist/tools/messaging-tools.d.ts.map +1 -0
- package/dist/tools/messaging-tools.js +396 -0
- package/dist/tools/messaging-tools.js.map +1 -0
- package/dist/tools/permissions.d.ts +31 -0
- package/dist/tools/permissions.d.ts.map +1 -0
- package/dist/tools/permissions.js +48 -0
- package/dist/tools/permissions.js.map +1 -0
- package/dist/tools/read-tools.d.ts +30 -0
- package/dist/tools/read-tools.d.ts.map +1 -0
- package/dist/tools/read-tools.js +293 -0
- package/dist/tools/read-tools.js.map +1 -0
- package/dist/tools/reports.d.ts +174 -0
- package/dist/tools/reports.d.ts.map +1 -0
- package/dist/tools/reports.js +380 -0
- package/dist/tools/reports.js.map +1 -0
- package/dist/tools/run-watch.d.ts +46 -0
- package/dist/tools/run-watch.d.ts.map +1 -0
- package/dist/tools/run-watch.js +102 -0
- package/dist/tools/run-watch.js.map +1 -0
- package/dist/tools/scope.d.ts +54 -0
- package/dist/tools/scope.d.ts.map +1 -0
- package/dist/tools/scope.js +87 -0
- package/dist/tools/scope.js.map +1 -0
- package/dist/tools/session-tools.d.ts +43 -0
- package/dist/tools/session-tools.d.ts.map +1 -0
- package/dist/tools/session-tools.js +410 -0
- package/dist/tools/session-tools.js.map +1 -0
- package/dist/tools/sessions-state-processor.d.ts +41 -0
- package/dist/tools/sessions-state-processor.d.ts.map +1 -0
- package/dist/tools/sessions-state-processor.js +190 -0
- package/dist/tools/sessions-state-processor.js.map +1 -0
- package/dist/tools/side-sessions.d.ts +53 -0
- package/dist/tools/side-sessions.d.ts.map +1 -0
- package/dist/tools/side-sessions.js +83 -0
- package/dist/tools/side-sessions.js.map +1 -0
- package/dist/tools/work-item-tools.d.ts +28 -0
- package/dist/tools/work-item-tools.d.ts.map +1 -0
- package/dist/tools/work-item-tools.js +342 -0
- package/dist/tools/work-item-tools.js.map +1 -0
- package/dist/work-items/create-work-item.d.ts +55 -0
- package/dist/work-items/create-work-item.d.ts.map +1 -0
- package/dist/work-items/create-work-item.js +101 -0
- package/dist/work-items/create-work-item.js.map +1 -0
- package/dist/work-items/decision-actions.d.ts +42 -0
- package/dist/work-items/decision-actions.d.ts.map +1 -0
- package/dist/work-items/decision-actions.js +70 -0
- package/dist/work-items/decision-actions.js.map +1 -0
- package/dist/workspace.d.ts +1 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +15 -7
- package/dist/workspace.js.map +1 -1
- package/factory-skills/factory-gitlab-rereview/SKILL.md +1 -1
- package/factory-skills/factory-gitlab-review/SKILL.md +1 -1
- package/factory-skills/factory-plan/SKILL.md +4 -4
- package/factory-skills/factory-rereview/SKILL.md +3 -1
- package/factory-skills/factory-review/SKILL.md +3 -1
- package/factory-skills/factory-self-review/SKILL.md +46 -0
- package/package.json +13 -13
|
@@ -0,0 +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 | '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 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 // 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 (!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, 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 // 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: 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.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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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { NotificationsStorage } from '@mastra/core/notifications';
|
|
2
|
+
import type { ComputeStateSignalArgs, ComputeStateSignalResult, Processor } from '@mastra/core/processors';
|
|
3
|
+
import type { BoardRegistry } from '../boards/registry.js';
|
|
4
|
+
import type { SourceControlSessionLookup } from '../session/factory-session.js';
|
|
5
|
+
import type { WorkItemsStorage } from '../storage/domains/work-items/base.js';
|
|
6
|
+
import type { ResolveFactoryToolScopeOptions } from './scope.js';
|
|
7
|
+
/** Finished side sessions and items that reached a terminal stage drop out after this long. */
|
|
8
|
+
export declare const FACTORY_SESSIONS_MAX_AGE_MS: number;
|
|
9
|
+
/** A running worker whose thread has not moved for this long is shown as quiet, so a stuck run stands out. */
|
|
10
|
+
export declare const FACTORY_SESSIONS_QUIET_MS: number;
|
|
11
|
+
/** Rows shown before the rest collapse into "+N more". */
|
|
12
|
+
export declare const FACTORY_SESSIONS_MAX_ROWS = 20;
|
|
13
|
+
export type { FactorySessionRunState } from './side-sessions.js';
|
|
14
|
+
export interface FactorySessionsStateDependencies {
|
|
15
|
+
authEnabled: boolean;
|
|
16
|
+
sessions: Pick<SourceControlSessionLookup, 'getBySessionId' | 'listByOrigin'>;
|
|
17
|
+
projects: ResolveFactoryToolScopeOptions['projects'];
|
|
18
|
+
workItems: Pick<WorkItemsStorage, 'hasRunBindingForSession' | 'listSessionSubscriptions' | 'getForProject' | 'listRunBindings' | 'findActiveRunBindingForSession' | 'listWorkItemSubscribers'>;
|
|
19
|
+
boards: BoardRegistry;
|
|
20
|
+
/** Whether the session's agent is mid-run in this process; `idle` when it isn't live here. */
|
|
21
|
+
isRunning: (sessionId: string) => Promise<boolean>;
|
|
22
|
+
/** When a thread last changed; tells a long run that is still moving from one that stopped. */
|
|
23
|
+
lastActivity?: (threadId: string) => Promise<Date | null>;
|
|
24
|
+
notifications: () => Promise<Pick<NotificationsStorage, 'listNotifications'> | null>;
|
|
25
|
+
now?: () => number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The `<factory-sessions>` state signal. A user session sees the side sessions
|
|
29
|
+
* it started and the work items it follows; a side session or item worker sees
|
|
30
|
+
* who it reports to. Sessions it only messaged never appear. Silent until there
|
|
31
|
+
* is something to show; when the last entry ages out it sends one final empty
|
|
32
|
+
* snapshot, then stays silent.
|
|
33
|
+
*/
|
|
34
|
+
export declare class FactorySessionsStateProcessor implements Processor<'factory-sessions'> {
|
|
35
|
+
#private;
|
|
36
|
+
readonly id = "factory-sessions";
|
|
37
|
+
readonly stateId = "factory-sessions";
|
|
38
|
+
constructor(deps: FactorySessionsStateDependencies);
|
|
39
|
+
computeStateSignal(args: ComputeStateSignalArgs): Promise<ComputeStateSignalResult>;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=sessions-state-processor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions-state-processor.d.ts","sourceRoot":"","sources":["../../src/tools/sessions-state-processor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,KAAK,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAG3G,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAChF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE9E,OAAO,KAAK,EAAoB,8BAA8B,EAAE,MAAM,YAAY,CAAC;AAWnF,+FAA+F;AAC/F,eAAO,MAAM,2BAA2B,QAAsB,CAAC;AAI/D,8GAA8G;AAC9G,eAAO,MAAM,yBAAyB,QAAiB,CAAC;AACxD,0DAA0D;AAC1D,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAG5C,YAAY,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAEjE,MAAM,WAAW,gCAAgC;IAC/C,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,IAAI,CAAC,0BAA0B,EAAE,gBAAgB,GAAG,cAAc,CAAC,CAAC;IAC9E,QAAQ,EAAE,8BAA8B,CAAC,UAAU,CAAC,CAAC;IACrD,SAAS,EAAE,IAAI,CACb,gBAAgB,EACd,yBAAyB,GACzB,0BAA0B,GAC1B,eAAe,GACf,iBAAiB,GACjB,gCAAgC,GAChC,yBAAyB,CAC5B,CAAC;IACF,MAAM,EAAE,aAAa,CAAC;IACtB,8FAA8F;IAC9F,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,+FAA+F;IAC/F,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IAC1D,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,oBAAoB,EAAE,mBAAmB,CAAC,GAAG,IAAI,CAAC,CAAC;IACrF,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAiCD;;;;;;GAMG;AACH,qBAAa,6BAA8B,YAAW,SAAS,CAAC,kBAAkB,CAAC;;IACjF,QAAQ,CAAC,EAAE,sBAAY;IACvB,QAAQ,CAAC,OAAO,sBAAY;gBAGhB,IAAI,EAAE,gCAAgC;IAI5C,kBAAkB,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,wBAAwB,CAAC;CAyM1F"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { boardForWorkItem } from "../boards/semantics.js";
|
|
2
|
+
import "../boards/index.js";
|
|
3
|
+
import { resolveFactoryToolScope } from "./scope.js";
|
|
4
|
+
import { itemLabel, lastReport, listSessionNotifications, listSideSessions, newestNotification } from "./side-sessions.js";
|
|
5
|
+
import { createHash } from "crypto";
|
|
6
|
+
//#region src/tools/sessions-state-processor.ts
|
|
7
|
+
const STATE_ID = "factory-sessions";
|
|
8
|
+
/** Finished side sessions and items that reached a terminal stage drop out after this long. */
|
|
9
|
+
const FACTORY_SESSIONS_MAX_AGE_MS = 1440 * 60 * 1e3;
|
|
10
|
+
/** Asks a long run to show it is still moving; a silent half hour reads the same as a stuck one. */
|
|
11
|
+
const PROGRESS_ASK = "On long work, send a progress report after each plan step.";
|
|
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 = 600 * 1e3;
|
|
14
|
+
/** Rows shown before the rest collapse into "+N more". */
|
|
15
|
+
const FACTORY_SESSIONS_MAX_ROWS = 20;
|
|
16
|
+
const EMPTY_CACHE_KEY = "empty";
|
|
17
|
+
function escapeText(value) {
|
|
18
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
19
|
+
}
|
|
20
|
+
function cacheKeyFor(value) {
|
|
21
|
+
return `sessions:${createHash("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16)}`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The `<factory-sessions>` state signal. A user session sees the side sessions
|
|
25
|
+
* it started and the work items it follows; a side session or item worker sees
|
|
26
|
+
* who it reports to. Sessions it only messaged never appear. Silent until there
|
|
27
|
+
* is something to show; when the last entry ages out it sends one final empty
|
|
28
|
+
* snapshot, then stays silent.
|
|
29
|
+
*/
|
|
30
|
+
var FactorySessionsStateProcessor = class {
|
|
31
|
+
id = STATE_ID;
|
|
32
|
+
stateId = STATE_ID;
|
|
33
|
+
#deps;
|
|
34
|
+
constructor(deps) {
|
|
35
|
+
this.#deps = deps;
|
|
36
|
+
}
|
|
37
|
+
async computeStateSignal(args) {
|
|
38
|
+
const scope = await resolveFactoryToolScope({
|
|
39
|
+
requestContext: args.requestContext,
|
|
40
|
+
authEnabled: this.#deps.authEnabled,
|
|
41
|
+
sessions: this.#deps.sessions,
|
|
42
|
+
projects: this.#deps.projects,
|
|
43
|
+
workItems: this.#deps.workItems
|
|
44
|
+
});
|
|
45
|
+
if (!scope) return;
|
|
46
|
+
const hasBase = Boolean(args.lastSnapshot) && args.contextWindow.hasSnapshot;
|
|
47
|
+
const previousKey = hasBase ? args.tracking?.currentCacheKey ?? (args.lastSnapshot?.metadata?.state)?.cacheKey : void 0;
|
|
48
|
+
const unchanged = (cacheKey) => previousKey === cacheKey;
|
|
49
|
+
const cleared = () => hasBase && previousKey !== void 0 && previousKey !== EMPTY_CACHE_KEY;
|
|
50
|
+
if (scope.kind === "worker") {
|
|
51
|
+
const contents = await this.#workerLine(scope);
|
|
52
|
+
if (!contents) return cleared() ? this.#signal(EMPTY_CACHE_KEY, "This session is not reporting on any work now.", {}, { entries: 0 }) : void 0;
|
|
53
|
+
const cacheKey = cacheKeyFor(contents);
|
|
54
|
+
if (unchanged(cacheKey)) return;
|
|
55
|
+
return this.#signal(cacheKey, escapeText(contents), { worker: contents }, { entries: 1 });
|
|
56
|
+
}
|
|
57
|
+
const state = await this.#userState(scope);
|
|
58
|
+
if (state.sideSessions.length === 0 && state.following.length === 0) {
|
|
59
|
+
if (!cleared()) return;
|
|
60
|
+
return this.#signal(EMPTY_CACHE_KEY, "No sub-sessions or followed work items.", state, { entries: 0 });
|
|
61
|
+
}
|
|
62
|
+
const cacheKey = cacheKeyFor(state);
|
|
63
|
+
if (unchanged(cacheKey)) return;
|
|
64
|
+
return this.#signal(cacheKey, this.#render(state), state, {
|
|
65
|
+
sideSessions: state.sideSessions.length,
|
|
66
|
+
following: state.following.length
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
#signal(cacheKey, contents, value, attributes) {
|
|
70
|
+
return {
|
|
71
|
+
id: STATE_ID,
|
|
72
|
+
cacheKey,
|
|
73
|
+
mode: "snapshot",
|
|
74
|
+
tagName: "factory-sessions",
|
|
75
|
+
contents,
|
|
76
|
+
value: { sessions: value },
|
|
77
|
+
attributes,
|
|
78
|
+
metadata: { value: { sessions: value } }
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
async #workerLine(scope) {
|
|
82
|
+
if (scope.origin) return `Started by session ${scope.origin.sessionId} (thread ${scope.origin.threadId}). Report back with factory_message_origin. ${PROGRESS_ASK}`;
|
|
83
|
+
const binding = await this.#deps.workItems.findActiveRunBindingForSession({
|
|
84
|
+
orgId: scope.orgId,
|
|
85
|
+
factoryProjectId: scope.factoryProjectId,
|
|
86
|
+
sessionId: scope.sessionId
|
|
87
|
+
});
|
|
88
|
+
if (!binding) return null;
|
|
89
|
+
const item = await this.#deps.workItems.getForProject(scope.orgId, scope.factoryProjectId, binding.workItemId);
|
|
90
|
+
const following = (await this.#deps.workItems.listWorkItemSubscribers({
|
|
91
|
+
orgId: scope.orgId,
|
|
92
|
+
workItemId: binding.workItemId
|
|
93
|
+
})).filter((subscriber) => subscriber.sessionId !== scope.sessionId).length;
|
|
94
|
+
if (following === 0) return null;
|
|
95
|
+
return `Working on ${item ? itemLabel(item) : binding.workItemId} as ${binding.role}. Reports go to ${following} following session${following === 1 ? "" : "s"}. ${PROGRESS_ASK}`;
|
|
96
|
+
}
|
|
97
|
+
async #userState(scope) {
|
|
98
|
+
const now = this.#deps.now?.() ?? Date.now();
|
|
99
|
+
const notifications = await this.#deps.notifications();
|
|
100
|
+
const records = await listSessionNotifications(notifications, scope.threadId);
|
|
101
|
+
const sideSessions = [];
|
|
102
|
+
const listed = await listSideSessions({
|
|
103
|
+
sessions: this.#deps.sessions,
|
|
104
|
+
workItems: this.#deps.workItems,
|
|
105
|
+
isRunning: this.#deps.isRunning,
|
|
106
|
+
notifications
|
|
107
|
+
}, scope, { records });
|
|
108
|
+
for (const row of listed) {
|
|
109
|
+
if (row.state !== "running" && now - Date.parse(row.lastActivityAt) > 864e5) continue;
|
|
110
|
+
sideSessions.push({
|
|
111
|
+
sessionId: row.sessionId,
|
|
112
|
+
title: row.title,
|
|
113
|
+
state: row.state,
|
|
114
|
+
lastReport: row.lastReport,
|
|
115
|
+
muted: row.mutedAt !== null,
|
|
116
|
+
active: row.state !== "idle"
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
const subscriptions = await this.#deps.workItems.listSessionSubscriptions({
|
|
120
|
+
orgId: scope.orgId,
|
|
121
|
+
factoryProjectId: scope.factoryProjectId,
|
|
122
|
+
sessionId: scope.sessionId
|
|
123
|
+
});
|
|
124
|
+
const following = [];
|
|
125
|
+
for (const subscription of subscriptions) {
|
|
126
|
+
const item = await this.#deps.workItems.getForProject(scope.orgId, scope.factoryProjectId, subscription.workItemId);
|
|
127
|
+
if (!item) continue;
|
|
128
|
+
const stage = item.stages[0] ?? "unknown";
|
|
129
|
+
const terminal = this.#deps.boards.get(boardForWorkItem(item))?.isTerminal(stage) ?? false;
|
|
130
|
+
if (terminal && now - item.updatedAt.getTime() > 864e5) continue;
|
|
131
|
+
const active = (await this.#deps.workItems.listRunBindings(scope.orgId, scope.factoryProjectId, item.id)).find((binding) => binding.status === "active");
|
|
132
|
+
const label = itemLabel(item);
|
|
133
|
+
const last = newestNotification(records, (record) => record.attributes?.["work-item"] === label || record.kind === "stage" && record.sourceId === item.id);
|
|
134
|
+
const running = active ? await this.#deps.isRunning(active.sessionId) : false;
|
|
135
|
+
const lastActivity = active && running ? await this.#deps.lastActivity?.(active.threadId) : null;
|
|
136
|
+
const quiet = lastActivity && now - lastActivity.getTime() >= 6e5 ? lastActivity : null;
|
|
137
|
+
const lastFromWorker = active ? newestNotification(records, (record) => record.attributes?.["from-session"] === active.sessionId) : void 0;
|
|
138
|
+
following.push({
|
|
139
|
+
workItemId: item.id,
|
|
140
|
+
label,
|
|
141
|
+
title: item.title,
|
|
142
|
+
stage,
|
|
143
|
+
worker: active ? {
|
|
144
|
+
role: active.role,
|
|
145
|
+
sessionId: active.sessionId,
|
|
146
|
+
state: running ? "running" : lastFromWorker?.kind === "blocked" ? "parked" : "idle",
|
|
147
|
+
quietSince: quiet ? quiet.toISOString() : null
|
|
148
|
+
} : null,
|
|
149
|
+
lastReport: lastReport(last),
|
|
150
|
+
active: running && !terminal
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
const ordered = [...sideSessions.map((row) => ({
|
|
154
|
+
section: "side",
|
|
155
|
+
row
|
|
156
|
+
})), ...following.map((row) => ({
|
|
157
|
+
section: "following",
|
|
158
|
+
row
|
|
159
|
+
}))].sort((a, b) => Number(b.row.active) - Number(a.row.active));
|
|
160
|
+
const shown = ordered.slice(0, 20);
|
|
161
|
+
const strip = ({ active: _active, ...row }) => row;
|
|
162
|
+
return {
|
|
163
|
+
sideSessions: shown.flatMap((entry) => entry.section === "side" ? [strip(entry.row)] : []),
|
|
164
|
+
following: shown.flatMap((entry) => entry.section === "following" ? [strip(entry.row)] : []),
|
|
165
|
+
more: ordered.length - shown.length
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
#render(state) {
|
|
169
|
+
const lastReport = (last) => last ? `; last report ${last.kind} at ${last.at}` : "";
|
|
170
|
+
const lines = [];
|
|
171
|
+
if (state.sideSessions.length > 0) {
|
|
172
|
+
lines.push("Side sessions you started:");
|
|
173
|
+
for (const row of state.sideSessions) lines.push(`- "${escapeText(row.title)}" (session ${row.sessionId}): ${row.state}${row.muted ? " (muted)" : ""}${lastReport(row.lastReport)}`);
|
|
174
|
+
}
|
|
175
|
+
if (state.following.length > 0) {
|
|
176
|
+
lines.push("Work items you follow:");
|
|
177
|
+
for (const row of state.following) {
|
|
178
|
+
const worker = row.worker ? `; ${escapeText(row.worker.role)} session ${row.worker.sessionId} ${row.worker.state}${row.worker.quietSince ? ` but quiet since ${row.worker.quietSince}` : ""}` : "";
|
|
179
|
+
lines.push(`- ${escapeText(row.label)} "${escapeText(row.title)}": ${escapeText(row.stage)}${worker}${lastReport(row.lastReport)}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (state.more > 0) lines.push(`+${state.more} more`);
|
|
183
|
+
lines.push("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.");
|
|
184
|
+
return lines.join("\n");
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
//#endregion
|
|
188
|
+
export { FACTORY_SESSIONS_MAX_AGE_MS, FACTORY_SESSIONS_MAX_ROWS, FACTORY_SESSIONS_QUIET_MS, FactorySessionsStateProcessor };
|
|
189
|
+
|
|
190
|
+
//# sourceMappingURL=sessions-state-processor.js.map
|
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { NotificationRecord, NotificationsStorage } from '@mastra/core/notifications';
|
|
2
|
+
import type { SourceControlSessionLookup } from '../session/factory-session.js';
|
|
3
|
+
import type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';
|
|
4
|
+
import { resolveSessionThread } from './deliver.js';
|
|
5
|
+
import type { FactoryDeliveryController } from './deliver.js';
|
|
6
|
+
import type { FactoryToolScope } from './scope.js';
|
|
7
|
+
/** Recent notifications scanned to find each side session's last report. */
|
|
8
|
+
export declare const SIDE_SESSION_NOTIFICATION_SCAN = 100;
|
|
9
|
+
export type FactorySessionRunState = 'running' | 'idle' | 'parked';
|
|
10
|
+
export interface LastReport {
|
|
11
|
+
kind: string;
|
|
12
|
+
at: string;
|
|
13
|
+
}
|
|
14
|
+
export interface SideSession {
|
|
15
|
+
sessionId: string;
|
|
16
|
+
title: string;
|
|
17
|
+
state: FactorySessionRunState;
|
|
18
|
+
lastReport: LastReport | null;
|
|
19
|
+
/** When the origin muted this session's reports; null while it reports normally. */
|
|
20
|
+
mutedAt: string | null;
|
|
21
|
+
/** The work item this session is bound to as a worker, if any. */
|
|
22
|
+
workItem: {
|
|
23
|
+
id: string;
|
|
24
|
+
label: string;
|
|
25
|
+
stage: string;
|
|
26
|
+
} | null;
|
|
27
|
+
/** The model the session's thread uses, when known. */
|
|
28
|
+
model: string | null;
|
|
29
|
+
/** Last report time, or the session's last update when it never reported. */
|
|
30
|
+
lastActivityAt: string;
|
|
31
|
+
}
|
|
32
|
+
export interface SideSessionDependencies {
|
|
33
|
+
sessions: Pick<SourceControlSessionLookup, 'listByOrigin'>;
|
|
34
|
+
workItems: Pick<WorkItemsStorage, 'findActiveRunBindingForSession' | 'getForProject'>;
|
|
35
|
+
isRunning: (sessionId: string) => Promise<boolean>;
|
|
36
|
+
notifications: Pick<NotificationsStorage, 'listNotifications'> | null;
|
|
37
|
+
/** Resolves the model a side session uses; omit to leave `model` null. */
|
|
38
|
+
model?: (sessionId: string) => Promise<string | null>;
|
|
39
|
+
}
|
|
40
|
+
export declare function lastReport(record: NotificationRecord | undefined): LastReport | null;
|
|
41
|
+
export declare function newestNotification(records: NotificationRecord[], match: (record: NotificationRecord) => boolean): NotificationRecord | undefined;
|
|
42
|
+
export declare function itemLabel(item: WorkItemRow): string;
|
|
43
|
+
export declare function listSessionNotifications(notifications: Pick<NotificationsStorage, 'listNotifications'> | null, threadId: string): Promise<NotificationRecord[]>;
|
|
44
|
+
/**
|
|
45
|
+
* Every side session the scope's session started in its Factory project,
|
|
46
|
+
* newest activity first. Callers apply their own age and row caps.
|
|
47
|
+
*/
|
|
48
|
+
export declare function listSideSessions(deps: SideSessionDependencies, scope: Pick<FactoryToolScope, 'orgId' | 'factoryProjectId' | 'sessionId' | 'threadId'>, options?: {
|
|
49
|
+
records?: NotificationRecord[];
|
|
50
|
+
}): Promise<SideSession[]>;
|
|
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, 'getSession' | 'queryThreads' | 'queryThreadById'>, sessions: Parameters<typeof resolveSessionThread>[0]['sessions'], workItems: Parameters<typeof resolveSessionThread>[0]['workItems'], sessionId: string): Promise<string | null>;
|
|
53
|
+
//# sourceMappingURL=side-sessions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"side-sessions.d.ts","sourceRoot":"","sources":["../../src/tools/side-sessions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { FACTORY_SESSION_NOTIFICATION_SOURCE, resolveSessionThread } from "./deliver.js";
|
|
2
|
+
import { workItemNumber } from "./read-tools.js";
|
|
3
|
+
//#region src/tools/side-sessions.ts
|
|
4
|
+
/** Recent notifications scanned to find each side session's last report. */
|
|
5
|
+
const SIDE_SESSION_NOTIFICATION_SCAN = 100;
|
|
6
|
+
function lastReport(record) {
|
|
7
|
+
return record ? {
|
|
8
|
+
kind: record.kind,
|
|
9
|
+
at: record.createdAt.toISOString()
|
|
10
|
+
} : null;
|
|
11
|
+
}
|
|
12
|
+
function newestNotification(records, match) {
|
|
13
|
+
let found;
|
|
14
|
+
for (const record of records) if (match(record) && (!found || record.createdAt > found.createdAt)) found = record;
|
|
15
|
+
return found;
|
|
16
|
+
}
|
|
17
|
+
function itemLabel(item) {
|
|
18
|
+
const number = workItemNumber(item);
|
|
19
|
+
return number === null ? item.id : `#${number}`;
|
|
20
|
+
}
|
|
21
|
+
async function listSessionNotifications(notifications, threadId) {
|
|
22
|
+
return notifications ? notifications.listNotifications({
|
|
23
|
+
threadId,
|
|
24
|
+
source: FACTORY_SESSION_NOTIFICATION_SOURCE,
|
|
25
|
+
limit: 100
|
|
26
|
+
}) : [];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Every side session the scope's session started in its Factory project,
|
|
30
|
+
* newest activity first. Callers apply their own age and row caps.
|
|
31
|
+
*/
|
|
32
|
+
async function listSideSessions(deps, scope, options = {}) {
|
|
33
|
+
const records = options.records ?? await listSessionNotifications(deps.notifications, scope.threadId);
|
|
34
|
+
const rows = await deps.sessions.listByOrigin({
|
|
35
|
+
orgId: scope.orgId,
|
|
36
|
+
originSessionId: scope.sessionId
|
|
37
|
+
});
|
|
38
|
+
const sideSessions = [];
|
|
39
|
+
for (const row of rows) {
|
|
40
|
+
if (row.factoryProjectId !== scope.factoryProjectId) continue;
|
|
41
|
+
const last = newestNotification(records, (record) => record.attributes?.["from-session"] === row.sessionId);
|
|
42
|
+
const state = await deps.isRunning(row.sessionId) ? "running" : last?.kind === "blocked" ? "parked" : "idle";
|
|
43
|
+
const binding = await deps.workItems.findActiveRunBindingForSession({
|
|
44
|
+
orgId: scope.orgId,
|
|
45
|
+
factoryProjectId: scope.factoryProjectId,
|
|
46
|
+
sessionId: row.sessionId
|
|
47
|
+
});
|
|
48
|
+
const item = binding ? await deps.workItems.getForProject(scope.orgId, scope.factoryProjectId, binding.workItemId) : null;
|
|
49
|
+
sideSessions.push({
|
|
50
|
+
sessionId: row.sessionId,
|
|
51
|
+
title: row.title ?? row.sessionId,
|
|
52
|
+
state,
|
|
53
|
+
lastReport: lastReport(last),
|
|
54
|
+
mutedAt: row.originMutedAt?.toISOString() ?? null,
|
|
55
|
+
workItem: item ? {
|
|
56
|
+
id: item.id,
|
|
57
|
+
label: itemLabel(item),
|
|
58
|
+
stage: item.stages[0] ?? "unknown"
|
|
59
|
+
} : null,
|
|
60
|
+
model: deps.model ? await deps.model(row.sessionId) : null,
|
|
61
|
+
lastActivityAt: (last?.createdAt ?? row.updatedAt).toISOString()
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return sideSessions.sort((left, right) => right.lastActivityAt.localeCompare(left.lastActivityAt));
|
|
65
|
+
}
|
|
66
|
+
/** The model a session's thread uses: the live session's selection, else the persisted one. */
|
|
67
|
+
async function sessionModel(controller, sessions, workItems, sessionId) {
|
|
68
|
+
const liveModel = (await controller.getSession(sessionId))?.model.get();
|
|
69
|
+
if (liveModel) return liveModel;
|
|
70
|
+
const threadId = await resolveSessionThread({
|
|
71
|
+
controller,
|
|
72
|
+
sessions,
|
|
73
|
+
workItems,
|
|
74
|
+
sessionId
|
|
75
|
+
});
|
|
76
|
+
if (!threadId) return null;
|
|
77
|
+
const persisted = (await controller.queryThreadById({ threadId }))?.metadata?.currentModelId;
|
|
78
|
+
return typeof persisted === "string" && persisted ? persisted : null;
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { SIDE_SESSION_NOTIFICATION_SCAN, itemLabel, lastReport, listSessionNotifications, listSideSessions, newestNotification, sessionModel };
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=side-sessions.js.map
|
|
@@ -0,0 +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, '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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory tools that change work items: file one, move it, settle its
|
|
3
|
+
* decisions, and follow it. They act as the session's person (a human actor),
|
|
4
|
+
* through the same code the board's routes use.
|
|
5
|
+
*/
|
|
6
|
+
import type { BoardRegistry } from '../boards/index.js';
|
|
7
|
+
import type { IntegrationTools } from '../integrations/base.js';
|
|
8
|
+
import type { FactoryTransitionService } from '../rules/transition-service.js';
|
|
9
|
+
import type { SourceControlSessionLookup } from '../session/factory-session.js';
|
|
10
|
+
import type { AuditRecorder } from '../storage/domains/audit/domain.js';
|
|
11
|
+
import type { CommentsDomain } from '../storage/domains/comments/domain.js';
|
|
12
|
+
import type { WorkItemsStorage } from '../storage/domains/work-items/base.js';
|
|
13
|
+
import type { FactoryToolScope } from './scope.js';
|
|
14
|
+
export interface FactoryWorkItemToolDependencies {
|
|
15
|
+
workItems: WorkItemsStorage;
|
|
16
|
+
boards: BoardRegistry;
|
|
17
|
+
/** Governed stage moves; filing and moving items refuse while it is unavailable. */
|
|
18
|
+
transitionService: Pick<FactoryTransitionService, 'transition'> | undefined;
|
|
19
|
+
/** Writes audit rows with an explicit actor; tool calls have no HTTP request to read one from. */
|
|
20
|
+
auditRecorder: AuditRecorder;
|
|
21
|
+
/** The feed's front door: validates, touches open boards and mirrors to linked channels. */
|
|
22
|
+
feed: Pick<CommentsDomain, 'createComment'>;
|
|
23
|
+
/** Finds the caller session's source-control provider, whose linked repositories a card can name. */
|
|
24
|
+
sessions: Pick<SourceControlSessionLookup, 'getSourceControlBySessionId'>;
|
|
25
|
+
}
|
|
26
|
+
/** The work-item write and subscription tools for one user session's turn. */
|
|
27
|
+
export declare function createFactoryWorkItemTools(scope: FactoryToolScope, deps: FactoryWorkItemToolDependencies): IntegrationTools;
|
|
28
|
+
//# sourceMappingURL=work-item-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"work-item-tools.d.ts","sourceRoot":"","sources":["../../src/tools/work-item-tools.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAC/E,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAEhF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAE5E,OAAO,KAAK,EAAoC,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAmBhH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,MAAM,WAAW,+BAA+B;IAC9C,SAAS,EAAE,gBAAgB,CAAC;IAC5B,MAAM,EAAE,aAAa,CAAC;IACtB,oFAAoF;IACpF,iBAAiB,EAAE,IAAI,CAAC,wBAAwB,EAAE,YAAY,CAAC,GAAG,SAAS,CAAC;IAC5E,kGAAkG;IAClG,aAAa,EAAE,aAAa,CAAC;IAC7B,4FAA4F;IAC5F,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,eAAe,CAAC,CAAC;IAC5C,qGAAqG;IACrG,QAAQ,EAAE,IAAI,CAAC,0BAA0B,EAAE,6BAA6B,CAAC,CAAC;CAC3E;AA2BD,8EAA8E;AAC9E,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,+BAA+B,GACpC,gBAAgB,CAgTlB"}
|