@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
package/dist/timing.d.ts
CHANGED
|
@@ -5,6 +5,29 @@
|
|
|
5
5
|
* grepped (`[factory:timing]`) and diffed across runs to localize slow boot
|
|
6
6
|
* phases and slow per-request dependencies.
|
|
7
7
|
*/
|
|
8
|
+
import type { Context } from 'hono';
|
|
9
|
+
/**
|
|
10
|
+
* Verbose per-operation timing (HTTP requests, storage calls, sandbox
|
|
11
|
+
* commands). Off by default; enable per deployment with `FACTORY_TIMING=1`.
|
|
12
|
+
*/
|
|
13
|
+
export declare const factoryTimingEnabled: boolean;
|
|
14
|
+
/** Run `fn`, logging its duration only when `FACTORY_TIMING` is enabled. */
|
|
15
|
+
export declare function timed<T>(phase: string, fn: () => Promise<T>): Promise<T>;
|
|
16
|
+
/** Hono middleware logging method, path, status, and duration of each request. */
|
|
17
|
+
export declare function createRequestTimingMiddleware(): (c: Context, next: () => Promise<void>) => Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Patch `methods` on `target` in place so each call logs `<label>.<method>`
|
|
20
|
+
* duration. In-place (not a Proxy) so object identity is preserved.
|
|
21
|
+
*/
|
|
22
|
+
export declare function instrumentMethods<T extends object>(target: T, label: string, methods: readonly string[]): T;
|
|
23
|
+
/**
|
|
24
|
+
* Wrap every async method on `target` so each call logs `<label>.<method>`
|
|
25
|
+
* duration. Domains passed to a `registerDomain` method are instrumented in
|
|
26
|
+
* place as `<label>.<domain name>`, so the handle it returns and later
|
|
27
|
+
* `getDomain` lookups are the same timed object. Returns `target` unchanged
|
|
28
|
+
* when timing is disabled.
|
|
29
|
+
*/
|
|
30
|
+
export declare function instrumentAsyncMethods<T extends object>(target: T, label: string): T;
|
|
8
31
|
/** Run `fn`, always logging its duration under `phase`. Boot-phase use. */
|
|
9
32
|
export declare function timedPhase<T>(phase: string, fn: () => Promise<T>): Promise<T>;
|
|
10
33
|
/**
|
package/dist/timing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timing.d.ts","sourceRoot":"","sources":["../src/timing.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,2EAA2E;AAC3E,wBAAsB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAOnF;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAUjH"}
|
|
1
|
+
{"version":3,"file":"timing.d.ts","sourceRoot":"","sources":["../src/timing.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC;;;GAGG;AACH,eAAO,MAAM,oBAAoB,SAA2D,CAAC;AAM7F,4EAA4E;AAC5E,wBAAsB,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAQ9E;AAED,kFAAkF;AAClF,wBAAgB,6BAA6B,KAC7B,GAAG,OAAO,EAAE,MAAM,MAAM,OAAO,CAAC,IAAI,CAAC,KAAG,OAAO,CAAC,IAAI,CAAC,CAQpE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,CAAC,CAgB3G;AAmBD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,CAyBpF;AAED,2EAA2E;AAC3E,wBAAsB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAOnF;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAUjH"}
|
package/dist/timing.js
CHANGED
|
@@ -1,11 +1,90 @@
|
|
|
1
1
|
//#region src/timing.ts
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Deliberately not a metrics framework: structured stderr lines that can be
|
|
6
|
-
* grepped (`[factory:timing]`) and diffed across runs to localize slow boot
|
|
7
|
-
* phases and slow per-request dependencies.
|
|
3
|
+
* Verbose per-operation timing (HTTP requests, storage calls, sandbox
|
|
4
|
+
* commands). Off by default; enable per deployment with `FACTORY_TIMING=1`.
|
|
8
5
|
*/
|
|
6
|
+
const factoryTimingEnabled = /^(1|true|yes)$/i.test(process.env.FACTORY_TIMING ?? "");
|
|
7
|
+
function logTiming(line) {
|
|
8
|
+
process.stderr.write(`[factory:timing] ${line}\n`);
|
|
9
|
+
}
|
|
10
|
+
/** Run `fn`, logging its duration only when `FACTORY_TIMING` is enabled. */
|
|
11
|
+
async function timed(phase, fn) {
|
|
12
|
+
if (!factoryTimingEnabled) return fn();
|
|
13
|
+
const start = performance.now();
|
|
14
|
+
try {
|
|
15
|
+
return await fn();
|
|
16
|
+
} finally {
|
|
17
|
+
logTiming(`${phase} ${Math.round(performance.now() - start)}ms`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Hono middleware logging method, path, status, and duration of each request. */
|
|
21
|
+
function createRequestTimingMiddleware() {
|
|
22
|
+
return async (c, next) => {
|
|
23
|
+
const start = performance.now();
|
|
24
|
+
try {
|
|
25
|
+
await next();
|
|
26
|
+
} finally {
|
|
27
|
+
logTiming(`http ${c.req.method} ${c.req.path} ${c.res.status} ${Math.round(performance.now() - start)}ms`);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Patch `methods` on `target` in place so each call logs `<label>.<method>`
|
|
33
|
+
* duration. In-place (not a Proxy) so object identity is preserved.
|
|
34
|
+
*/
|
|
35
|
+
function instrumentMethods(target, label, methods) {
|
|
36
|
+
if (!factoryTimingEnabled) return target;
|
|
37
|
+
const record = target;
|
|
38
|
+
for (const name of methods) {
|
|
39
|
+
const original = record[name];
|
|
40
|
+
if (typeof original !== "function") continue;
|
|
41
|
+
record[name] = (...args) => {
|
|
42
|
+
const start = performance.now();
|
|
43
|
+
const result = original.apply(target, args);
|
|
44
|
+
if (result instanceof Promise) return result.finally(() => logTiming(`${label}.${name} ${Math.round(performance.now() - start)}ms`));
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return target;
|
|
49
|
+
}
|
|
50
|
+
/** Every method name on `target`'s prototype chain, excluding `Object.prototype`. */
|
|
51
|
+
function prototypeMethodNames(target) {
|
|
52
|
+
const names = /* @__PURE__ */ new Set();
|
|
53
|
+
for (let proto = Object.getPrototypeOf(target); proto && proto !== Object.prototype; proto = Object.getPrototypeOf(proto)) for (const name of Object.getOwnPropertyNames(proto)) {
|
|
54
|
+
if (name === "constructor") continue;
|
|
55
|
+
if (typeof Object.getOwnPropertyDescriptor(proto, name)?.value === "function") names.add(name);
|
|
56
|
+
}
|
|
57
|
+
return [...names];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Wrap every async method on `target` so each call logs `<label>.<method>`
|
|
61
|
+
* duration. Domains passed to a `registerDomain` method are instrumented in
|
|
62
|
+
* place as `<label>.<domain name>`, so the handle it returns and later
|
|
63
|
+
* `getDomain` lookups are the same timed object. Returns `target` unchanged
|
|
64
|
+
* when timing is disabled.
|
|
65
|
+
*/
|
|
66
|
+
function instrumentAsyncMethods(target, label) {
|
|
67
|
+
if (!factoryTimingEnabled) return target;
|
|
68
|
+
const wrapped = /* @__PURE__ */ new Map();
|
|
69
|
+
return new Proxy(target, { get(obj, prop, receiver) {
|
|
70
|
+
const value = Reflect.get(obj, prop, receiver);
|
|
71
|
+
if (typeof value !== "function" || typeof prop === "symbol" || prop === "constructor") return value;
|
|
72
|
+
const cached = wrapped.get(prop);
|
|
73
|
+
if (cached) return cached;
|
|
74
|
+
const fn = (...args) => {
|
|
75
|
+
const start = performance.now();
|
|
76
|
+
const result = value.apply(obj, args);
|
|
77
|
+
if (prop === "registerDomain" && result && typeof result === "object" && !(result instanceof Promise)) {
|
|
78
|
+
const name = result.name;
|
|
79
|
+
return instrumentMethods(result, `${label}.${String(name)}`, prototypeMethodNames(result));
|
|
80
|
+
}
|
|
81
|
+
if (result instanceof Promise) return result.finally(() => logTiming(`${label}.${prop} ${Math.round(performance.now() - start)}ms`));
|
|
82
|
+
return result;
|
|
83
|
+
};
|
|
84
|
+
wrapped.set(prop, fn);
|
|
85
|
+
return fn;
|
|
86
|
+
} });
|
|
87
|
+
}
|
|
9
88
|
/** Run `fn`, always logging its duration under `phase`. Boot-phase use. */
|
|
10
89
|
async function timedPhase(phase, fn) {
|
|
11
90
|
const start = performance.now();
|
|
@@ -29,6 +108,6 @@ async function timedAboveThreshold(phase, thresholdMs, fn) {
|
|
|
29
108
|
}
|
|
30
109
|
}
|
|
31
110
|
//#endregion
|
|
32
|
-
export { timedAboveThreshold, timedPhase };
|
|
111
|
+
export { createRequestTimingMiddleware, factoryTimingEnabled, instrumentAsyncMethods, instrumentMethods, timed, timedAboveThreshold, timedPhase };
|
|
33
112
|
|
|
34
113
|
//# sourceMappingURL=timing.js.map
|
package/dist/timing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timing.js","names":[],"sources":["../src/timing.ts"],"sourcesContent":["/**\n * Lightweight phase timing for factory boot and hot request paths.\n *\n * Deliberately not a metrics framework: structured stderr lines that can be\n * grepped (`[factory:timing]`) and diffed across runs to localize slow boot\n * phases and slow per-request dependencies.\n */\n\n/** Run `fn`, always logging its duration under `phase`. Boot-phase use. */\nexport async function timedPhase<T>(phase: string, fn: () => Promise<T>): Promise<T> {\n const start = performance.now();\n try {\n return await fn();\n } finally {\n process.stderr.write(`[factory:timing] ${phase} ${Math.round(performance.now() - start)}ms\\n`);\n }\n}\n\n/**\n * Run `fn`, logging only when it exceeds `thresholdMs`. Request-path use,\n * where per-request logging would be noise but slow outliers must surface.\n */\nexport async function timedAboveThreshold<T>(phase: string, thresholdMs: number, fn: () => Promise<T>): Promise<T> {\n const start = performance.now();\n try {\n return await fn();\n } finally {\n const elapsed = performance.now() - start;\n if (elapsed > thresholdMs) {\n process.stderr.write(`[factory:timing] slow ${phase} ${Math.round(elapsed)}ms\\n`);\n }\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"timing.js","names":[],"sources":["../src/timing.ts"],"sourcesContent":["/**\n * Lightweight phase timing for factory boot and hot request paths.\n *\n * Deliberately not a metrics framework: structured stderr lines that can be\n * grepped (`[factory:timing]`) and diffed across runs to localize slow boot\n * phases and slow per-request dependencies.\n */\n\nimport type { Context } from 'hono';\n\n/**\n * Verbose per-operation timing (HTTP requests, storage calls, sandbox\n * commands). Off by default; enable per deployment with `FACTORY_TIMING=1`.\n */\nexport const factoryTimingEnabled = /^(1|true|yes)$/i.test(process.env.FACTORY_TIMING ?? '');\n\nfunction logTiming(line: string): void {\n process.stderr.write(`[factory:timing] ${line}\\n`);\n}\n\n/** Run `fn`, logging its duration only when `FACTORY_TIMING` is enabled. */\nexport async function timed<T>(phase: string, fn: () => Promise<T>): Promise<T> {\n if (!factoryTimingEnabled) return fn();\n const start = performance.now();\n try {\n return await fn();\n } finally {\n logTiming(`${phase} ${Math.round(performance.now() - start)}ms`);\n }\n}\n\n/** Hono middleware logging method, path, status, and duration of each request. */\nexport function createRequestTimingMiddleware() {\n return async (c: Context, next: () => Promise<void>): Promise<void> => {\n const start = performance.now();\n try {\n await next();\n } finally {\n logTiming(`http ${c.req.method} ${c.req.path} ${c.res.status} ${Math.round(performance.now() - start)}ms`);\n }\n };\n}\n\n/**\n * Patch `methods` on `target` in place so each call logs `<label>.<method>`\n * duration. In-place (not a Proxy) so object identity is preserved.\n */\nexport function instrumentMethods<T extends object>(target: T, label: string, methods: readonly string[]): T {\n if (!factoryTimingEnabled) return target;\n const record = target as Record<string, unknown>;\n for (const name of methods) {\n const original = record[name];\n if (typeof original !== 'function') continue;\n record[name] = (...args: unknown[]) => {\n const start = performance.now();\n const result = original.apply(target, args);\n if (result instanceof Promise) {\n return result.finally(() => logTiming(`${label}.${name} ${Math.round(performance.now() - start)}ms`));\n }\n return result;\n };\n }\n return target;\n}\n\n/** Every method name on `target`'s prototype chain, excluding `Object.prototype`. */\nfunction prototypeMethodNames(target: object): string[] {\n const names = new Set<string>();\n for (\n let proto = Object.getPrototypeOf(target);\n proto && proto !== Object.prototype;\n proto = Object.getPrototypeOf(proto)\n ) {\n for (const name of Object.getOwnPropertyNames(proto)) {\n if (name === 'constructor') continue;\n const descriptor = Object.getOwnPropertyDescriptor(proto, name);\n if (typeof descriptor?.value === 'function') names.add(name);\n }\n }\n return [...names];\n}\n\n/**\n * Wrap every async method on `target` so each call logs `<label>.<method>`\n * duration. Domains passed to a `registerDomain` method are instrumented in\n * place as `<label>.<domain name>`, so the handle it returns and later\n * `getDomain` lookups are the same timed object. Returns `target` unchanged\n * when timing is disabled.\n */\nexport function instrumentAsyncMethods<T extends object>(target: T, label: string): T {\n if (!factoryTimingEnabled) return target;\n const wrapped = new Map<PropertyKey, unknown>();\n return new Proxy(target, {\n get(obj, prop, receiver) {\n const value = Reflect.get(obj, prop, receiver);\n if (typeof value !== 'function' || typeof prop === 'symbol' || prop === 'constructor') return value;\n const cached = wrapped.get(prop);\n if (cached) return cached;\n const fn = (...args: unknown[]) => {\n const start = performance.now();\n const result = value.apply(obj, args);\n if (prop === 'registerDomain' && result && typeof result === 'object' && !(result instanceof Promise)) {\n const name = (result as { name?: unknown }).name;\n return instrumentMethods(result, `${label}.${String(name)}`, prototypeMethodNames(result));\n }\n if (result instanceof Promise) {\n return result.finally(() => logTiming(`${label}.${prop} ${Math.round(performance.now() - start)}ms`));\n }\n return result;\n };\n wrapped.set(prop, fn);\n return fn;\n },\n });\n}\n\n/** Run `fn`, always logging its duration under `phase`. Boot-phase use. */\nexport async function timedPhase<T>(phase: string, fn: () => Promise<T>): Promise<T> {\n const start = performance.now();\n try {\n return await fn();\n } finally {\n process.stderr.write(`[factory:timing] ${phase} ${Math.round(performance.now() - start)}ms\\n`);\n }\n}\n\n/**\n * Run `fn`, logging only when it exceeds `thresholdMs`. Request-path use,\n * where per-request logging would be noise but slow outliers must surface.\n */\nexport async function timedAboveThreshold<T>(phase: string, thresholdMs: number, fn: () => Promise<T>): Promise<T> {\n const start = performance.now();\n try {\n return await fn();\n } finally {\n const elapsed = performance.now() - start;\n if (elapsed > thresholdMs) {\n process.stderr.write(`[factory:timing] slow ${phase} ${Math.round(elapsed)}ms\\n`);\n }\n }\n}\n"],"mappings":";;;;;AAcA,MAAa,uBAAuB,kBAAkB,KAAK,QAAQ,IAAI,kBAAkB,EAAE;AAE3F,SAAS,UAAU,MAAoB;CACrC,QAAQ,OAAO,MAAM,oBAAoB,KAAK,GAAG;AACnD;;AAGA,eAAsB,MAAS,OAAe,IAAkC;CAC9E,IAAI,CAAC,sBAAsB,OAAO,GAAG;CACrC,MAAM,QAAQ,YAAY,IAAI;CAC9B,IAAI;EACF,OAAO,MAAM,GAAG;CAClB,UAAU;EACR,UAAU,GAAG,MAAM,GAAG,KAAK,MAAM,YAAY,IAAI,IAAI,KAAK,EAAE,GAAG;CACjE;AACF;;AAGA,SAAgB,gCAAgC;CAC9C,OAAO,OAAO,GAAY,SAA6C;EACrE,MAAM,QAAQ,YAAY,IAAI;EAC9B,IAAI;GACF,MAAM,KAAK;EACb,UAAU;GACR,UAAU,QAAQ,EAAE,IAAI,OAAO,GAAG,EAAE,IAAI,KAAK,GAAG,EAAE,IAAI,OAAO,GAAG,KAAK,MAAM,YAAY,IAAI,IAAI,KAAK,EAAE,GAAG;EAC3G;CACF;AACF;;;;;AAMA,SAAgB,kBAAoC,QAAW,OAAe,SAA+B;CAC3G,IAAI,CAAC,sBAAsB,OAAO;CAClC,MAAM,SAAS;CACf,KAAK,MAAM,QAAQ,SAAS;EAC1B,MAAM,WAAW,OAAO;EACxB,IAAI,OAAO,aAAa,YAAY;EACpC,OAAO,SAAS,GAAG,SAAoB;GACrC,MAAM,QAAQ,YAAY,IAAI;GAC9B,MAAM,SAAS,SAAS,MAAM,QAAQ,IAAI;GAC1C,IAAI,kBAAkB,SACpB,OAAO,OAAO,cAAc,UAAU,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,MAAM,YAAY,IAAI,IAAI,KAAK,EAAE,GAAG,CAAC;GAEtG,OAAO;EACT;CACF;CACA,OAAO;AACT;;AAGA,SAAS,qBAAqB,QAA0B;CACtD,MAAM,wBAAQ,IAAI,IAAY;CAC9B,KACE,IAAI,QAAQ,OAAO,eAAe,MAAM,GACxC,SAAS,UAAU,OAAO,WAC1B,QAAQ,OAAO,eAAe,KAAK,GAEnC,KAAK,MAAM,QAAQ,OAAO,oBAAoB,KAAK,GAAG;EACpD,IAAI,SAAS,eAAe;EAE5B,IAAI,OADe,OAAO,yBAAyB,OAAO,IACtC,CAAC,EAAE,UAAU,YAAY,MAAM,IAAI,IAAI;CAC7D;CAEF,OAAO,CAAC,GAAG,KAAK;AAClB;;;;;;;;AASA,SAAgB,uBAAyC,QAAW,OAAkB;CACpF,IAAI,CAAC,sBAAsB,OAAO;CAClC,MAAM,0BAAU,IAAI,IAA0B;CAC9C,OAAO,IAAI,MAAM,QAAQ,EACvB,IAAI,KAAK,MAAM,UAAU;EACvB,MAAM,QAAQ,QAAQ,IAAI,KAAK,MAAM,QAAQ;EAC7C,IAAI,OAAO,UAAU,cAAc,OAAO,SAAS,YAAY,SAAS,eAAe,OAAO;EAC9F,MAAM,SAAS,QAAQ,IAAI,IAAI;EAC/B,IAAI,QAAQ,OAAO;EACnB,MAAM,MAAM,GAAG,SAAoB;GACjC,MAAM,QAAQ,YAAY,IAAI;GAC9B,MAAM,SAAS,MAAM,MAAM,KAAK,IAAI;GACpC,IAAI,SAAS,oBAAoB,UAAU,OAAO,WAAW,YAAY,EAAE,kBAAkB,UAAU;IACrG,MAAM,OAAQ,OAA8B;IAC5C,OAAO,kBAAkB,QAAQ,GAAG,MAAM,GAAG,OAAO,IAAI,KAAK,qBAAqB,MAAM,CAAC;GAC3F;GACA,IAAI,kBAAkB,SACpB,OAAO,OAAO,cAAc,UAAU,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,MAAM,YAAY,IAAI,IAAI,KAAK,EAAE,GAAG,CAAC;GAEtG,OAAO;EACT;EACA,QAAQ,IAAI,MAAM,EAAE;EACpB,OAAO;CACT,EACF,CAAC;AACH;;AAGA,eAAsB,WAAc,OAAe,IAAkC;CACnF,MAAM,QAAQ,YAAY,IAAI;CAC9B,IAAI;EACF,OAAO,MAAM,GAAG;CAClB,UAAU;EACR,QAAQ,OAAO,MAAM,oBAAoB,MAAM,GAAG,KAAK,MAAM,YAAY,IAAI,IAAI,KAAK,EAAE,KAAK;CAC/F;AACF;;;;;AAMA,eAAsB,oBAAuB,OAAe,aAAqB,IAAkC;CACjH,MAAM,QAAQ,YAAY,IAAI;CAC9B,IAAI;EACF,OAAO,MAAM,GAAG;CAClB,UAAU;EACR,MAAM,UAAU,YAAY,IAAI,IAAI;EACpC,IAAI,UAAU,aACZ,QAAQ,OAAO,MAAM,yBAAyB,MAAM,GAAG,KAAK,MAAM,OAAO,EAAE,KAAK;CAEpF;AACF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ToolAnnotations } from '@mastra/core/tools';
|
|
2
|
+
/**
|
|
3
|
+
* MCP hints for each Factory tool, so clients like ChatGPT can tell lookups
|
|
4
|
+
* from writes and ask before a tool changes or discards state.
|
|
5
|
+
*/
|
|
6
|
+
export declare const FACTORY_TOOL_ANNOTATIONS: Record<string, ToolAnnotations>;
|
|
7
|
+
//# sourceMappingURL=annotations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"annotations.d.ts","sourceRoot":"","sources":["../../src/tools/annotations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAY1D;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAkBpE,CAAC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
//#region src/tools/annotations.ts
|
|
2
|
+
const read = {
|
|
3
|
+
readOnlyHint: true,
|
|
4
|
+
destructiveHint: false,
|
|
5
|
+
idempotentHint: true,
|
|
6
|
+
openWorldHint: false
|
|
7
|
+
};
|
|
8
|
+
const additive = {
|
|
9
|
+
readOnlyHint: false,
|
|
10
|
+
destructiveHint: false,
|
|
11
|
+
openWorldHint: false
|
|
12
|
+
};
|
|
13
|
+
const toggle = {
|
|
14
|
+
...additive,
|
|
15
|
+
idempotentHint: true
|
|
16
|
+
};
|
|
17
|
+
const changes = {
|
|
18
|
+
readOnlyHint: false,
|
|
19
|
+
destructiveHint: true,
|
|
20
|
+
openWorldHint: false
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* MCP hints for each Factory tool, so clients like ChatGPT can tell lookups
|
|
24
|
+
* from writes and ask before a tool changes or discards state.
|
|
25
|
+
*/
|
|
26
|
+
const FACTORY_TOOL_ANNOTATIONS = {
|
|
27
|
+
factory_list_work_items: {
|
|
28
|
+
...read,
|
|
29
|
+
title: "List work items"
|
|
30
|
+
},
|
|
31
|
+
factory_inspect_work_item: {
|
|
32
|
+
...read,
|
|
33
|
+
title: "Inspect a work item"
|
|
34
|
+
},
|
|
35
|
+
factory_list_attention: {
|
|
36
|
+
...read,
|
|
37
|
+
title: "List items that need attention"
|
|
38
|
+
},
|
|
39
|
+
factory_create_work_item: {
|
|
40
|
+
...additive,
|
|
41
|
+
title: "Create a work item"
|
|
42
|
+
},
|
|
43
|
+
factory_update_work_item: {
|
|
44
|
+
...changes,
|
|
45
|
+
title: "Update a work item"
|
|
46
|
+
},
|
|
47
|
+
factory_transition_work_item: {
|
|
48
|
+
...changes,
|
|
49
|
+
title: "Move a work item to another stage"
|
|
50
|
+
},
|
|
51
|
+
factory_retry_decision: {
|
|
52
|
+
...additive,
|
|
53
|
+
title: "Retry a failed decision"
|
|
54
|
+
},
|
|
55
|
+
factory_dismiss_decision: {
|
|
56
|
+
...changes,
|
|
57
|
+
title: "Dismiss a decision"
|
|
58
|
+
},
|
|
59
|
+
factory_resolve_proposal: {
|
|
60
|
+
...changes,
|
|
61
|
+
title: "Approve or reject a proposal"
|
|
62
|
+
},
|
|
63
|
+
factory_subscribe_work_item: {
|
|
64
|
+
...toggle,
|
|
65
|
+
title: "Follow a work item"
|
|
66
|
+
},
|
|
67
|
+
factory_unsubscribe_work_item: {
|
|
68
|
+
...toggle,
|
|
69
|
+
title: "Stop following a work item"
|
|
70
|
+
},
|
|
71
|
+
factory_start_session: {
|
|
72
|
+
...additive,
|
|
73
|
+
openWorldHint: true,
|
|
74
|
+
title: "Start an agent session"
|
|
75
|
+
},
|
|
76
|
+
factory_signal_session: {
|
|
77
|
+
...additive,
|
|
78
|
+
openWorldHint: true,
|
|
79
|
+
title: "Message a running session"
|
|
80
|
+
},
|
|
81
|
+
factory_message_origin: {
|
|
82
|
+
...additive,
|
|
83
|
+
title: "Reply to the session that started this one"
|
|
84
|
+
},
|
|
85
|
+
factory_answer_approval: {
|
|
86
|
+
...changes,
|
|
87
|
+
openWorldHint: true,
|
|
88
|
+
title: "Answer a session approval request"
|
|
89
|
+
},
|
|
90
|
+
search: {
|
|
91
|
+
...read,
|
|
92
|
+
title: "Search work items"
|
|
93
|
+
},
|
|
94
|
+
fetch: {
|
|
95
|
+
...read,
|
|
96
|
+
title: "Fetch a work item"
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
//#endregion
|
|
100
|
+
export { FACTORY_TOOL_ANNOTATIONS };
|
|
101
|
+
|
|
102
|
+
//# sourceMappingURL=annotations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"annotations.js","names":[],"sources":["../../src/tools/annotations.ts"],"sourcesContent":["import type { ToolAnnotations } from '@mastra/core/tools';\n\nconst read: ToolAnnotations = {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n};\nconst additive: ToolAnnotations = { readOnlyHint: false, destructiveHint: false, openWorldHint: false };\nconst toggle: ToolAnnotations = { ...additive, idempotentHint: true };\nconst changes: ToolAnnotations = { readOnlyHint: false, destructiveHint: true, openWorldHint: false };\n\n/**\n * MCP hints for each Factory tool, so clients like ChatGPT can tell lookups\n * from writes and ask before a tool changes or discards state.\n */\nexport const FACTORY_TOOL_ANNOTATIONS: Record<string, ToolAnnotations> = {\n factory_list_work_items: { ...read, title: 'List work items' },\n factory_inspect_work_item: { ...read, title: 'Inspect a work item' },\n factory_list_attention: { ...read, title: 'List items that need attention' },\n factory_create_work_item: { ...additive, title: 'Create a work item' },\n factory_update_work_item: { ...changes, title: 'Update a work item' },\n factory_transition_work_item: { ...changes, title: 'Move a work item to another stage' },\n factory_retry_decision: { ...additive, title: 'Retry a failed decision' },\n factory_dismiss_decision: { ...changes, title: 'Dismiss a decision' },\n factory_resolve_proposal: { ...changes, title: 'Approve or reject a proposal' },\n factory_subscribe_work_item: { ...toggle, title: 'Follow a work item' },\n factory_unsubscribe_work_item: { ...toggle, title: 'Stop following a work item' },\n factory_start_session: { ...additive, openWorldHint: true, title: 'Start an agent session' },\n factory_signal_session: { ...additive, openWorldHint: true, title: 'Message a running session' },\n factory_message_origin: { ...additive, title: 'Reply to the session that started this one' },\n factory_answer_approval: { ...changes, openWorldHint: true, title: 'Answer a session approval request' },\n search: { ...read, title: 'Search work items' },\n fetch: { ...read, title: 'Fetch a work item' },\n};\n"],"mappings":";AAEA,MAAM,OAAwB;CAC5B,cAAc;CACd,iBAAiB;CACjB,gBAAgB;CAChB,eAAe;AACjB;AACA,MAAM,WAA4B;CAAE,cAAc;CAAO,iBAAiB;CAAO,eAAe;AAAM;AACtG,MAAM,SAA0B;CAAE,GAAG;CAAU,gBAAgB;AAAK;AACpE,MAAM,UAA2B;CAAE,cAAc;CAAO,iBAAiB;CAAM,eAAe;AAAM;;;;;AAMpG,MAAa,2BAA4D;CACvE,yBAAyB;EAAE,GAAG;EAAM,OAAO;CAAkB;CAC7D,2BAA2B;EAAE,GAAG;EAAM,OAAO;CAAsB;CACnE,wBAAwB;EAAE,GAAG;EAAM,OAAO;CAAiC;CAC3E,0BAA0B;EAAE,GAAG;EAAU,OAAO;CAAqB;CACrE,0BAA0B;EAAE,GAAG;EAAS,OAAO;CAAqB;CACpE,8BAA8B;EAAE,GAAG;EAAS,OAAO;CAAoC;CACvF,wBAAwB;EAAE,GAAG;EAAU,OAAO;CAA0B;CACxE,0BAA0B;EAAE,GAAG;EAAS,OAAO;CAAqB;CACpE,0BAA0B;EAAE,GAAG;EAAS,OAAO;CAA+B;CAC9E,6BAA6B;EAAE,GAAG;EAAQ,OAAO;CAAqB;CACtE,+BAA+B;EAAE,GAAG;EAAQ,OAAO;CAA6B;CAChF,uBAAuB;EAAE,GAAG;EAAU,eAAe;EAAM,OAAO;CAAyB;CAC3F,wBAAwB;EAAE,GAAG;EAAU,eAAe;EAAM,OAAO;CAA4B;CAC/F,wBAAwB;EAAE,GAAG;EAAU,OAAO;CAA6C;CAC3F,yBAAyB;EAAE,GAAG;EAAS,eAAe;EAAM,OAAO;CAAoC;CACvG,QAAQ;EAAE,GAAG;EAAM,OAAO;CAAoB;CAC9C,OAAO;EAAE,GAAG;EAAM,OAAO;CAAoB;AAC/C"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IntegrationTools } from '../integrations/base.js';
|
|
2
|
+
import type { SourceControlSessionLookup } from '../session/factory-session.js';
|
|
3
|
+
import type { AuditRecorder } from '../storage/domains/audit/domain.js';
|
|
4
|
+
import type { WorkItemsStorage } from '../storage/domains/work-items/base.js';
|
|
5
|
+
import type { FactoryMessaging } from './messaging-tools.js';
|
|
6
|
+
import type { FactoryToolScope } from './scope.js';
|
|
7
|
+
export interface FactoryApprovalToolDependencies {
|
|
8
|
+
sessions: Pick<SourceControlSessionLookup, 'getBySessionId'>;
|
|
9
|
+
workItems: Pick<WorkItemsStorage, 'findActiveRunBindingForSession' | 'listSessionSubscriptions'>;
|
|
10
|
+
auditRecorder: AuditRecorder;
|
|
11
|
+
messaging: () => Promise<FactoryMessaging | null>;
|
|
12
|
+
}
|
|
13
|
+
/** Answers a tool call another session holds for approval. Only a signed-in person's turn may answer. */
|
|
14
|
+
export declare function createFactoryApprovalTools(scope: FactoryToolScope, deps: FactoryApprovalToolDependencies): IntegrationTools;
|
|
15
|
+
//# sourceMappingURL=approval-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-tools.d.ts","sourceRoot":"","sources":["../../src/tools/approval-tools.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAChF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,EAAE,IAAI,CAAC,0BAA0B,EAAE,gBAAgB,CAAC,CAAC;IAC7D,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,gCAAgC,GAAG,0BAA0B,CAAC,CAAC;IACjG,aAAa,EAAE,aAAa,CAAC;IAC7B,SAAS,EAAE,MAAM,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;CACnD;AAYD,yGAAyG;AACzG,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,+BAA+B,GACpC,gBAAgB,CAoFlB"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { requirePerson } from "./scope.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createTool } from "@mastra/core/tools";
|
|
4
|
+
//#region src/tools/approval-tools.ts
|
|
5
|
+
/** Answers a tool call another session holds for approval. Only a signed-in person's turn may answer. */
|
|
6
|
+
function createFactoryApprovalTools(scope, deps) {
|
|
7
|
+
return { factory_answer_approval: createTool({
|
|
8
|
+
id: "factory_answer_approval",
|
|
9
|
+
description: [
|
|
10
|
+
"Approve or decline a tool call a session you follow is waiting on, as you.",
|
|
11
|
+
"A `factory-session` notification of kind `approval` names the session, tool, arguments and toolCallId.",
|
|
12
|
+
"Answer only what the person in this conversation agreed to; decline with a reason the session will see."
|
|
13
|
+
].join(" "),
|
|
14
|
+
inputSchema: z.object({
|
|
15
|
+
sessionId: z.string().uuid().describe("The session holding the call, from the approval notice."),
|
|
16
|
+
toolCallId: z.string().trim().min(1).max(200),
|
|
17
|
+
decision: z.enum(["approve", "decline"]),
|
|
18
|
+
reason: z.string().trim().min(1).max(1e3).optional().describe("Why, when declining.")
|
|
19
|
+
}).strict(),
|
|
20
|
+
execute: async ({ sessionId, toolCallId, decision, reason }) => {
|
|
21
|
+
requirePerson(scope, "answer an approval");
|
|
22
|
+
const row = await deps.sessions.getBySessionId(sessionId);
|
|
23
|
+
if (!row || row.orgId !== scope.orgId || row.factoryProjectId !== scope.factoryProjectId) throw new Error(`Session ${sessionId} was not found in this Factory.`);
|
|
24
|
+
if (row.visibility === "private" && row.userId !== scope.userId) throw new Error(`Session ${sessionId} is private to its owner.`);
|
|
25
|
+
const binding = await deps.workItems.findActiveRunBindingForSession({
|
|
26
|
+
orgId: scope.orgId,
|
|
27
|
+
factoryProjectId: scope.factoryProjectId,
|
|
28
|
+
sessionId
|
|
29
|
+
});
|
|
30
|
+
const isOrigin = row.originSessionId === scope.sessionId;
|
|
31
|
+
const follows = binding !== null && (await deps.workItems.listSessionSubscriptions({
|
|
32
|
+
orgId: scope.orgId,
|
|
33
|
+
factoryProjectId: scope.factoryProjectId,
|
|
34
|
+
sessionId: scope.sessionId
|
|
35
|
+
})).some((subscription) => subscription.workItemId === binding.workItemId);
|
|
36
|
+
if (!isOrigin && !follows) throw new Error(`This session doesn't follow session ${sessionId}: follow its work item first, or answer from that session.`);
|
|
37
|
+
const messaging = await deps.messaging();
|
|
38
|
+
if (!messaging) throw new Error("Answering approvals is not available on this server.");
|
|
39
|
+
const live = await messaging.controller.getSession(sessionId);
|
|
40
|
+
if (!live?.approval.isArmed({ toolCallId })) throw new Error("Nothing is waiting on that tool call any more: the run that asked has ended or it was already answered. Message the session with factory_signal_session if it should try again.");
|
|
41
|
+
const result = live.respondToToolApproval({
|
|
42
|
+
decision,
|
|
43
|
+
toolCallId,
|
|
44
|
+
...decision === "decline" ? { declineContext: { reason: reason ?? "Declined by a session following it." } } : {}
|
|
45
|
+
});
|
|
46
|
+
if (!result.accepted) throw new Error(`The answer was not accepted (${result.reason}).`);
|
|
47
|
+
deps.auditRecorder.record({
|
|
48
|
+
orgId: scope.orgId,
|
|
49
|
+
actorId: scope.userId,
|
|
50
|
+
actorType: "human",
|
|
51
|
+
...scope.actorProfile ? { actorProfile: scope.actorProfile } : {},
|
|
52
|
+
action: decision === "approve" ? "factory.run.approved" : "factory.run.dismissed",
|
|
53
|
+
factoryProjectId: scope.factoryProjectId,
|
|
54
|
+
targets: [{
|
|
55
|
+
type: "session",
|
|
56
|
+
id: sessionId
|
|
57
|
+
}, ...binding ? [{
|
|
58
|
+
type: "work_item",
|
|
59
|
+
id: binding.workItemId
|
|
60
|
+
}] : []],
|
|
61
|
+
metadata: {
|
|
62
|
+
toolCallId,
|
|
63
|
+
sessionId,
|
|
64
|
+
decision,
|
|
65
|
+
cause: "factory_tools"
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
answered: decision,
|
|
70
|
+
sessionId,
|
|
71
|
+
toolCallId
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}) };
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
export { createFactoryApprovalTools };
|
|
78
|
+
|
|
79
|
+
//# sourceMappingURL=approval-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-tools.js","names":[],"sources":["../../src/tools/approval-tools.ts"],"sourcesContent":["import { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport type { IntegrationTools } from '../integrations/base.js';\nimport type { SourceControlSessionLookup } from '../session/factory-session.js';\nimport type { AuditRecorder } from '../storage/domains/audit/domain.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { FactoryMessaging } from './messaging-tools.js';\nimport { requirePerson } from './scope.js';\nimport type { FactoryToolScope } from './scope.js';\n\nexport interface FactoryApprovalToolDependencies {\n sessions: Pick<SourceControlSessionLookup, 'getBySessionId'>;\n workItems: Pick<WorkItemsStorage, 'findActiveRunBindingForSession' | 'listSessionSubscriptions'>;\n auditRecorder: AuditRecorder;\n messaging: () => Promise<FactoryMessaging | null>;\n}\n\n/** The parts of a live session an answer needs: its open approval gates. */\ninterface ApprovalGateSession {\n approval: { isArmed(filter: { toolCallId: string }): boolean };\n respondToToolApproval(input: {\n decision: 'approve' | 'decline';\n toolCallId: string;\n declineContext?: { reason?: string };\n }): { accepted: true } | { accepted: false; reason: string };\n}\n\n/** Answers a tool call another session holds for approval. Only a signed-in person's turn may answer. */\nexport function createFactoryApprovalTools(\n scope: FactoryToolScope,\n deps: FactoryApprovalToolDependencies,\n): IntegrationTools {\n const answer = createTool({\n id: 'factory_answer_approval',\n description: [\n 'Approve or decline a tool call a session you follow is waiting on, as you.',\n 'A `factory-session` notification of kind `approval` names the session, tool, arguments and toolCallId.',\n 'Answer only what the person in this conversation agreed to; decline with a reason the session will see.',\n ].join(' '),\n inputSchema: z\n .object({\n sessionId: z.string().uuid().describe('The session holding the call, from the approval notice.'),\n toolCallId: z.string().trim().min(1).max(200),\n decision: z.enum(['approve', 'decline']),\n reason: z.string().trim().min(1).max(1000).optional().describe('Why, when declining.'),\n })\n .strict(),\n execute: async ({ sessionId, toolCallId, decision, reason }) => {\n requirePerson(scope, 'answer an approval');\n const row = await deps.sessions.getBySessionId(sessionId);\n if (!row || row.orgId !== scope.orgId || row.factoryProjectId !== scope.factoryProjectId) {\n throw new Error(`Session ${sessionId} was not found in this Factory.`);\n }\n if (row.visibility === 'private' && row.userId !== scope.userId) {\n throw new Error(`Session ${sessionId} is private to its owner.`);\n }\n\n // Only the sessions it reports to may answer it: its origin, or followers of its item.\n const binding = await deps.workItems.findActiveRunBindingForSession({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n sessionId,\n });\n const isOrigin = row.originSessionId === scope.sessionId;\n const follows =\n binding !== null &&\n (\n await deps.workItems.listSessionSubscriptions({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n sessionId: scope.sessionId,\n })\n ).some(subscription => subscription.workItemId === binding.workItemId);\n if (!isOrigin && !follows) {\n throw new Error(\n `This session doesn't follow session ${sessionId}: follow its work item first, or answer from that session.`,\n );\n }\n\n const messaging = await deps.messaging();\n if (!messaging) throw new Error('Answering approvals is not available on this server.');\n const live = (await messaging.controller.getSession(sessionId)) as ApprovalGateSession | null;\n if (!live?.approval.isArmed({ toolCallId })) {\n throw new Error(\n 'Nothing is waiting on that tool call any more: the run that asked has ended or it was already answered. ' +\n 'Message the session with factory_signal_session if it should try again.',\n );\n }\n const result = live.respondToToolApproval({\n decision,\n toolCallId,\n ...(decision === 'decline'\n ? { declineContext: { reason: reason ?? 'Declined by a session following it.' } }\n : {}),\n });\n if (!result.accepted) throw new Error(`The answer was not accepted (${result.reason}).`);\n\n deps.auditRecorder.record({\n orgId: scope.orgId,\n actorId: scope.userId,\n actorType: 'human',\n ...(scope.actorProfile ? { actorProfile: scope.actorProfile } : {}),\n action: decision === 'approve' ? 'factory.run.approved' : 'factory.run.dismissed',\n factoryProjectId: scope.factoryProjectId,\n targets: [\n { type: 'session', id: sessionId },\n ...(binding ? [{ type: 'work_item' as const, id: binding.workItemId }] : []),\n ],\n metadata: { toolCallId, sessionId, decision, cause: 'factory_tools' },\n });\n return { answered: decision, sessionId, toolCallId };\n },\n });\n\n return { factory_answer_approval: answer };\n}\n"],"mappings":";;;;;AA6BA,SAAgB,2BACd,OACA,MACkB;CAmFlB,OAAO,EAAE,yBAlFM,WAAW;EACxB,IAAI;EACJ,aAAa;GACX;GACA;GACA;EACF,CAAC,CAAC,KAAK,GAAG;EACV,aAAa,EACV,OAAO;GACN,WAAW,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,SAAS,yDAAyD;GAC/F,YAAY,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG;GAC5C,UAAU,EAAE,KAAK,CAAC,WAAW,SAAS,CAAC;GACvC,QAAQ,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAI,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,sBAAsB;EACvF,CAAC,CAAC,CACD,OAAO;EACV,SAAS,OAAO,EAAE,WAAW,YAAY,UAAU,aAAa;GAC9D,cAAc,OAAO,oBAAoB;GACzC,MAAM,MAAM,MAAM,KAAK,SAAS,eAAe,SAAS;GACxD,IAAI,CAAC,OAAO,IAAI,UAAU,MAAM,SAAS,IAAI,qBAAqB,MAAM,kBACtE,MAAM,IAAI,MAAM,WAAW,UAAU,gCAAgC;GAEvE,IAAI,IAAI,eAAe,aAAa,IAAI,WAAW,MAAM,QACvD,MAAM,IAAI,MAAM,WAAW,UAAU,0BAA0B;GAIjE,MAAM,UAAU,MAAM,KAAK,UAAU,+BAA+B;IAClE,OAAO,MAAM;IACb,kBAAkB,MAAM;IACxB;GACF,CAAC;GACD,MAAM,WAAW,IAAI,oBAAoB,MAAM;GAC/C,MAAM,UACJ,YAAY,SAEV,MAAM,KAAK,UAAU,yBAAyB;IAC5C,OAAO,MAAM;IACb,kBAAkB,MAAM;IACxB,WAAW,MAAM;GACnB,CAAC,EAAA,CACD,MAAK,iBAAgB,aAAa,eAAe,QAAQ,UAAU;GACvE,IAAI,CAAC,YAAY,CAAC,SAChB,MAAM,IAAI,MACR,uCAAuC,UAAU,2DACnD;GAGF,MAAM,YAAY,MAAM,KAAK,UAAU;GACvC,IAAI,CAAC,WAAW,MAAM,IAAI,MAAM,sDAAsD;GACtF,MAAM,OAAQ,MAAM,UAAU,WAAW,WAAW,SAAS;GAC7D,IAAI,CAAC,MAAM,SAAS,QAAQ,EAAE,WAAW,CAAC,GACxC,MAAM,IAAI,MACR,iLAEF;GAEF,MAAM,SAAS,KAAK,sBAAsB;IACxC;IACA;IACA,GAAI,aAAa,YACb,EAAE,gBAAgB,EAAE,QAAQ,UAAU,sCAAsC,EAAE,IAC9E,CAAC;GACP,CAAC;GACD,IAAI,CAAC,OAAO,UAAU,MAAM,IAAI,MAAM,gCAAgC,OAAO,OAAO,GAAG;GAEvF,KAAK,cAAc,OAAO;IACxB,OAAO,MAAM;IACb,SAAS,MAAM;IACf,WAAW;IACX,GAAI,MAAM,eAAe,EAAE,cAAc,MAAM,aAAa,IAAI,CAAC;IACjE,QAAQ,aAAa,YAAY,yBAAyB;IAC1D,kBAAkB,MAAM;IACxB,SAAS,CACP;KAAE,MAAM;KAAW,IAAI;IAAU,GACjC,GAAI,UAAU,CAAC;KAAE,MAAM;KAAsB,IAAI,QAAQ;IAAW,CAAC,IAAI,CAAC,CAC5E;IACA,UAAU;KAAE;KAAY;KAAW;KAAU,OAAO;IAAgB;GACtE,CAAC;GACD,OAAO;IAAE,UAAU;IAAU;IAAW;GAAW;EACrD;CACF,CAEuC,EAAE;AAC3C"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { MountedMastraCode } from '@mastra/code-sdk';
|
|
2
|
+
import type { SendNotificationSignalInput } from '@mastra/core/notifications';
|
|
3
|
+
import type { SourceControlSessionLookup } from '../session/factory-session.js';
|
|
4
|
+
import type { SessionRunBindings } from '../session/session-threads.js';
|
|
5
|
+
import type { WorkItemsStorage } from '../storage/domains/work-items/base.js';
|
|
6
|
+
/** Notifications between Factory sessions all carry this source. */
|
|
7
|
+
export declare const FACTORY_SESSION_NOTIFICATION_SOURCE = "factory-session";
|
|
8
|
+
export type FactoryDeliveryController = Pick<MountedMastraCode['controller'], 'queryThreadById' | 'queryThreads' | 'setThreadModel' | 'listAvailableModels' | 'getSession' | 'createSession' | 'getCurrentAgent'>;
|
|
9
|
+
export interface FactoryDeliveryTarget {
|
|
10
|
+
/** The target's source-control session id. */
|
|
11
|
+
sessionId: string;
|
|
12
|
+
/** The stored thread the notification belongs to. Never the session's current thread by default. */
|
|
13
|
+
threadId: string;
|
|
14
|
+
}
|
|
15
|
+
export type FactoryDeliveryResult =
|
|
16
|
+
/**
|
|
17
|
+
* `woken`: the target was idle and the notification started a run.
|
|
18
|
+
* `mid-run`: the target was already running; it reads the notification when that run
|
|
19
|
+
* next checks its inbox, not now.
|
|
20
|
+
*/
|
|
21
|
+
{
|
|
22
|
+
delivered: 'woken' | 'mid-run' | 'persisted' | 'summarized';
|
|
23
|
+
notificationId: string;
|
|
24
|
+
reason?: string;
|
|
25
|
+
} | {
|
|
26
|
+
delivered: 'skipped';
|
|
27
|
+
notificationId?: string;
|
|
28
|
+
reason: 'thread-not-found' | 'thread-not-owned' | 'session-not-found' | 'offline' | 'discarded';
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Deliver a notification to a Factory session's stored thread.
|
|
32
|
+
*
|
|
33
|
+
* Sent through the target's agent with an explicit `{ resourceId, threadId }`,
|
|
34
|
+
* never `Session.sendNotificationSignal`: that one builds the wake from the
|
|
35
|
+
* sender's request context and aims at the target's current thread. On this
|
|
36
|
+
* path the SDK builds the wake from the session that owns the target resource,
|
|
37
|
+
* so the woken run is the target row's owner, with that owner's credentials.
|
|
38
|
+
*
|
|
39
|
+
* A target that isn't live is created first, as its row's owner and bound to
|
|
40
|
+
* the stored thread: waking a resource no session owns starts a run with no
|
|
41
|
+
* model. A live session's thread is never switched.
|
|
42
|
+
*/
|
|
43
|
+
export declare function deliverFactorySessionNotification({ controller, sessions, target, notification, onlyIfLive, runs, }: {
|
|
44
|
+
controller: FactoryDeliveryController;
|
|
45
|
+
sessions: Pick<SourceControlSessionLookup, 'getBySessionId'>;
|
|
46
|
+
/** Run bindings, which decide whether a session may use its Factory's resource. */
|
|
47
|
+
runs: SessionRunBindings | undefined;
|
|
48
|
+
target: FactoryDeliveryTarget;
|
|
49
|
+
notification: Omit<SendNotificationSignalInput, 'source'>;
|
|
50
|
+
/** Skip, without creating anything, when no live session owns the target (stage notices). */
|
|
51
|
+
onlyIfLive?: boolean;
|
|
52
|
+
}): Promise<FactoryDeliveryResult>;
|
|
53
|
+
export interface FactorySessionActivity {
|
|
54
|
+
/** Whether the session's agent is mid-run in this process. */
|
|
55
|
+
running: boolean;
|
|
56
|
+
/** When the session's thread last changed (a message saved, a run step persisted), ISO. */
|
|
57
|
+
lastActivityAt: string | null;
|
|
58
|
+
}
|
|
59
|
+
/** Whether a session is busy right now, and when its thread last moved, so a stuck run is told apart from a long one. */
|
|
60
|
+
export declare function sessionActivity(controller: Pick<FactoryDeliveryController, 'getSession' | 'queryThreadById'>, sessionId: string, threadId: string): Promise<FactorySessionActivity>;
|
|
61
|
+
/**
|
|
62
|
+
* The thread a message to a session should land on: the thread its role runs
|
|
63
|
+
* on, else the thread it has open, else its most recently updated thread.
|
|
64
|
+
*/
|
|
65
|
+
export declare function resolveSessionThread({ controller, sessions, workItems, sessionId, }: {
|
|
66
|
+
controller: Pick<FactoryDeliveryController, 'getSession' | 'queryThreads' | 'queryThreadById'>;
|
|
67
|
+
sessions: Pick<SourceControlSessionLookup, 'getBySessionId'>;
|
|
68
|
+
workItems: Pick<WorkItemsStorage, 'findActiveRunBindingForSession' | 'hasRunBindingForSession'>;
|
|
69
|
+
sessionId: string;
|
|
70
|
+
}): Promise<string | null>;
|
|
71
|
+
//# sourceMappingURL=deliver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deliver.d.ts","sourceRoot":"","sources":["../../src/tools/deliver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;AAG9E,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAEhF,OAAO,KAAK,EAA2B,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACjG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE9E,oEAAoE;AACpE,eAAO,MAAM,mCAAmC,oBAAoB,CAAC;AAErE,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAC1C,iBAAiB,CAAC,YAAY,CAAC,EAC7B,iBAAiB,GACjB,cAAc,GACd,gBAAgB,GAChB,qBAAqB,GACrB,YAAY,GACZ,eAAe,GACf,iBAAiB,CACpB,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,oGAAoG;IACpG,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,qBAAqB;AAC/B;;;;GAIG;AACD;IAAE,SAAS,EAAE,OAAO,GAAG,SAAS,GAAG,WAAW,GAAG,YAAY,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACxG;IACE,SAAS,EAAE,SAAS,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,kBAAkB,GAAG,kBAAkB,GAAG,mBAAmB,GAAG,SAAS,GAAG,WAAW,CAAC;CACjG,CAAC;AAEN;;;;;;;;;;;;GAYG;AACH,wBAAsB,iCAAiC,CAAC,EACtD,UAAU,EACV,QAAQ,EACR,MAAM,EACN,YAAY,EACZ,UAAkB,EAClB,IAAI,GACL,EAAE;IACD,UAAU,EAAE,yBAAyB,CAAC;IACtC,QAAQ,EAAE,IAAI,CAAC,0BAA0B,EAAE,gBAAgB,CAAC,CAAC;IAC7D,mFAAmF;IACnF,IAAI,EAAE,kBAAkB,GAAG,SAAS,CAAC;IACrC,MAAM,EAAE,qBAAqB,CAAC;IAC9B,YAAY,EAAE,IAAI,CAAC,2BAA2B,EAAE,QAAQ,CAAC,CAAC;IAC1D,6FAA6F;IAC7F,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAwDjC;AAED,MAAM,WAAW,sBAAsB;IACrC,8DAA8D;IAC9D,OAAO,EAAE,OAAO,CAAC;IACjB,2FAA2F;IAC3F,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,yHAAyH;AACzH,wBAAsB,eAAe,CACnC,UAAU,EAAE,IAAI,CAAC,yBAAyB,EAAE,YAAY,GAAG,iBAAiB,CAAC,EAC7E,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,sBAAsB,CAAC,CAUjC;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,EACzC,UAAU,EACV,QAAQ,EACR,SAAS,EACT,SAAS,GACV,EAAE;IACD,UAAU,EAAE,IAAI,CAAC,yBAAyB,EAAE,YAAY,GAAG,cAAc,GAAG,iBAAiB,CAAC,CAAC;IAC/F,QAAQ,EAAE,IAAI,CAAC,0BAA0B,EAAE,gBAAgB,CAAC,CAAC;IAC7D,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,gCAAgC,GAAG,yBAAyB,CAAC,CAAC;IAChG,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiBzB"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { listFactorySessionThreads, sessionMemoryResourceId, sessionStampCounts } from "../session/session-threads.js";
|
|
2
|
+
import { createSessionAsRowOwner } from "../integrations/subscription-session.js";
|
|
3
|
+
//#region src/tools/deliver.ts
|
|
4
|
+
/** Notifications between Factory sessions all carry this source. */
|
|
5
|
+
const FACTORY_SESSION_NOTIFICATION_SOURCE = "factory-session";
|
|
6
|
+
/**
|
|
7
|
+
* Deliver a notification to a Factory session's stored thread.
|
|
8
|
+
*
|
|
9
|
+
* Sent through the target's agent with an explicit `{ resourceId, threadId }`,
|
|
10
|
+
* never `Session.sendNotificationSignal`: that one builds the wake from the
|
|
11
|
+
* sender's request context and aims at the target's current thread. On this
|
|
12
|
+
* path the SDK builds the wake from the session that owns the target resource,
|
|
13
|
+
* so the woken run is the target row's owner, with that owner's credentials.
|
|
14
|
+
*
|
|
15
|
+
* A target that isn't live is created first, as its row's owner and bound to
|
|
16
|
+
* the stored thread: waking a resource no session owns starts a run with no
|
|
17
|
+
* model. A live session's thread is never switched.
|
|
18
|
+
*/
|
|
19
|
+
async function deliverFactorySessionNotification({ controller, sessions, target, notification, onlyIfLive = false, runs }) {
|
|
20
|
+
const thread = await controller.queryThreadById({ threadId: target.threadId });
|
|
21
|
+
if (!thread) return {
|
|
22
|
+
delivered: "skipped",
|
|
23
|
+
reason: "thread-not-found"
|
|
24
|
+
};
|
|
25
|
+
let targetRow;
|
|
26
|
+
const loadRow = () => targetRow ??= sessions.getBySessionId(target.sessionId);
|
|
27
|
+
if (!await isSessionThread(controller, runs, thread, target.sessionId, loadRow)) return {
|
|
28
|
+
delivered: "skipped",
|
|
29
|
+
reason: "thread-not-owned"
|
|
30
|
+
};
|
|
31
|
+
const ownerResourceId = thread.resourceId || target.sessionId;
|
|
32
|
+
let session = await controller.getSession(target.sessionId);
|
|
33
|
+
if (!session) {
|
|
34
|
+
if (onlyIfLive) return {
|
|
35
|
+
delivered: "skipped",
|
|
36
|
+
reason: "offline"
|
|
37
|
+
};
|
|
38
|
+
const row = await loadRow();
|
|
39
|
+
if (!row?.factoryProjectId) return {
|
|
40
|
+
delivered: "skipped",
|
|
41
|
+
reason: "session-not-found"
|
|
42
|
+
};
|
|
43
|
+
session = await createSessionAsRowOwner(controller, {
|
|
44
|
+
sessionId: target.sessionId,
|
|
45
|
+
ownerResourceId,
|
|
46
|
+
row,
|
|
47
|
+
tags: {
|
|
48
|
+
factoryProjectId: row.factoryProjectId,
|
|
49
|
+
...row.projectRepositoryId ? { projectRepositoryId: row.projectRepositoryId } : {}
|
|
50
|
+
},
|
|
51
|
+
threadId: target.threadId
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const busy = session.stream.isActive() && session.thread.getId() === target.threadId;
|
|
55
|
+
const result = await controller.getCurrentAgent(session).sendNotificationSignal({
|
|
56
|
+
...notification,
|
|
57
|
+
source: FACTORY_SESSION_NOTIFICATION_SOURCE
|
|
58
|
+
}, {
|
|
59
|
+
resourceId: ownerResourceId,
|
|
60
|
+
threadId: target.threadId
|
|
61
|
+
});
|
|
62
|
+
await Promise.all([result.persisted, result.accepted].filter(Boolean));
|
|
63
|
+
const notificationId = result.record.id;
|
|
64
|
+
const reason = result.decision.reason ? { reason: result.decision.reason } : {};
|
|
65
|
+
switch (result.decision.action) {
|
|
66
|
+
case "deliver": return {
|
|
67
|
+
delivered: busy ? "mid-run" : "woken",
|
|
68
|
+
notificationId,
|
|
69
|
+
...reason
|
|
70
|
+
};
|
|
71
|
+
case "summarize": return {
|
|
72
|
+
delivered: "summarized",
|
|
73
|
+
notificationId,
|
|
74
|
+
...reason
|
|
75
|
+
};
|
|
76
|
+
case "discard": return {
|
|
77
|
+
delivered: "skipped",
|
|
78
|
+
notificationId,
|
|
79
|
+
reason: "discarded"
|
|
80
|
+
};
|
|
81
|
+
default: return {
|
|
82
|
+
delivered: "persisted",
|
|
83
|
+
notificationId,
|
|
84
|
+
...reason
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Whether a session is busy right now, and when its thread last moved, so a stuck run is told apart from a long one. */
|
|
89
|
+
async function sessionActivity(controller, sessionId, threadId) {
|
|
90
|
+
const [live, thread] = await Promise.all([controller.getSession(sessionId), controller.queryThreadById({ threadId })]);
|
|
91
|
+
const updatedAt = thread?.updatedAt ? new Date(thread.updatedAt) : null;
|
|
92
|
+
return {
|
|
93
|
+
running: live?.stream.isActive() ?? false,
|
|
94
|
+
lastActivityAt: updatedAt && Number.isFinite(updatedAt.getTime()) ? updatedAt.toISOString() : null
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The thread a message to a session should land on: the thread its role runs
|
|
99
|
+
* on, else the thread it has open, else its most recently updated thread.
|
|
100
|
+
*/
|
|
101
|
+
async function resolveSessionThread({ controller, sessions, workItems, sessionId }) {
|
|
102
|
+
const row = await sessions.getBySessionId(sessionId);
|
|
103
|
+
if (row?.factoryProjectId) {
|
|
104
|
+
const binding = await workItems.findActiveRunBindingForSession({
|
|
105
|
+
orgId: row.orgId,
|
|
106
|
+
factoryProjectId: row.factoryProjectId,
|
|
107
|
+
sessionId
|
|
108
|
+
});
|
|
109
|
+
if (binding) return binding.threadId;
|
|
110
|
+
}
|
|
111
|
+
const current = (await controller.getSession(sessionId))?.thread.getId();
|
|
112
|
+
if (current) return current;
|
|
113
|
+
return [...await listFactorySessionThreads(controller, row ?? {
|
|
114
|
+
sessionId,
|
|
115
|
+
userId: ""
|
|
116
|
+
}, workItems)].sort((left, right) => right.updatedAt.getTime() - left.updatedAt.getTime())[0]?.id ?? null;
|
|
117
|
+
}
|
|
118
|
+
async function isSessionThread(controller, runs, thread, sessionId, loadRow) {
|
|
119
|
+
if (thread.resourceId === sessionId) return true;
|
|
120
|
+
if (thread.id !== sessionId && thread.metadata?.factorySessionId !== sessionId) return false;
|
|
121
|
+
const row = await loadRow();
|
|
122
|
+
if (!row) return false;
|
|
123
|
+
if (thread.id !== sessionId && !sessionStampCounts(row, thread.resourceId)) return false;
|
|
124
|
+
return thread.resourceId === await sessionMemoryResourceId(controller, row, runs);
|
|
125
|
+
}
|
|
126
|
+
//#endregion
|
|
127
|
+
export { FACTORY_SESSION_NOTIFICATION_SOURCE, deliverFactorySessionNotification, resolveSessionThread, sessionActivity };
|
|
128
|
+
|
|
129
|
+
//# sourceMappingURL=deliver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deliver.js","names":[],"sources":["../../src/tools/deliver.ts"],"sourcesContent":["import type { MountedMastraCode } from '@mastra/code-sdk';\nimport type { SendNotificationSignalInput } from '@mastra/core/notifications';\n\nimport { createSessionAsRowOwner } from '../integrations/subscription-session.js';\nimport type { SourceControlSessionLookup } from '../session/factory-session.js';\nimport { listFactorySessionThreads, sessionMemoryResourceId, sessionStampCounts } from '../session/session-threads.js';\nimport type { FactorySessionOwnership, SessionRunBindings } from '../session/session-threads.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\n\n/** Notifications between Factory sessions all carry this source. */\nexport const FACTORY_SESSION_NOTIFICATION_SOURCE = 'factory-session';\n\nexport type FactoryDeliveryController = Pick<\n MountedMastraCode['controller'],\n | 'queryThreadById'\n | 'queryThreads'\n | 'setThreadModel'\n | 'listAvailableModels'\n | 'getSession'\n | 'createSession'\n | 'getCurrentAgent'\n>;\n\nexport interface FactoryDeliveryTarget {\n /** The target's source-control session id. */\n sessionId: string;\n /** The stored thread the notification belongs to. Never the session's current thread by default. */\n threadId: string;\n}\n\nexport type FactoryDeliveryResult =\n /**\n * `woken`: the target was idle and the notification started a run.\n * `mid-run`: the target was already running; it reads the notification when that run\n * next checks its inbox, not now.\n */\n | { delivered: 'woken' | 'mid-run' | 'persisted' | 'summarized'; notificationId: string; reason?: string }\n | {\n delivered: 'skipped';\n notificationId?: string;\n reason: 'thread-not-found' | 'thread-not-owned' | 'session-not-found' | 'offline' | 'discarded';\n };\n\n/**\n * Deliver a notification to a Factory session's stored thread.\n *\n * Sent through the target's agent with an explicit `{ resourceId, threadId }`,\n * never `Session.sendNotificationSignal`: that one builds the wake from the\n * sender's request context and aims at the target's current thread. On this\n * path the SDK builds the wake from the session that owns the target resource,\n * so the woken run is the target row's owner, with that owner's credentials.\n *\n * A target that isn't live is created first, as its row's owner and bound to\n * the stored thread: waking a resource no session owns starts a run with no\n * model. A live session's thread is never switched.\n */\nexport async function deliverFactorySessionNotification({\n controller,\n sessions,\n target,\n notification,\n onlyIfLive = false,\n runs,\n}: {\n controller: FactoryDeliveryController;\n sessions: Pick<SourceControlSessionLookup, 'getBySessionId'>;\n /** Run bindings, which decide whether a session may use its Factory's resource. */\n runs: SessionRunBindings | undefined;\n target: FactoryDeliveryTarget;\n notification: Omit<SendNotificationSignalInput, 'source'>;\n /** Skip, without creating anything, when no live session owns the target (stage notices). */\n onlyIfLive?: boolean;\n}): Promise<FactoryDeliveryResult> {\n const thread = await controller.queryThreadById({ threadId: target.threadId });\n if (!thread) return { delivered: 'skipped', reason: 'thread-not-found' };\n // Sessions share their Factory's or owner's resource, so a thread is this\n // session's when it is the session's own thread or carries its session stamp\n // (or, before migration, sits under the session id). Anything else is not\n // this session's, whatever the caller paired it with.\n let targetRow: ReturnType<SourceControlSessionLookup['getBySessionId']> | undefined;\n const loadRow = () => (targetRow ??= sessions.getBySessionId(target.sessionId));\n if (!(await isSessionThread(controller, runs, thread, target.sessionId, loadRow))) {\n return { delivered: 'skipped', reason: 'thread-not-owned' };\n }\n const ownerResourceId = thread.resourceId || target.sessionId;\n\n // No request context: without a user the controller's access check is a no-op,\n // and this is a server-side lookup on the target's behalf, not the sender's.\n let session = await controller.getSession(target.sessionId);\n if (!session) {\n if (onlyIfLive) return { delivered: 'skipped', reason: 'offline' };\n const row = await loadRow();\n if (!row?.factoryProjectId) return { delivered: 'skipped', reason: 'session-not-found' };\n session = await createSessionAsRowOwner(controller, {\n sessionId: target.sessionId,\n ownerResourceId,\n row,\n tags: {\n factoryProjectId: row.factoryProjectId,\n ...(row.projectRepositoryId ? { projectRepositoryId: row.projectRepositoryId } : {}),\n },\n threadId: target.threadId,\n });\n }\n\n // Read before sending: the send itself starts a run on an idle target. Delivery is per thread,\n // so a run on another thread of the same session leaves the target thread idle.\n const busy = session.stream.isActive() && session.thread.getId() === target.threadId;\n const result = await controller\n .getCurrentAgent(session)\n .sendNotificationSignal(\n { ...notification, source: FACTORY_SESSION_NOTIFICATION_SOURCE },\n { resourceId: ownerResourceId, threadId: target.threadId },\n );\n await Promise.all([result.persisted, result.accepted].filter(Boolean));\n\n const notificationId = result.record.id;\n const reason = result.decision.reason ? { reason: result.decision.reason } : {};\n switch (result.decision.action) {\n case 'deliver':\n return { delivered: busy ? 'mid-run' : 'woken', notificationId, ...reason };\n case 'summarize':\n return { delivered: 'summarized', notificationId, ...reason };\n case 'discard':\n return { delivered: 'skipped', notificationId, reason: 'discarded' };\n default:\n return { delivered: 'persisted', notificationId, ...reason };\n }\n}\n\nexport interface FactorySessionActivity {\n /** Whether the session's agent is mid-run in this process. */\n running: boolean;\n /** When the session's thread last changed (a message saved, a run step persisted), ISO. */\n lastActivityAt: string | null;\n}\n\n/** Whether a session is busy right now, and when its thread last moved, so a stuck run is told apart from a long one. */\nexport async function sessionActivity(\n controller: Pick<FactoryDeliveryController, 'getSession' | 'queryThreadById'>,\n sessionId: string,\n threadId: string,\n): Promise<FactorySessionActivity> {\n const [live, thread] = await Promise.all([\n controller.getSession(sessionId),\n controller.queryThreadById({ threadId }),\n ]);\n const updatedAt = thread?.updatedAt ? new Date(thread.updatedAt) : null;\n return {\n running: live?.stream.isActive() ?? false,\n lastActivityAt: updatedAt && Number.isFinite(updatedAt.getTime()) ? updatedAt.toISOString() : null,\n };\n}\n\n/**\n * The thread a message to a session should land on: the thread its role runs\n * on, else the thread it has open, else its most recently updated thread.\n */\nexport async function resolveSessionThread({\n controller,\n sessions,\n workItems,\n sessionId,\n}: {\n controller: Pick<FactoryDeliveryController, 'getSession' | 'queryThreads' | 'queryThreadById'>;\n sessions: Pick<SourceControlSessionLookup, 'getBySessionId'>;\n workItems: Pick<WorkItemsStorage, 'findActiveRunBindingForSession' | 'hasRunBindingForSession'>;\n sessionId: string;\n}): Promise<string | null> {\n const row = await sessions.getBySessionId(sessionId);\n if (row?.factoryProjectId) {\n const binding = await workItems.findActiveRunBindingForSession({\n orgId: row.orgId,\n factoryProjectId: row.factoryProjectId,\n sessionId,\n });\n if (binding) return binding.threadId;\n }\n const live = await controller.getSession(sessionId);\n const current = live?.thread.getId();\n if (current) return current;\n // Without a row no resource is the session's to claim stamped threads on.\n const threads = await listFactorySessionThreads(controller, row ?? { sessionId, userId: '' }, workItems);\n const newest = [...threads].sort((left, right) => right.updatedAt.getTime() - left.updatedAt.getTime())[0];\n return newest?.id ?? null;\n}\n\nasync function isSessionThread(\n controller: Pick<FactoryDeliveryController, 'queryThreadById'>,\n runs: SessionRunBindings | undefined,\n thread: { id: string; resourceId?: string | null; metadata?: Record<string, unknown> | null },\n sessionId: string,\n loadRow: () => Promise<FactorySessionOwnership | null>,\n): Promise<boolean> {\n // Before resources were shared a session's threads ran under its own id.\n if (thread.resourceId === sessionId) return true;\n if (thread.id !== sessionId && thread.metadata?.factorySessionId !== sessionId) return false;\n // Thread ids and stamps are client-writable: either only counts under the\n // session's own memory resource.\n const row = await loadRow();\n if (!row) return false;\n if (thread.id !== sessionId && !sessionStampCounts(row, thread.resourceId)) return false;\n return thread.resourceId === (await sessionMemoryResourceId(controller, row, runs));\n}\n"],"mappings":"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"}
|