@mastra/factory 0.24.0-alpha.7 → 0.24.0-alpha.9

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.
Files changed (125) hide show
  1. package/dist/boards/review.d.ts.map +1 -1
  2. package/dist/boards/review.js +21 -1
  3. package/dist/boards/review.js.map +1 -1
  4. package/dist/boards/work-transition-policy.d.ts.map +1 -1
  5. package/dist/boards/work-transition-policy.js +2 -1
  6. package/dist/boards/work-transition-policy.js.map +1 -1
  7. package/dist/factory.d.ts.map +1 -1
  8. package/dist/factory.js +91 -13
  9. package/dist/factory.js.map +1 -1
  10. package/dist/integrations/github/rules.d.ts.map +1 -1
  11. package/dist/integrations/github/rules.js +26 -22
  12. package/dist/integrations/github/rules.js.map +1 -1
  13. package/dist/integrations/github/sandbox.d.ts +2 -3
  14. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  15. package/dist/integrations/github/sandbox.js +78 -7
  16. package/dist/integrations/github/sandbox.js.map +1 -1
  17. package/dist/integrations/gitlab/rules.d.ts.map +1 -1
  18. package/dist/integrations/gitlab/rules.js +28 -21
  19. package/dist/integrations/gitlab/rules.js.map +1 -1
  20. package/dist/integrations/slack/integration.d.ts.map +1 -1
  21. package/dist/integrations/slack/integration.js +33 -6
  22. package/dist/integrations/slack/integration.js.map +1 -1
  23. package/dist/integrations/slack/slack.d.ts.map +1 -1
  24. package/dist/integrations/slack/slack.js +25 -24
  25. package/dist/integrations/slack/slack.js.map +1 -1
  26. package/dist/routes/attention-activity.d.ts.map +1 -1
  27. package/dist/routes/attention-activity.js +5 -2
  28. package/dist/routes/attention-activity.js.map +1 -1
  29. package/dist/routes/attention-providers.d.ts +0 -1
  30. package/dist/routes/attention-providers.d.ts.map +1 -1
  31. package/dist/routes/attention-providers.js +5 -6
  32. package/dist/routes/attention-providers.js.map +1 -1
  33. package/dist/routes/config.d.ts +4 -0
  34. package/dist/routes/config.d.ts.map +1 -1
  35. package/dist/routes/config.js +20 -6
  36. package/dist/routes/config.js.map +1 -1
  37. package/dist/routes/provider-credentials.d.ts +2 -1
  38. package/dist/routes/provider-credentials.d.ts.map +1 -1
  39. package/dist/routes/provider-credentials.js +6 -2
  40. package/dist/routes/provider-credentials.js.map +1 -1
  41. package/dist/rules/dispatch-errors.d.ts.map +1 -1
  42. package/dist/rules/dispatch-errors.js +12 -0
  43. package/dist/rules/dispatch-errors.js.map +1 -1
  44. package/dist/rules/dispatcher.d.ts.map +1 -1
  45. package/dist/rules/dispatcher.js +42 -3
  46. package/dist/rules/dispatcher.js.map +1 -1
  47. package/dist/session/channel-reply-processor.d.ts +35 -0
  48. package/dist/session/channel-reply-processor.d.ts.map +1 -0
  49. package/dist/session/channel-reply-processor.js +46 -0
  50. package/dist/session/channel-reply-processor.js.map +1 -0
  51. package/dist/session/channel-session-link-processor.d.ts +39 -0
  52. package/dist/session/channel-session-link-processor.d.ts.map +1 -0
  53. package/dist/session/channel-session-link-processor.js +99 -0
  54. package/dist/session/channel-session-link-processor.js.map +1 -0
  55. package/dist/session/factory-session.d.ts +4 -0
  56. package/dist/session/factory-session.d.ts.map +1 -1
  57. package/dist/session/factory-session.js +1 -0
  58. package/dist/session/factory-session.js.map +1 -1
  59. package/dist/session/reply-credential-processor.d.ts.map +1 -1
  60. package/dist/session/reply-credential-processor.js +4 -6
  61. package/dist/session/reply-credential-processor.js.map +1 -1
  62. package/dist/session/run-audit.d.ts +24 -0
  63. package/dist/session/run-audit.d.ts.map +1 -1
  64. package/dist/session/run-audit.js +93 -7
  65. package/dist/session/run-audit.js.map +1 -1
  66. package/dist/session/source-control-tools.d.ts.map +1 -1
  67. package/dist/session/source-control-tools.js.map +1 -1
  68. package/dist/storage/domains/source-control/base.d.ts +7 -0
  69. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  70. package/dist/storage/domains/source-control/base.js +12 -0
  71. package/dist/storage/domains/source-control/base.js.map +1 -1
  72. package/dist/storage/domains/source-control/inmemory.d.ts +4 -0
  73. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  74. package/dist/storage/domains/source-control/inmemory.js +8 -0
  75. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  76. package/dist/storage/domains/work-items/base.d.ts +2 -2
  77. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  78. package/dist/storage/domains/work-items/base.js +5 -0
  79. package/dist/storage/domains/work-items/base.js.map +1 -1
  80. package/dist/tools/approval-tools.d.ts +1 -1
  81. package/dist/tools/approval-tools.d.ts.map +1 -1
  82. package/dist/tools/approval-tools.js +3 -1
  83. package/dist/tools/approval-tools.js.map +1 -1
  84. package/dist/tools/deliver.d.ts +16 -2
  85. package/dist/tools/deliver.d.ts.map +1 -1
  86. package/dist/tools/deliver.js +12 -2
  87. package/dist/tools/deliver.js.map +1 -1
  88. package/dist/tools/index.d.ts +6 -5
  89. package/dist/tools/index.d.ts.map +1 -1
  90. package/dist/tools/index.js +9 -12
  91. package/dist/tools/index.js.map +1 -1
  92. package/dist/tools/messaging-tools.d.ts +1 -1
  93. package/dist/tools/messaging-tools.d.ts.map +1 -1
  94. package/dist/tools/messaging-tools.js +84 -3
  95. package/dist/tools/messaging-tools.js.map +1 -1
  96. package/dist/tools/permissions.d.ts.map +1 -1
  97. package/dist/tools/permissions.js +3 -0
  98. package/dist/tools/permissions.js.map +1 -1
  99. package/dist/tools/read-tools.d.ts +3 -0
  100. package/dist/tools/read-tools.d.ts.map +1 -1
  101. package/dist/tools/read-tools.js +16 -8
  102. package/dist/tools/read-tools.js.map +1 -1
  103. package/dist/tools/reports.d.ts.map +1 -1
  104. package/dist/tools/reports.js +3 -1
  105. package/dist/tools/reports.js.map +1 -1
  106. package/dist/tools/scope.d.ts +6 -0
  107. package/dist/tools/scope.d.ts.map +1 -1
  108. package/dist/tools/scope.js +10 -1
  109. package/dist/tools/scope.js.map +1 -1
  110. package/dist/tools/session-tools.d.ts +1 -1
  111. package/dist/tools/session-tools.d.ts.map +1 -1
  112. package/dist/tools/session-tools.js +11 -7
  113. package/dist/tools/session-tools.js.map +1 -1
  114. package/dist/tools/sessions-state-processor.d.ts +5 -1
  115. package/dist/tools/sessions-state-processor.d.ts.map +1 -1
  116. package/dist/tools/sessions-state-processor.js +32 -48
  117. package/dist/tools/sessions-state-processor.js.map +1 -1
  118. package/dist/tools/side-sessions.d.ts +53 -0
  119. package/dist/tools/side-sessions.d.ts.map +1 -0
  120. package/dist/tools/side-sessions.js +83 -0
  121. package/dist/tools/side-sessions.js.map +1 -0
  122. package/dist/tools/work-item-tools.d.ts.map +1 -1
  123. package/dist/tools/work-item-tools.js +45 -25
  124. package/dist/tools/work-item-tools.js.map +1 -1
  125. package/package.json +6 -6
@@ -4,6 +4,7 @@ import type { IntegrationTools } from '../integrations/base.js';
4
4
  import type { AuditStorage } from '../storage/domains/audit/base.js';
5
5
  import type { WorkItemCommentsStorage } from '../storage/domains/comments/base.js';
6
6
  import type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';
7
+ import type { FactoryMessaging } from './messaging-tools.js';
7
8
  import type { FactoryToolScope } from './scope.js';
8
9
  export declare const MAX_TEXT = 600;
9
10
  export declare const MAX_ERROR = 400;
@@ -13,6 +14,8 @@ export interface FactoryReadToolDependencies {
13
14
  comments: Pick<WorkItemCommentsStorage, 'listRecent'>;
14
15
  audit: Pick<AuditStorage, 'list'>;
15
16
  boards: BoardRegistry;
17
+ /** The live controller, for whether a session is mid-run and when it last moved; absent before the mount is up. */
18
+ messaging?: () => Promise<Pick<FactoryMessaging, 'controller'> | null>;
16
19
  }
17
20
  export declare function truncate(text: string, max: number): string;
18
21
  /** The number a person sees on the card: the issue or PR number the item was filed from. */
@@ -1 +1 @@
1
- {"version":3,"file":"read-tools.d.ts","sourceRoot":"","sources":["../../src/tools/read-tools.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAGhE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,qCAAqC,CAAC;AACnF,OAAO,KAAK,EAGV,WAAW,EACX,gBAAgB,EACjB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,eAAO,MAAM,QAAQ,MAAM,CAAC;AAC5B,eAAO,MAAM,SAAS,MAAM,CAAC;AAC7B,eAAO,MAAM,QAAQ,KAAK,CAAC;AAG3B,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,IAAI,CACb,gBAAgB,EACd,MAAM,GACN,eAAe,GACf,iBAAiB,GACjB,uBAAuB,GACvB,WAAW,GACX,6BAA6B,GAC7B,yBAAyB,GACzB,0BAA0B,CAC7B,CAAC;IACF,QAAQ,EAAE,IAAI,CAAC,uBAAuB,EAAE,YAAY,CAAC,CAAC;IACtD,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAClC,MAAM,EAAE,aAAa,CAAC;CACvB;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE1D;AAQD,4FAA4F;AAC5F,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,GAAG,MAAM,GAAG,IAAI,CAGjF;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,GAAG,MAAM,CAEvE;AAED,2EAA2E;AAC3E,eAAO,MAAM,iBAAiB,aAOyB,CAAC;AAExD,+GAA+G;AAC/G,wBAAsB,YAAY,CAChC,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,MAAM,GAAG,eAAe,CAAC,EAC3D,KAAK,EAAE,IAAI,CAAC,gBAAgB,EAAE,OAAO,GAAG,kBAAkB,CAAC,EAC3D,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAQ7B;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAEnD;AAkED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,2BAA2B,GAAG,gBAAgB,CA8MnH"}
1
+ {"version":3,"file":"read-tools.d.ts","sourceRoot":"","sources":["../../src/tools/read-tools.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAGhE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,qCAAqC,CAAC;AACnF,OAAO,KAAK,EAGV,WAAW,EACX,gBAAgB,EACjB,MAAM,uCAAuC,CAAC;AAG/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,eAAO,MAAM,QAAQ,MAAM,CAAC;AAC5B,eAAO,MAAM,SAAS,MAAM,CAAC;AAC7B,eAAO,MAAM,QAAQ,KAAK,CAAC;AAG3B,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,IAAI,CACb,gBAAgB,EACd,MAAM,GACN,eAAe,GACf,iBAAiB,GACjB,uBAAuB,GACvB,WAAW,GACX,6BAA6B,GAC7B,yBAAyB,GACzB,0BAA0B,CAC7B,CAAC;IACF,QAAQ,EAAE,IAAI,CAAC,uBAAuB,EAAE,YAAY,CAAC,CAAC;IACtD,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAClC,MAAM,EAAE,aAAa,CAAC;IACtB,mHAAmH;IACnH,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,gBAAgB,EAAE,YAAY,CAAC,GAAG,IAAI,CAAC,CAAC;CACxE;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE1D;AAQD,4FAA4F;AAC5F,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,GAAG,MAAM,GAAG,IAAI,CAGjF;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,GAAG,MAAM,CAEvE;AAED,2EAA2E;AAC3E,eAAO,MAAM,iBAAiB,aAOyB,CAAC;AAExD,+GAA+G;AAC/G,wBAAsB,YAAY,CAChC,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,MAAM,GAAG,eAAe,CAAC,EAC3D,KAAK,EAAE,IAAI,CAAC,gBAAgB,EAAE,OAAO,GAAG,kBAAkB,CAAC,EAC3D,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAQ7B;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAEnD;AAkED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,2BAA2B,GAAG,gBAAgB,CAsNnH"}
@@ -1,6 +1,7 @@
1
1
  import { boardForWorkItem } from "../boards/semantics.js";
2
2
  import "../boards/index.js";
3
3
  import { factoryDispatchFailureMetadata } from "../rules/dispatch-errors.js";
4
+ import { sessionActivity } from "./deliver.js";
4
5
  import { decisionRetryable, overtakenDecisionIds } from "../rules/decision-applicability.js";
5
6
  import { z } from "zod";
6
7
  import { createTool } from "@mastra/core/tools";
@@ -141,7 +142,7 @@ function createFactoryReadTools(scope, deps) {
141
142
  }),
142
143
  factory_inspect_work_item: createTool({
143
144
  id: "factory_inspect_work_item",
144
- description: "Everything the Factory knows about one work item: stage history, sessions by role, decisions with errors, recent audit events, recent feed comments, labels, linked items and the sessions following it. Use it to explain why an item is where it is.",
145
+ description: "Everything the Factory knows about one work item: stage history, sessions by role (with whether each is mid-run and when its thread last moved), decisions with errors, recent audit events, recent feed comments, labels, linked items and the sessions following it. Use it to explain why an item is where it is.",
145
146
  inputSchema: z.object({ workItem: workItemRefSchema }).strict(),
146
147
  execute: async ({ workItem }) => {
147
148
  const item = await findWorkItem(deps.workItems, scope, workItem);
@@ -169,6 +170,19 @@ function createFactoryReadTools(scope, deps) {
169
170
  const labels = Array.isArray(item.metadata?.labels) ? item.metadata.labels.filter((label) => typeof label === "string").slice(0, 50) : [];
170
171
  const parent = item.parentWorkItemId ? all.find((candidate) => candidate.id === item.parentWorkItemId) : void 0;
171
172
  const children = all.filter((candidate) => candidate.parentWorkItemId === item.id);
173
+ const controller = (await deps.messaging?.())?.controller;
174
+ const activity = async (sessionId, threadId) => controller ? sessionActivity(controller, sessionId, threadId) : null;
175
+ const sessions = await Promise.all(Object.entries(item.sessions).map(async ([role, ref]) => ({
176
+ role,
177
+ sessionId: ref.sessionId,
178
+ threadId: ref.threadId,
179
+ startedBy: ref.startedBy,
180
+ active: bindings.some((binding) => binding.role === role && binding.sessionId === ref.sessionId && binding.status === "active"),
181
+ ...await activity(ref.sessionId, ref.threadId) ?? {
182
+ running: null,
183
+ lastActivityAt: null
184
+ }
185
+ })));
172
186
  return {
173
187
  item: {
174
188
  ...summarizeItem(item, bindings),
@@ -184,13 +198,7 @@ function createFactoryReadTools(scope, deps) {
184
198
  exitedAt: entry.exitedAt ?? null,
185
199
  by: entry.by
186
200
  })),
187
- sessions: Object.entries(item.sessions).map(([role, ref]) => ({
188
- role,
189
- sessionId: ref.sessionId,
190
- threadId: ref.threadId,
191
- startedBy: ref.startedBy,
192
- active: bindings.some((binding) => binding.role === role && binding.sessionId === ref.sessionId && binding.status === "active")
193
- })),
201
+ sessions,
194
202
  decisions: itemDecisions.map((decision) => summarizeDecision(decision, overtaken.has(decision.id))),
195
203
  audit: audit.events.filter((event) => event.targets.some((target) => target.type === "work_item" && target.id === item.id)).slice(0, 25).map((event) => ({
196
204
  action: event.action,
@@ -1 +1 @@
1
- {"version":3,"file":"read-tools.js","names":[],"sources":["../../src/tools/read-tools.ts"],"sourcesContent":["import { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport { boardForWorkItem } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/index.js';\nimport type { IntegrationTools } from '../integrations/base.js';\nimport { decisionRetryable, overtakenDecisionIds } from '../rules/decision-applicability.js';\nimport { factoryDispatchFailureMetadata } from '../rules/dispatch-errors.js';\nimport type { AuditStorage } from '../storage/domains/audit/base.js';\nimport type { WorkItemCommentsStorage } from '../storage/domains/comments/base.js';\nimport type {\n FactoryDeferredDecisionRecord,\n FactoryRunBindingRecord,\n WorkItemRow,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\nimport type { FactoryToolScope } from './scope.js';\n\nexport const MAX_TEXT = 600;\nexport const MAX_ERROR = 400;\nexport const MAX_LIST = 50;\nconst DEFAULT_LIST = 25;\n\nexport interface FactoryReadToolDependencies {\n workItems: Pick<\n WorkItemsStorage,\n | 'list'\n | 'getForProject'\n | 'listRunBindings'\n | 'listDeferredDecisions'\n | 'listByIds'\n | 'listDecisionsForEvaluations'\n | 'listWorkItemSubscribers'\n | 'listSessionSubscriptions'\n >;\n comments: Pick<WorkItemCommentsStorage, 'listRecent'>;\n audit: Pick<AuditStorage, 'list'>;\n boards: BoardRegistry;\n}\n\nexport function truncate(text: string, max: number): string {\n return text.length > max ? `${text.slice(0, max - 1)}…` : text;\n}\n\nfunction iso(value: Date | string | null | undefined): string | null {\n if (!value) return null;\n const date = value instanceof Date ? value : new Date(value);\n return Number.isFinite(date.getTime()) ? date.toISOString() : null;\n}\n\n/** The number a person sees on the card: the issue or PR number the item was filed from. */\nexport function workItemNumber(item: Pick<WorkItemRow, 'metadata'>): number | null {\n const number = item.metadata?.number ?? item.metadata?.githubIssueNumber ?? item.metadata?.githubPullRequestNumber;\n return typeof number === 'number' ? number : null;\n}\n\nexport function workItemStage(item: Pick<WorkItemRow, 'stages'>): string {\n return item.stages.join('+');\n}\n\n/** A work item id or its card number; anything else can't name an item. */\nexport const workItemRefSchema = z\n .string()\n .trim()\n .max(64)\n .refine(ref => z.string().uuid().safeParse(ref).success || /^#?\\d+$/.test(ref), {\n message: 'Use a work item id (UUID) or its number like \"#123\".',\n })\n .describe('Work item id, or its number like \"#123\".');\n\n/** Resolve `<uuid>` or `#123` to an item in this Factory only. Pass a ref validated by `workItemRefSchema`. */\nexport async function findWorkItem(\n workItems: Pick<WorkItemsStorage, 'list' | 'getForProject'>,\n scope: Pick<FactoryToolScope, 'orgId' | 'factoryProjectId'>,\n ref: string,\n): Promise<WorkItemRow | null> {\n const trimmed = ref.trim();\n const number = /^#?(\\d+)$/.exec(trimmed)?.[1];\n if (number === undefined) return workItems.getForProject(scope.orgId, scope.factoryProjectId, trimmed);\n const items = await workItems.list(scope);\n const matches = items.filter(item => workItemNumber(item) === Number(number));\n // Issue and PR numbers share a space on GitHub; prefer the Work-board card.\n return matches.find(item => item.externalSource?.type !== 'pull-request') ?? matches[0] ?? null;\n}\n\nexport function workItemNotFound(ref: string): Error {\n return new Error(`No work item ${ref} was found in this Factory.`);\n}\n\nfunction activeWorker(bindings: FactoryRunBindingRecord[], itemId: string) {\n const binding = bindings.find(candidate => candidate.workItemId === itemId && candidate.status === 'active');\n return binding ? { role: binding.role, sessionId: binding.sessionId } : null;\n}\n\nfunction summarizeItem(item: WorkItemRow, bindings: FactoryRunBindingRecord[]) {\n return {\n id: item.id,\n number: workItemNumber(item),\n title: truncate(item.title, MAX_TEXT),\n board: boardForWorkItem(item),\n stage: workItemStage(item),\n activeWorker: activeWorker(bindings, item.id),\n requestedBy: item.createdBy,\n updatedAt: iso(item.updatedAt),\n };\n}\n\nfunction summarizeDecision(decision: FactoryDeferredDecisionRecord, overtaken = false) {\n const type = typeof decision.decision.type === 'string' ? decision.decision.type : 'decision';\n const role = typeof decision.decision.role === 'string' ? decision.decision.role : null;\n return {\n id: decision.id,\n workItemId: decision.workItemId,\n type,\n role,\n status: decision.status,\n attempts: decision.attempts,\n failureCode: decision.failureCode,\n failureLabel: decision.failureCode ? factoryDispatchFailureMetadata(decision.failureCode).label : null,\n canRetry: decisionRetryable(decision, overtaken),\n error: decision.lastError ? truncate(decision.lastError, MAX_ERROR) : null,\n approvedBy: decision.approvedBy,\n updatedAt: iso(decision.updatedAt),\n };\n}\n\n/** Held: a non-bug item triage parked until a person accepts it. */\nfunction isHeld(item: WorkItemRow): boolean {\n return item.stages.includes('triage') && !!item.triageType && item.triageType !== 'bug' && !item.acceptedAt;\n}\n\nconst listInputSchema = z\n .object({\n query: z\n .string()\n .trim()\n .min(1)\n .max(200)\n .optional()\n .describe('Case-insensitive text in the title, or an exact card number like \"#123\".'),\n stage: z.string().min(1).max(64).optional().describe('Only items in this stage, e.g. \"triage\" or \"review\".'),\n board: z.string().min(1).max(64).optional().describe('Only items on this board, e.g. \"work\" or \"review\".'),\n role: z.string().min(1).max(64).optional().describe('Only items that have a session for this role.'),\n active: z.boolean().optional().describe('true: only items with an agent working now; false: only idle items.'),\n requestedBy: z.literal('me').optional().describe('\"me\": only items you filed.'),\n subscribed: z\n .boolean()\n .optional()\n .describe('true: only items this session follows; false: only items it does not.'),\n limit: z.number().int().min(1).max(MAX_LIST).optional().describe(`Default ${DEFAULT_LIST}, max ${MAX_LIST}.`),\n })\n .strict();\n\nexport function createFactoryReadTools(scope: FactoryToolScope, deps: FactoryReadToolDependencies): IntegrationTools {\n const project = { orgId: scope.orgId, factoryProjectId: scope.factoryProjectId };\n\n return {\n factory_list_work_items: createTool({\n id: 'factory_list_work_items',\n description:\n \"List this Factory's work items, newest activity first, with counts per stage for the whole Factory. Filter by text or #number, stage, board, role, whether an agent is working on it, items you filed, or items this session follows. Check here for an existing item before filing a new one.\",\n inputSchema: listInputSchema,\n execute: async input => {\n const [items, bindings, followed] = await Promise.all([\n deps.workItems.list(project),\n deps.workItems.listRunBindings(scope.orgId, scope.factoryProjectId),\n input.subscribed === undefined\n ? undefined\n : deps.workItems.listSessionSubscriptions({ ...project, sessionId: scope.sessionId }),\n ]);\n const followedIds = new Set(followed?.map(subscription => subscription.workItemId));\n const byStage: Record<string, number> = {};\n for (const item of items) {\n const stage = workItemStage(item);\n byStage[stage] = (byStage[stage] ?? 0) + 1;\n }\n const numberQuery = input.query ? /^#(\\d+)$/.exec(input.query)?.[1] : undefined;\n const text = input.query?.toLowerCase();\n const matches = items\n .filter(item => {\n if (numberQuery !== undefined) {\n if (workItemNumber(item) !== Number(numberQuery)) return false;\n } else if (text && !item.title.toLowerCase().includes(text)) return false;\n if (input.stage && !item.stages.includes(input.stage)) return false;\n if (input.board && boardForWorkItem(item) !== input.board) return false;\n if (input.role && !item.sessions[input.role]) return false;\n if (input.active !== undefined && (activeWorker(bindings, item.id) !== null) !== input.active) return false;\n if (input.requestedBy === 'me' && item.createdBy !== scope.userId) return false;\n if (input.subscribed !== undefined && followedIds.has(item.id) !== input.subscribed) return false;\n return true;\n })\n .sort((left, right) => right.updatedAt.getTime() - left.updatedAt.getTime());\n const limit = input.limit ?? DEFAULT_LIST;\n return {\n counts: { total: items.length, byStage },\n items: matches.slice(0, limit).map(item => summarizeItem(item, bindings)),\n truncated: matches.length > limit,\n };\n },\n }),\n\n factory_inspect_work_item: createTool({\n id: 'factory_inspect_work_item',\n description:\n 'Everything the Factory knows about one work item: stage history, sessions by role, decisions with errors, recent audit events, recent feed comments, labels, linked items and the sessions following it. Use it to explain why an item is where it is.',\n inputSchema: z.object({ workItem: workItemRefSchema }).strict(),\n execute: async ({ workItem }) => {\n const item = await findWorkItem(deps.workItems, scope, workItem);\n if (!item) throw workItemNotFound(workItem);\n const [bindings, decisions, audit, feed, all, subscribers] = await Promise.all([\n deps.workItems.listRunBindings(scope.orgId, scope.factoryProjectId, item.id),\n deps.workItems.listDeferredDecisions(scope.orgId, scope.factoryProjectId, { workItemId: item.id }),\n deps.audit.list({ ...project, limit: 200 }),\n deps.comments.listRecent({ ...project, workItemId: item.id, limit: 10 }),\n deps.workItems.list(project),\n deps.workItems.listWorkItemSubscribers({ orgId: scope.orgId, workItemId: item.id }),\n ]);\n const itemDecisions = decisions.slice(-MAX_LIST);\n const overtaken = await overtakenDecisionIds(\n deps.workItems,\n deps.boards,\n project,\n itemDecisions.filter(decision => decision.status === 'failed'),\n );\n const labels = Array.isArray(item.metadata?.labels)\n ? item.metadata.labels.filter((label): label is string => typeof label === 'string').slice(0, MAX_LIST)\n : [];\n const parent = item.parentWorkItemId\n ? all.find(candidate => candidate.id === item.parentWorkItemId)\n : undefined;\n const children = all.filter(candidate => candidate.parentWorkItemId === item.id);\n return {\n item: {\n ...summarizeItem(item, bindings),\n source: item.externalSource ? `${item.externalSource.integrationId}:${item.externalSource.type}` : 'manual',\n url: typeof item.metadata?.url === 'string' ? item.metadata.url : null,\n triageType: item.triageType,\n revision: item.revision,\n },\n labels,\n stageHistory: item.stageHistory.slice(-MAX_LIST).map(entry => ({\n stage: entry.stage,\n enteredAt: entry.enteredAt,\n exitedAt: entry.exitedAt ?? null,\n by: entry.by,\n })),\n sessions: Object.entries(item.sessions).map(([role, ref]) => ({\n role,\n sessionId: ref.sessionId,\n threadId: ref.threadId,\n startedBy: ref.startedBy,\n active: bindings.some(\n binding => binding.role === role && binding.sessionId === ref.sessionId && binding.status === 'active',\n ),\n })),\n decisions: itemDecisions.map(decision => summarizeDecision(decision, overtaken.has(decision.id))),\n audit: audit.events\n .filter(event => event.targets.some(target => target.type === 'work_item' && target.id === item.id))\n .slice(0, 25)\n .map(event => ({\n action: event.action,\n actorId: event.actorId,\n actorType: event.actorType,\n occurredAt: iso(event.occurredAt),\n })),\n feed: feed.map(comment => ({\n id: comment.id,\n kind: comment.kind,\n author: comment.author,\n occurredAt: iso(comment.occurredAt),\n body: truncate(comment.body, MAX_TEXT),\n })),\n parent: parent ? summarizeItem(parent, bindings) : null,\n children: children.slice(0, MAX_LIST).map(child => summarizeItem(child, bindings)),\n subscribers: subscribers.slice(0, MAX_LIST).map(subscription => ({\n sessionId: subscription.sessionId,\n reason: subscription.reason,\n isCaller: subscription.sessionId === scope.sessionId,\n })),\n };\n },\n }),\n\n factory_list_attention: createTool({\n id: 'factory_list_attention',\n description:\n 'What in this Factory needs a person: failed decisions grouped by failure code and error (one incident per group), proposals waiting for approval, and items triage is holding until someone accepts them.',\n inputSchema: z.object({ limit: z.number().int().min(1).max(MAX_LIST).optional() }).strict(),\n execute: async ({ limit = DEFAULT_LIST }) => {\n const [decisions, items] = await Promise.all([\n deps.workItems.listDeferredDecisions(scope.orgId, scope.factoryProjectId, {\n statuses: ['failed', 'proposed'],\n }),\n deps.workItems.list(project),\n ]);\n const itemsById = new Map(items.map(item => [item.id, item]));\n const failed = decisions.filter(decision => decision.status === 'failed');\n const groups = new Map<\n string,\n { failureCode: string; error: string; decisions: FactoryDeferredDecisionRecord[] }\n >();\n for (const decision of failed) {\n const error = truncate(decision.lastError ?? '', MAX_ERROR);\n const key = `${decision.failureCode ?? 'unknown'}|${error}`;\n const group = groups.get(key) ?? { failureCode: decision.failureCode ?? 'unknown', error, decisions: [] };\n group.decisions.push(decision);\n groups.set(key, group);\n }\n const ref = (workItemId: string | null) => {\n const item = workItemId ? itemsById.get(workItemId) : undefined;\n return {\n workItemId,\n number: item ? workItemNumber(item) : null,\n title: item ? truncate(item.title, MAX_TEXT) : null,\n };\n };\n const allIncidents = [...groups.values()].sort((a, b) => b.decisions.length - a.decisions.length);\n const incidents = allIncidents\n .slice(0, limit)\n .map(group => ({ ...group, shown: group.decisions.slice(0, limit) }));\n // Check exactly the decisions this call returns, so every canRetry is real.\n const overtaken = await overtakenDecisionIds(\n deps.workItems,\n deps.boards,\n project,\n incidents.flatMap(group => group.shown),\n );\n const proposals = decisions.filter(decision => decision.status === 'proposed');\n const held = items.filter(isHeld);\n return {\n incidents: incidents.map(group => ({\n failureCode: group.failureCode,\n failureLabel: factoryDispatchFailureMetadata(group.decisions[0]!.failureCode).label,\n error: group.error,\n count: group.decisions.length,\n decisions: group.shown.map(decision => ({\n id: decision.id,\n ...ref(decision.workItemId),\n attempts: decision.attempts,\n canRetry: decisionRetryable(decision, overtaken.has(decision.id)),\n })),\n })),\n proposals: proposals\n .slice(0, limit)\n .map(decision => ({ ...summarizeDecision(decision), ...ref(decision.workItemId) })),\n held: held.slice(0, limit).map(item => ({\n ...ref(item.id),\n triageType: item.triageType,\n stage: workItemStage(item),\n })),\n truncated:\n allIncidents.length > limit ||\n incidents.some(group => group.shown.length < group.decisions.length) ||\n proposals.length > limit ||\n held.length > limit,\n };\n },\n }),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"read-tools.js","names":[],"sources":["../../src/tools/read-tools.ts"],"sourcesContent":["import { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport { boardForWorkItem } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/index.js';\nimport type { IntegrationTools } from '../integrations/base.js';\nimport { decisionRetryable, overtakenDecisionIds } from '../rules/decision-applicability.js';\nimport { factoryDispatchFailureMetadata } from '../rules/dispatch-errors.js';\nimport type { AuditStorage } from '../storage/domains/audit/base.js';\nimport type { WorkItemCommentsStorage } from '../storage/domains/comments/base.js';\nimport type {\n FactoryDeferredDecisionRecord,\n FactoryRunBindingRecord,\n WorkItemRow,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\nimport { sessionActivity } from './deliver.js';\nimport type { FactorySessionActivity } from './deliver.js';\nimport type { FactoryMessaging } from './messaging-tools.js';\nimport type { FactoryToolScope } from './scope.js';\n\nexport const MAX_TEXT = 600;\nexport const MAX_ERROR = 400;\nexport const MAX_LIST = 50;\nconst DEFAULT_LIST = 25;\n\nexport interface FactoryReadToolDependencies {\n workItems: Pick<\n WorkItemsStorage,\n | 'list'\n | 'getForProject'\n | 'listRunBindings'\n | 'listDeferredDecisions'\n | 'listByIds'\n | 'listDecisionsForEvaluations'\n | 'listWorkItemSubscribers'\n | 'listSessionSubscriptions'\n >;\n comments: Pick<WorkItemCommentsStorage, 'listRecent'>;\n audit: Pick<AuditStorage, 'list'>;\n boards: BoardRegistry;\n /** The live controller, for whether a session is mid-run and when it last moved; absent before the mount is up. */\n messaging?: () => Promise<Pick<FactoryMessaging, 'controller'> | null>;\n}\n\nexport function truncate(text: string, max: number): string {\n return text.length > max ? `${text.slice(0, max - 1)}…` : text;\n}\n\nfunction iso(value: Date | string | null | undefined): string | null {\n if (!value) return null;\n const date = value instanceof Date ? value : new Date(value);\n return Number.isFinite(date.getTime()) ? date.toISOString() : null;\n}\n\n/** The number a person sees on the card: the issue or PR number the item was filed from. */\nexport function workItemNumber(item: Pick<WorkItemRow, 'metadata'>): number | null {\n const number = item.metadata?.number ?? item.metadata?.githubIssueNumber ?? item.metadata?.githubPullRequestNumber;\n return typeof number === 'number' ? number : null;\n}\n\nexport function workItemStage(item: Pick<WorkItemRow, 'stages'>): string {\n return item.stages.join('+');\n}\n\n/** A work item id or its card number; anything else can't name an item. */\nexport const workItemRefSchema = z\n .string()\n .trim()\n .max(64)\n .refine(ref => z.string().uuid().safeParse(ref).success || /^#?\\d+$/.test(ref), {\n message: 'Use a work item id (UUID) or its number like \"#123\".',\n })\n .describe('Work item id, or its number like \"#123\".');\n\n/** Resolve `<uuid>` or `#123` to an item in this Factory only. Pass a ref validated by `workItemRefSchema`. */\nexport async function findWorkItem(\n workItems: Pick<WorkItemsStorage, 'list' | 'getForProject'>,\n scope: Pick<FactoryToolScope, 'orgId' | 'factoryProjectId'>,\n ref: string,\n): Promise<WorkItemRow | null> {\n const trimmed = ref.trim();\n const number = /^#?(\\d+)$/.exec(trimmed)?.[1];\n if (number === undefined) return workItems.getForProject(scope.orgId, scope.factoryProjectId, trimmed);\n const items = await workItems.list(scope);\n const matches = items.filter(item => workItemNumber(item) === Number(number));\n // Issue and PR numbers share a space on GitHub; prefer the Work-board card.\n return matches.find(item => item.externalSource?.type !== 'pull-request') ?? matches[0] ?? null;\n}\n\nexport function workItemNotFound(ref: string): Error {\n return new Error(`No work item ${ref} was found in this Factory.`);\n}\n\nfunction activeWorker(bindings: FactoryRunBindingRecord[], itemId: string) {\n const binding = bindings.find(candidate => candidate.workItemId === itemId && candidate.status === 'active');\n return binding ? { role: binding.role, sessionId: binding.sessionId } : null;\n}\n\nfunction summarizeItem(item: WorkItemRow, bindings: FactoryRunBindingRecord[]) {\n return {\n id: item.id,\n number: workItemNumber(item),\n title: truncate(item.title, MAX_TEXT),\n board: boardForWorkItem(item),\n stage: workItemStage(item),\n activeWorker: activeWorker(bindings, item.id),\n requestedBy: item.createdBy,\n updatedAt: iso(item.updatedAt),\n };\n}\n\nfunction summarizeDecision(decision: FactoryDeferredDecisionRecord, overtaken = false) {\n const type = typeof decision.decision.type === 'string' ? decision.decision.type : 'decision';\n const role = typeof decision.decision.role === 'string' ? decision.decision.role : null;\n return {\n id: decision.id,\n workItemId: decision.workItemId,\n type,\n role,\n status: decision.status,\n attempts: decision.attempts,\n failureCode: decision.failureCode,\n failureLabel: decision.failureCode ? factoryDispatchFailureMetadata(decision.failureCode).label : null,\n canRetry: decisionRetryable(decision, overtaken),\n error: decision.lastError ? truncate(decision.lastError, MAX_ERROR) : null,\n approvedBy: decision.approvedBy,\n updatedAt: iso(decision.updatedAt),\n };\n}\n\n/** Held: a non-bug item triage parked until a person accepts it. */\nfunction isHeld(item: WorkItemRow): boolean {\n return item.stages.includes('triage') && !!item.triageType && item.triageType !== 'bug' && !item.acceptedAt;\n}\n\nconst listInputSchema = z\n .object({\n query: z\n .string()\n .trim()\n .min(1)\n .max(200)\n .optional()\n .describe('Case-insensitive text in the title, or an exact card number like \"#123\".'),\n stage: z.string().min(1).max(64).optional().describe('Only items in this stage, e.g. \"triage\" or \"review\".'),\n board: z.string().min(1).max(64).optional().describe('Only items on this board, e.g. \"work\" or \"review\".'),\n role: z.string().min(1).max(64).optional().describe('Only items that have a session for this role.'),\n active: z.boolean().optional().describe('true: only items with an agent working now; false: only idle items.'),\n requestedBy: z.literal('me').optional().describe('\"me\": only items you filed.'),\n subscribed: z\n .boolean()\n .optional()\n .describe('true: only items this session follows; false: only items it does not.'),\n limit: z.number().int().min(1).max(MAX_LIST).optional().describe(`Default ${DEFAULT_LIST}, max ${MAX_LIST}.`),\n })\n .strict();\n\nexport function createFactoryReadTools(scope: FactoryToolScope, deps: FactoryReadToolDependencies): IntegrationTools {\n const project = { orgId: scope.orgId, factoryProjectId: scope.factoryProjectId };\n\n return {\n factory_list_work_items: createTool({\n id: 'factory_list_work_items',\n description:\n \"List this Factory's work items, newest activity first, with counts per stage for the whole Factory. Filter by text or #number, stage, board, role, whether an agent is working on it, items you filed, or items this session follows. Check here for an existing item before filing a new one.\",\n inputSchema: listInputSchema,\n execute: async input => {\n const [items, bindings, followed] = await Promise.all([\n deps.workItems.list(project),\n deps.workItems.listRunBindings(scope.orgId, scope.factoryProjectId),\n input.subscribed === undefined\n ? undefined\n : deps.workItems.listSessionSubscriptions({ ...project, sessionId: scope.sessionId }),\n ]);\n const followedIds = new Set(followed?.map(subscription => subscription.workItemId));\n const byStage: Record<string, number> = {};\n for (const item of items) {\n const stage = workItemStage(item);\n byStage[stage] = (byStage[stage] ?? 0) + 1;\n }\n const numberQuery = input.query ? /^#(\\d+)$/.exec(input.query)?.[1] : undefined;\n const text = input.query?.toLowerCase();\n const matches = items\n .filter(item => {\n if (numberQuery !== undefined) {\n if (workItemNumber(item) !== Number(numberQuery)) return false;\n } else if (text && !item.title.toLowerCase().includes(text)) return false;\n if (input.stage && !item.stages.includes(input.stage)) return false;\n if (input.board && boardForWorkItem(item) !== input.board) return false;\n if (input.role && !item.sessions[input.role]) return false;\n if (input.active !== undefined && (activeWorker(bindings, item.id) !== null) !== input.active) return false;\n if (input.requestedBy === 'me' && item.createdBy !== scope.userId) return false;\n if (input.subscribed !== undefined && followedIds.has(item.id) !== input.subscribed) return false;\n return true;\n })\n .sort((left, right) => right.updatedAt.getTime() - left.updatedAt.getTime());\n const limit = input.limit ?? DEFAULT_LIST;\n return {\n counts: { total: items.length, byStage },\n items: matches.slice(0, limit).map(item => summarizeItem(item, bindings)),\n truncated: matches.length > limit,\n };\n },\n }),\n\n factory_inspect_work_item: createTool({\n id: 'factory_inspect_work_item',\n description:\n 'Everything the Factory knows about one work item: stage history, sessions by role (with whether each is mid-run and when its thread last moved), decisions with errors, recent audit events, recent feed comments, labels, linked items and the sessions following it. Use it to explain why an item is where it is.',\n inputSchema: z.object({ workItem: workItemRefSchema }).strict(),\n execute: async ({ workItem }) => {\n const item = await findWorkItem(deps.workItems, scope, workItem);\n if (!item) throw workItemNotFound(workItem);\n const [bindings, decisions, audit, feed, all, subscribers] = await Promise.all([\n deps.workItems.listRunBindings(scope.orgId, scope.factoryProjectId, item.id),\n deps.workItems.listDeferredDecisions(scope.orgId, scope.factoryProjectId, { workItemId: item.id }),\n deps.audit.list({ ...project, limit: 200 }),\n deps.comments.listRecent({ ...project, workItemId: item.id, limit: 10 }),\n deps.workItems.list(project),\n deps.workItems.listWorkItemSubscribers({ orgId: scope.orgId, workItemId: item.id }),\n ]);\n const itemDecisions = decisions.slice(-MAX_LIST);\n const overtaken = await overtakenDecisionIds(\n deps.workItems,\n deps.boards,\n project,\n itemDecisions.filter(decision => decision.status === 'failed'),\n );\n const labels = Array.isArray(item.metadata?.labels)\n ? item.metadata.labels.filter((label): label is string => typeof label === 'string').slice(0, MAX_LIST)\n : [];\n const parent = item.parentWorkItemId\n ? all.find(candidate => candidate.id === item.parentWorkItemId)\n : undefined;\n const children = all.filter(candidate => candidate.parentWorkItemId === item.id);\n const controller = (await deps.messaging?.())?.controller;\n const activity = async (sessionId: string, threadId: string): Promise<FactorySessionActivity | null> =>\n controller ? sessionActivity(controller, sessionId, threadId) : null;\n const sessions = await Promise.all(\n Object.entries(item.sessions).map(async ([role, ref]) => ({\n role,\n sessionId: ref.sessionId,\n threadId: ref.threadId,\n startedBy: ref.startedBy,\n active: bindings.some(\n binding => binding.role === role && binding.sessionId === ref.sessionId && binding.status === 'active',\n ),\n // `running` with an old `lastActivityAt` is a stuck run; `running` with a recent one is a long one.\n ...((await activity(ref.sessionId, ref.threadId)) ?? { running: null, lastActivityAt: null }),\n })),\n );\n return {\n item: {\n ...summarizeItem(item, bindings),\n source: item.externalSource ? `${item.externalSource.integrationId}:${item.externalSource.type}` : 'manual',\n url: typeof item.metadata?.url === 'string' ? item.metadata.url : null,\n triageType: item.triageType,\n revision: item.revision,\n },\n labels,\n stageHistory: item.stageHistory.slice(-MAX_LIST).map(entry => ({\n stage: entry.stage,\n enteredAt: entry.enteredAt,\n exitedAt: entry.exitedAt ?? null,\n by: entry.by,\n })),\n sessions,\n decisions: itemDecisions.map(decision => summarizeDecision(decision, overtaken.has(decision.id))),\n audit: audit.events\n .filter(event => event.targets.some(target => target.type === 'work_item' && target.id === item.id))\n .slice(0, 25)\n .map(event => ({\n action: event.action,\n actorId: event.actorId,\n actorType: event.actorType,\n occurredAt: iso(event.occurredAt),\n })),\n feed: feed.map(comment => ({\n id: comment.id,\n kind: comment.kind,\n author: comment.author,\n occurredAt: iso(comment.occurredAt),\n body: truncate(comment.body, MAX_TEXT),\n })),\n parent: parent ? summarizeItem(parent, bindings) : null,\n children: children.slice(0, MAX_LIST).map(child => summarizeItem(child, bindings)),\n subscribers: subscribers.slice(0, MAX_LIST).map(subscription => ({\n sessionId: subscription.sessionId,\n reason: subscription.reason,\n isCaller: subscription.sessionId === scope.sessionId,\n })),\n };\n },\n }),\n\n factory_list_attention: createTool({\n id: 'factory_list_attention',\n description:\n 'What in this Factory needs a person: failed decisions grouped by failure code and error (one incident per group), proposals waiting for approval, and items triage is holding until someone accepts them.',\n inputSchema: z.object({ limit: z.number().int().min(1).max(MAX_LIST).optional() }).strict(),\n execute: async ({ limit = DEFAULT_LIST }) => {\n const [decisions, items] = await Promise.all([\n deps.workItems.listDeferredDecisions(scope.orgId, scope.factoryProjectId, {\n statuses: ['failed', 'proposed'],\n }),\n deps.workItems.list(project),\n ]);\n const itemsById = new Map(items.map(item => [item.id, item]));\n const failed = decisions.filter(decision => decision.status === 'failed');\n const groups = new Map<\n string,\n { failureCode: string; error: string; decisions: FactoryDeferredDecisionRecord[] }\n >();\n for (const decision of failed) {\n const error = truncate(decision.lastError ?? '', MAX_ERROR);\n const key = `${decision.failureCode ?? 'unknown'}|${error}`;\n const group = groups.get(key) ?? { failureCode: decision.failureCode ?? 'unknown', error, decisions: [] };\n group.decisions.push(decision);\n groups.set(key, group);\n }\n const ref = (workItemId: string | null) => {\n const item = workItemId ? itemsById.get(workItemId) : undefined;\n return {\n workItemId,\n number: item ? workItemNumber(item) : null,\n title: item ? truncate(item.title, MAX_TEXT) : null,\n };\n };\n const allIncidents = [...groups.values()].sort((a, b) => b.decisions.length - a.decisions.length);\n const incidents = allIncidents\n .slice(0, limit)\n .map(group => ({ ...group, shown: group.decisions.slice(0, limit) }));\n // Check exactly the decisions this call returns, so every canRetry is real.\n const overtaken = await overtakenDecisionIds(\n deps.workItems,\n deps.boards,\n project,\n incidents.flatMap(group => group.shown),\n );\n const proposals = decisions.filter(decision => decision.status === 'proposed');\n const held = items.filter(isHeld);\n return {\n incidents: incidents.map(group => ({\n failureCode: group.failureCode,\n failureLabel: factoryDispatchFailureMetadata(group.decisions[0]!.failureCode).label,\n error: group.error,\n count: group.decisions.length,\n decisions: group.shown.map(decision => ({\n id: decision.id,\n ...ref(decision.workItemId),\n attempts: decision.attempts,\n canRetry: decisionRetryable(decision, overtaken.has(decision.id)),\n })),\n })),\n proposals: proposals\n .slice(0, limit)\n .map(decision => ({ ...summarizeDecision(decision), ...ref(decision.workItemId) })),\n held: held.slice(0, limit).map(item => ({\n ...ref(item.id),\n triageType: item.triageType,\n stage: workItemStage(item),\n })),\n truncated:\n allIncidents.length > limit ||\n incidents.some(group => group.shown.length < group.decisions.length) ||\n proposals.length > limit ||\n held.length > limit,\n };\n },\n }),\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"reports.d.ts","sourceRoot":"","sources":["../../src/tools/reports.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"reports.d.ts","sourceRoot":"","sources":["../../src/tools/reports.ts"],"names":[],"mappings":"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"}
@@ -281,7 +281,8 @@ async function sessionAudience({ sessions, workItems }, sessionId, threadId, sin
281
281
  sessionId: row.originSessionId,
282
282
  threadId: row.originThreadId
283
283
  },
284
- subject: `The side session "${row.title ?? row.sessionId}"`
284
+ subject: `The side session "${row.title ?? row.sessionId}"`,
285
+ mutedAt: row.originMutedAt ?? null
285
286
  };
286
287
  }
287
288
  /** Reports each run end of a work or side session to whoever follows it. */
@@ -303,6 +304,7 @@ function createSessionRunEndReporter(deps) {
303
304
  });
304
305
  return;
305
306
  }
307
+ if (audience.mutedAt && outcome.status !== "blocked") return;
306
308
  const resolved = await deps.reporting();
307
309
  if (!resolved) return;
308
310
  await sendRunEndReport(resolved, {
@@ -1 +1 @@
1
- {"version":3,"file":"reports.js","names":[],"sources":["../../src/tools/reports.ts"],"sourcesContent":["/**\n * Reports from sessions doing the work back to whoever follows it: a side\n * session's origin, or every subscriber of a work item. The agent reports with\n * `factory_message_origin`; when a run ends without one, the run-end backstop\n * sends it, so each run produces exactly one final report per recipient.\n */\n\nimport type {\n NotificationPriority,\n NotificationsStorage,\n SendNotificationSignalInput,\n} from '@mastra/core/notifications';\n\nimport type { SourceControlSessionLookup } from '../session/factory-session.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { FACTORY_SESSION_NOTIFICATION_SOURCE } from './deliver.js';\nimport type { FactoryDeliveryResult, FactoryDeliveryTarget } from './deliver.js';\nimport { workItemNumber } from './read-tools.js';\n\nexport type FactoryReportStatus = 'progress' | 'blocked' | 'done' | 'failed';\n\n/** How each report status travels. Final reports never coalesce, so a later report can't overwrite them. */\nexport const FACTORY_REPORT_FORMS: Record<\n FactoryReportStatus | 'report',\n { kind: string; priority: NotificationPriority; coalesce: boolean }\n> = {\n failed: { kind: 'failed', priority: 'urgent', coalesce: false },\n blocked: { kind: 'blocked', priority: 'urgent', coalesce: false },\n done: { kind: 'done', priority: 'high', coalesce: false },\n progress: { kind: 'progress', priority: 'low', coalesce: true },\n // Not `message`: only messages can be answered, so a report can't start a reply loop.\n report: { kind: 'report', priority: 'high', coalesce: false },\n};\n\n/** How many recent `factory-session` notifications on a recipient thread the final-report check reads. */\nconst FINAL_REPORT_SCAN = 100;\n\nexport interface FactoryReportSender {\n sessionId: string;\n threadId: string;\n userId: string;\n}\n\nexport type FactoryReportDeliver = (\n target: FactoryDeliveryTarget,\n notification: Omit<SendNotificationSignalInput, 'source'>,\n options?: { onlyIfLive?: boolean },\n) => Promise<FactoryDeliveryResult>;\n\nexport interface FactoryReporting {\n deliver: FactoryReportDeliver;\n notifications: Pick<NotificationsStorage, 'listNotifications'>;\n}\n\n/**\n * Whether `senderSessionId` already put a report of one of `kinds` on the\n * recipient thread since `since`. Reads the durable notifications store, so it\n * holds across restarts. `listNotifications` has no time filter: records come\n * newest-updated first, and the scan stops at the first one last updated\n * before `since` (nothing after it can have been created later).\n */\nexport async function hasFinalReport(\n notifications: FactoryReporting['notifications'],\n {\n senderSessionId,\n recipientThreadId,\n since,\n kinds,\n agentOnly = false,\n }: {\n senderSessionId: string;\n recipientThreadId: string;\n since: Date;\n kinds: readonly string[];\n /** Ignore reports Factory sent on the session's behalf (the backstop). */\n agentOnly?: boolean;\n },\n): Promise<boolean> {\n const records = await notifications.listNotifications({\n threadId: recipientThreadId,\n source: FACTORY_SESSION_NOTIFICATION_SOURCE,\n limit: FINAL_REPORT_SCAN,\n });\n for (const record of records) {\n if (record.updatedAt < since) break;\n if (\n record.attributes?.['from-session'] === senderSessionId &&\n !(agentOnly && record.attributes?.backstop === 'true') &&\n // A reply carrying a final status counts like the report itself.\n (kinds.includes(record.kind) ||\n (record.kind === 'reply' &&\n typeof record.attributes?.status === 'string' &&\n kinds.includes(record.attributes.status))) &&\n record.createdAt >= since\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Whether the newest report `senderSessionId` put on the recipient thread is\n * already this exact `failed` backstop. A session stuck failing the same way\n * (a run that dies before the model, restarted over and over) then reports\n * once, not once per attempt; any other report in between resets it.\n */\nexport async function repeatsLastFailure(\n notifications: FactoryReporting['notifications'],\n {\n senderSessionId,\n recipientThreadId,\n summary,\n }: { senderSessionId: string; recipientThreadId: string; summary: string },\n): Promise<boolean> {\n const records = await notifications.listNotifications({\n threadId: recipientThreadId,\n source: FACTORY_SESSION_NOTIFICATION_SOURCE,\n limit: FINAL_REPORT_SCAN,\n });\n // Storage orders by `updatedAt`, which moves when a report is read; the\n // newest report is the one created last.\n const last = records\n .filter(record => record.attributes?.['from-session'] === senderSessionId)\n .reduce<(typeof records)[number] | undefined>(\n (newest, record) => (!newest || record.createdAt > newest.createdAt ? record : newest),\n undefined,\n );\n return last?.kind === 'failed' && last.attributes?.backstop === 'true' && last.summary === summary;\n}\n\nexport function reportNotification({\n sender,\n status,\n message,\n attributes = {},\n}: {\n sender: FactoryReportSender;\n status?: FactoryReportStatus;\n message: string;\n attributes?: Record<string, string>;\n}): Omit<SendNotificationSignalInput, 'source'> {\n const form = FACTORY_REPORT_FORMS[status ?? 'report'];\n return {\n kind: form.kind,\n priority: form.priority,\n // The whole text: a notification's summary is all the recipient's agent ever sees, even\n // through notification_inbox (which hides payloads), so a cut-off report loses its verdict.\n summary: message,\n payload: { message },\n sourceId: sender.sessionId,\n ...(form.coalesce ? { coalesceKey: `factory-session:${sender.sessionId}:${form.kind}` } : {}),\n attributes: {\n ...attributes,\n ...(status ? { status } : {}),\n // Last, so nothing above can claim another sender.\n 'from-session': sender.sessionId,\n 'from-thread': sender.threadId,\n 'from-user': sender.userId,\n },\n };\n}\n\n/** Everyone following a work item, except the session doing the reporting. */\nexport async function workItemReportRecipients(\n workItems: Pick<WorkItemsStorage, 'listWorkItemSubscribers'>,\n { orgId, workItemId, senderSessionId }: { orgId: string; workItemId: string; senderSessionId: string },\n): Promise<FactoryDeliveryTarget[]> {\n const subscribers = await workItems.listWorkItemSubscribers({ orgId, workItemId });\n return subscribers\n .filter(subscriber => subscriber.sessionId !== senderSessionId)\n .map(subscriber => ({ sessionId: subscriber.sessionId, threadId: subscriber.threadId }));\n}\n\n/** How reports name an item: `#n`, else its quoted title, else its id. */\nasync function workItemReference(\n workItems: Pick<WorkItemsStorage, 'getForProject'>,\n { orgId, factoryProjectId, workItemId }: { orgId: string; factoryProjectId: string; workItemId: string },\n): Promise<{ label: string; ref: string }> {\n const item = await workItems.getForProject(orgId, factoryProjectId, workItemId);\n const number = item ? workItemNumber(item) : null;\n if (number !== null) return { label: `#${number}`, ref: `#${number}` };\n return { label: item ? `\"${item.title}\"` : workItemId, ref: workItemId };\n}\n\n/** Attributes that say which item and role a worker's report is about. */\nexport async function workItemReportAttributes(\n workItems: Pick<WorkItemsStorage, 'getForProject'>,\n {\n orgId,\n factoryProjectId,\n workItemId,\n role,\n }: { orgId: string; factoryProjectId: string; workItemId: string; role: string },\n): Promise<{ label: string; attributes: Record<string, string> }> {\n const { label, ref } = await workItemReference(workItems, { orgId, factoryProjectId, workItemId });\n return { label, attributes: { 'work-item': ref, role } };\n}\n\nexport type FactoryRunOutcome =\n | { status: 'done' }\n | { status: 'failed'; reason: string; failureCode?: string }\n | { status: 'blocked'; toolName?: string; approval?: boolean };\n\n/** `waiting`: the session is waiting for an answer, so a later run owes the final report. */\nexport interface FactoryRunEndResult {\n waiting: boolean;\n deliveries?: FactoryDeliveryResult[];\n}\n\n/**\n * The run-end backstop: send `outcome` to each recipient that hasn't had a\n * final report from this sender since the run started. An agent's own `done`\n * or `failed` report wins. A run that completes after the agent's own\n * `blocked` sends nothing: the agent ended its turn to wait for an answer, and\n * its `done` comes from a later run. A `failed` backstop still goes out after an\n * earlier `blocked`.\n */\nexport async function sendRunEndReport(\n reporting: FactoryReporting,\n {\n sender,\n recipients,\n since,\n outcome,\n subject,\n attributes = {},\n }: {\n sender: FactoryReportSender;\n recipients: FactoryDeliveryTarget[];\n since: Date;\n outcome: FactoryRunOutcome;\n /** Who finished, e.g. \"The work session for #12\". */\n subject: string;\n attributes?: Record<string, string>;\n },\n): Promise<FactoryRunEndResult> {\n const kinds = outcome.status === 'blocked' ? ['blocked'] : ['done', 'failed'];\n const message =\n outcome.status === 'done'\n ? `${subject} finished.`\n : outcome.status === 'failed'\n ? `${subject} failed${outcome.failureCode ? ` (${outcome.failureCode})` : ''}: ${outcome.reason}`\n : outcome.approval\n ? `${subject} is waiting for someone to approve ${outcome.toolName ?? 'a tool call'} in that session.`\n : `${subject} is waiting${outcome.toolName ? ` on ${outcome.toolName}` : ''} and needs an answer.`;\n const notification = reportNotification({\n sender,\n status: outcome.status,\n message,\n attributes: {\n ...attributes,\n backstop: 'true',\n ...(outcome.status === 'failed' && outcome.failureCode ? { 'failure-code': outcome.failureCode } : {}),\n ...(outcome.status === 'blocked' && outcome.toolName ? { 'parked-tool': outcome.toolName } : {}),\n ...(outcome.status === 'blocked' && outcome.approval ? { 'waiting-for': 'approval' } : {}),\n },\n });\n const report = (recipient: FactoryDeliveryTarget, reportKinds: string[], agentOnly = false) =>\n hasFinalReport(reporting.notifications, {\n senderSessionId: sender.sessionId,\n recipientThreadId: recipient.threadId,\n since,\n kinds: reportKinds,\n agentOnly,\n });\n let waiting = outcome.status === 'blocked';\n const deliveries: FactoryDeliveryResult[] = [];\n for (const recipient of recipients) {\n // Only the agent's own reports count: an earlier run's backstop lands after this\n // run's window opened. A reply already told that follower how a finished run went.\n if (await report(recipient, outcome.status === 'done' ? [...kinds, 'reply'] : kinds, true)) continue;\n // Only the agent's own `blocked` means it ended its turn to wait; a backstop `blocked` from a parked run doesn't.\n if (outcome.status === 'done' && (await report(recipient, ['blocked'], true))) {\n waiting = true;\n continue;\n }\n if (\n outcome.status === 'failed' &&\n (await repeatsLastFailure(reporting.notifications, {\n senderSessionId: sender.sessionId,\n recipientThreadId: recipient.threadId,\n summary: message,\n }))\n ) {\n continue;\n }\n deliveries.push(await reporting.deliver(recipient, notification));\n }\n return { waiting, deliveries };\n}\n\nexport interface FactoryRunEndInput {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n role: string;\n sender: FactoryReportSender;\n since: Date;\n outcome: FactoryRunOutcome;\n}\n\nexport type FactoryRunEndReporter = (input: FactoryRunEndInput) => Promise<FactoryRunEndResult>;\n\n/** The backstop for a work-item run: reports to the item's subscribers. Failures are logged, never thrown. */\nexport function createWorkItemRunEndReporter({\n workItems,\n reporting,\n}: {\n workItems: Pick<WorkItemsStorage, 'listWorkItemSubscribers' | 'getForProject'>;\n /** Resolved per report: the controller exists only once the mount is up. */\n reporting: () => Promise<FactoryReporting | null>;\n}): FactoryRunEndReporter {\n return async input => {\n try {\n const resolved = await reporting();\n if (!resolved) return { waiting: false };\n const recipients = await workItemReportRecipients(workItems, {\n orgId: input.orgId,\n workItemId: input.workItemId,\n senderSessionId: input.sender.sessionId,\n });\n if (recipients.length === 0) return { waiting: false };\n const { label, attributes } = await workItemReportAttributes(workItems, input);\n return await sendRunEndReport(resolved, {\n sender: input.sender,\n recipients,\n since: input.since,\n outcome: input.outcome,\n subject: `The ${input.role} session for ${label}`,\n attributes,\n });\n } catch (error) {\n console.warn('[Factory reports] Unable to send the run-end report.', error);\n return { waiting: false };\n }\n };\n}\n\nexport interface FactoryStageChange {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n fromStage: string | undefined;\n toStage: string;\n}\n\n/**\n * Tells a work item's subscribers it moved, at `low` priority. Only sessions\n * already live hear it: a stage change never brings a session online; an\n * offline subscriber sees the stage in its state signal on its next turn.\n */\nexport function createStageChangeNotifier({\n workItems,\n reporting,\n}: {\n workItems: Pick<WorkItemsStorage, 'listWorkItemSubscribers' | 'getForProject'>;\n reporting: () => Promise<FactoryReporting | null>;\n}): (change: FactoryStageChange) => Promise<void> {\n return async change => {\n const subscribers = await workItems.listWorkItemSubscribers({\n orgId: change.orgId,\n workItemId: change.workItemId,\n });\n if (subscribers.length === 0) return;\n const resolved = await reporting();\n if (!resolved) return;\n const { label, ref } = await workItemReference(workItems, change);\n const summary = change.fromStage\n ? `${label} moved from ${change.fromStage} to ${change.toStage}.`\n : `${label} moved to ${change.toStage}.`;\n const notification: Omit<SendNotificationSignalInput, 'source'> = {\n kind: 'stage',\n priority: 'low',\n summary,\n payload: { message: summary },\n sourceId: change.workItemId,\n coalesceKey: `factory-stage:${change.workItemId}`,\n attributes: {\n 'work-item': ref,\n ...(change.fromStage ? { 'from-stage': change.fromStage } : {}),\n 'to-stage': change.toStage,\n },\n };\n for (const subscriber of subscribers) {\n try {\n await resolved.deliver({ sessionId: subscriber.sessionId, threadId: subscriber.threadId }, notification, {\n onlyIfLive: true,\n });\n } catch (error) {\n console.warn(`[Factory reports] Unable to tell ${subscriber.sessionId} that ${label} moved.`, error);\n }\n }\n };\n}\n\n/** Reports one run end of a session; the thread is the one the run used. */\nexport type FactorySessionRunEndReporter = (input: {\n sessionId: string;\n threadId: string | null;\n outcome: FactoryRunOutcome;\n since: Date;\n}) => Promise<void>;\n\n/** A tool call a session's run is holding until someone approves or declines it. */\nexport interface FactoryApprovalRequest {\n sessionId: string;\n threadId: string | null;\n toolName: string;\n toolCallId: string;\n args: unknown;\n}\n\nexport type FactoryApprovalNotifier = (request: FactoryApprovalRequest) => Promise<void>;\n\ninterface SessionReportDependencies {\n sessions: Pick<SourceControlSessionLookup, 'getBySessionId'>;\n workItems: Pick<\n WorkItemsStorage,\n 'findActiveRunBindingForSession' | 'findRunBindingBySession' | 'listWorkItemSubscribers' | 'getForProject'\n >;\n reporting: () => Promise<FactoryReporting | null>;\n}\n\n/** How much of a gated call's arguments an approval notice quotes. */\nconst APPROVAL_ARGS_LENGTH = 3000;\n\n/**\n * Who hears from a session: a work session (one with an active run binding)\n * reports to its item's followers, a side session to the session that started\n * it. Any other session (a person's own) has nobody to tell.\n */\nasync function sessionAudience(\n { sessions, workItems }: Omit<SessionReportDependencies, 'reporting'>,\n sessionId: string,\n threadId: string | null,\n /** When the run started: a binding revoked since then (the run moved its card to a finished stage) still counts. */\n since?: Date,\n): Promise<\n | {\n kind: 'work-item';\n binding: NonNullable<Awaited<ReturnType<WorkItemsStorage['findActiveRunBindingForSession']>>>;\n sender: FactoryReportSender;\n }\n | { kind: 'origin'; sender: FactoryReportSender; recipient: FactoryDeliveryTarget; subject: string }\n | null\n> {\n const row = await sessions.getBySessionId(sessionId);\n if (!row?.factoryProjectId) return null;\n let binding = await workItems.findActiveRunBindingForSession({\n orgId: row.orgId,\n factoryProjectId: row.factoryProjectId,\n sessionId: row.sessionId,\n });\n if (!binding && since && threadId) {\n const latest = await workItems.findRunBindingBySession({\n factoryProjectId: row.factoryProjectId,\n threadId,\n resourceId: row.sessionId,\n sessionId: row.sessionId,\n });\n if (latest?.orgId === row.orgId && latest.revokedAt && latest.revokedAt >= since) binding = latest;\n }\n if (binding) {\n return {\n kind: 'work-item',\n binding,\n sender: { sessionId: row.sessionId, threadId: binding.threadId, userId: row.userId },\n };\n }\n if (!row.originSessionId || !row.originThreadId || !threadId) return null;\n return {\n kind: 'origin',\n sender: { sessionId: row.sessionId, threadId, userId: row.userId },\n recipient: { sessionId: row.originSessionId, threadId: row.originThreadId },\n subject: `The side session \"${row.title ?? row.sessionId}\"`,\n };\n}\n\n/** Reports each run end of a work or side session to whoever follows it. */\nexport function createSessionRunEndReporter(deps: SessionReportDependencies): FactorySessionRunEndReporter {\n const workItemRunEnd = createWorkItemRunEndReporter(deps);\n return async ({ sessionId, threadId, outcome, since }) => {\n const audience = await sessionAudience(deps, sessionId, threadId, since);\n if (!audience) return;\n if (audience.kind === 'work-item') {\n const { binding, sender } = audience;\n await workItemRunEnd({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n workItemId: binding.workItemId,\n role: binding.role,\n sender,\n since,\n outcome,\n });\n return;\n }\n const resolved = await deps.reporting();\n if (!resolved) return;\n await sendRunEndReport(resolved, {\n sender: audience.sender,\n recipients: [audience.recipient],\n since,\n outcome,\n subject: audience.subject,\n });\n };\n}\n\n/**\n * Tells whoever follows a session, urgently, the moment its run holds a tool\n * call for approval. The run stays open on the gate, so no run end would say\n * so; the notice names the session and call so a follower can answer it with\n * `factory_answer_approval`.\n */\nexport function createSessionApprovalNotifier(deps: SessionReportDependencies): FactoryApprovalNotifier {\n return async ({ sessionId, threadId, toolName, toolCallId, args }) => {\n const audience = await sessionAudience(deps, sessionId, threadId);\n if (!audience) return;\n const resolved = await deps.reporting();\n if (!resolved) return;\n let subject: string;\n let recipients: FactoryDeliveryTarget[];\n let attributes: Record<string, string> = {};\n if (audience.kind === 'work-item') {\n const { binding } = audience;\n recipients = await workItemReportRecipients(deps.workItems, {\n orgId: binding.orgId,\n workItemId: binding.workItemId,\n senderSessionId: audience.sender.sessionId,\n });\n const named = await workItemReportAttributes(deps.workItems, binding);\n subject = `The ${binding.role} session for ${named.label}`;\n attributes = named.attributes;\n } else {\n recipients = [audience.recipient];\n subject = audience.subject;\n }\n if (recipients.length === 0) return;\n const argsText = JSON.stringify(args ?? {}) ?? '{}';\n const quoted = argsText.length > APPROVAL_ARGS_LENGTH ? `${argsText.slice(0, APPROVAL_ARGS_LENGTH)}…` : argsText;\n const message = [\n `${subject} is waiting for approval to run ${toolName} with ${quoted}.`,\n `Answer it with factory_answer_approval (sessionId \"${audience.sender.sessionId}\", toolCallId \"${toolCallId}\"), or open that session to approve or decline it there.`,\n ].join(' ');\n const notification = {\n ...reportNotification({\n sender: audience.sender,\n message,\n attributes: { ...attributes, 'waiting-for': 'approval', 'tool-name': toolName, 'tool-call-id': toolCallId },\n }),\n kind: 'approval',\n priority: 'urgent' as const,\n // One notice per held call, even if the gate is announced again.\n dedupeKey: `factory-approval:${toolCallId}`,\n };\n for (const recipient of recipients) await resolved.deliver(recipient, notification);\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"reports.js","names":[],"sources":["../../src/tools/reports.ts"],"sourcesContent":["/**\n * Reports from sessions doing the work back to whoever follows it: a side\n * session's origin, or every subscriber of a work item. The agent reports with\n * `factory_message_origin`; when a run ends without one, the run-end backstop\n * sends it, so each run produces exactly one final report per recipient.\n */\n\nimport type {\n NotificationPriority,\n NotificationsStorage,\n SendNotificationSignalInput,\n} from '@mastra/core/notifications';\n\nimport type { SourceControlSessionLookup } from '../session/factory-session.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { FACTORY_SESSION_NOTIFICATION_SOURCE } from './deliver.js';\nimport type { FactoryDeliveryResult, FactoryDeliveryTarget } from './deliver.js';\nimport { workItemNumber } from './read-tools.js';\n\nexport type FactoryReportStatus = 'progress' | 'blocked' | 'done' | 'failed';\n\n/** How each report status travels. Final reports never coalesce, so a later report can't overwrite them. */\nexport const FACTORY_REPORT_FORMS: Record<\n FactoryReportStatus | 'report',\n { kind: string; priority: NotificationPriority; coalesce: boolean }\n> = {\n failed: { kind: 'failed', priority: 'urgent', coalesce: false },\n blocked: { kind: 'blocked', priority: 'urgent', coalesce: false },\n done: { kind: 'done', priority: 'high', coalesce: false },\n progress: { kind: 'progress', priority: 'low', coalesce: true },\n // Not `message`: only messages can be answered, so a report can't start a reply loop.\n report: { kind: 'report', priority: 'high', coalesce: false },\n};\n\n/** How many recent `factory-session` notifications on a recipient thread the final-report check reads. */\nconst FINAL_REPORT_SCAN = 100;\n\nexport interface FactoryReportSender {\n sessionId: string;\n threadId: string;\n userId: string;\n}\n\nexport type FactoryReportDeliver = (\n target: FactoryDeliveryTarget,\n notification: Omit<SendNotificationSignalInput, 'source'>,\n options?: { onlyIfLive?: boolean },\n) => Promise<FactoryDeliveryResult>;\n\nexport interface FactoryReporting {\n deliver: FactoryReportDeliver;\n notifications: Pick<NotificationsStorage, 'listNotifications'>;\n}\n\n/**\n * Whether `senderSessionId` already put a report of one of `kinds` on the\n * recipient thread since `since`. Reads the durable notifications store, so it\n * holds across restarts. `listNotifications` has no time filter: records come\n * newest-updated first, and the scan stops at the first one last updated\n * before `since` (nothing after it can have been created later).\n */\nexport async function hasFinalReport(\n notifications: FactoryReporting['notifications'],\n {\n senderSessionId,\n recipientThreadId,\n since,\n kinds,\n agentOnly = false,\n }: {\n senderSessionId: string;\n recipientThreadId: string;\n since: Date;\n kinds: readonly string[];\n /** Ignore reports Factory sent on the session's behalf (the backstop). */\n agentOnly?: boolean;\n },\n): Promise<boolean> {\n const records = await notifications.listNotifications({\n threadId: recipientThreadId,\n source: FACTORY_SESSION_NOTIFICATION_SOURCE,\n limit: FINAL_REPORT_SCAN,\n });\n for (const record of records) {\n if (record.updatedAt < since) break;\n if (\n record.attributes?.['from-session'] === senderSessionId &&\n !(agentOnly && record.attributes?.backstop === 'true') &&\n // A reply carrying a final status counts like the report itself.\n (kinds.includes(record.kind) ||\n (record.kind === 'reply' &&\n typeof record.attributes?.status === 'string' &&\n kinds.includes(record.attributes.status))) &&\n record.createdAt >= since\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Whether the newest report `senderSessionId` put on the recipient thread is\n * already this exact `failed` backstop. A session stuck failing the same way\n * (a run that dies before the model, restarted over and over) then reports\n * once, not once per attempt; any other report in between resets it.\n */\nexport async function repeatsLastFailure(\n notifications: FactoryReporting['notifications'],\n {\n senderSessionId,\n recipientThreadId,\n summary,\n }: { senderSessionId: string; recipientThreadId: string; summary: string },\n): Promise<boolean> {\n const records = await notifications.listNotifications({\n threadId: recipientThreadId,\n source: FACTORY_SESSION_NOTIFICATION_SOURCE,\n limit: FINAL_REPORT_SCAN,\n });\n // Storage orders by `updatedAt`, which moves when a report is read; the\n // newest report is the one created last.\n const last = records\n .filter(record => record.attributes?.['from-session'] === senderSessionId)\n .reduce<(typeof records)[number] | undefined>(\n (newest, record) => (!newest || record.createdAt > newest.createdAt ? record : newest),\n undefined,\n );\n return last?.kind === 'failed' && last.attributes?.backstop === 'true' && last.summary === summary;\n}\n\nexport function reportNotification({\n sender,\n status,\n message,\n attributes = {},\n}: {\n sender: FactoryReportSender;\n status?: FactoryReportStatus;\n message: string;\n attributes?: Record<string, string>;\n}): Omit<SendNotificationSignalInput, 'source'> {\n const form = FACTORY_REPORT_FORMS[status ?? 'report'];\n return {\n kind: form.kind,\n priority: form.priority,\n // The whole text: a notification's summary is all the recipient's agent ever sees, even\n // through notification_inbox (which hides payloads), so a cut-off report loses its verdict.\n summary: message,\n payload: { message },\n sourceId: sender.sessionId,\n ...(form.coalesce ? { coalesceKey: `factory-session:${sender.sessionId}:${form.kind}` } : {}),\n attributes: {\n ...attributes,\n ...(status ? { status } : {}),\n // Last, so nothing above can claim another sender.\n 'from-session': sender.sessionId,\n 'from-thread': sender.threadId,\n 'from-user': sender.userId,\n },\n };\n}\n\n/** Everyone following a work item, except the session doing the reporting. */\nexport async function workItemReportRecipients(\n workItems: Pick<WorkItemsStorage, 'listWorkItemSubscribers'>,\n { orgId, workItemId, senderSessionId }: { orgId: string; workItemId: string; senderSessionId: string },\n): Promise<FactoryDeliveryTarget[]> {\n const subscribers = await workItems.listWorkItemSubscribers({ orgId, workItemId });\n return subscribers\n .filter(subscriber => subscriber.sessionId !== senderSessionId)\n .map(subscriber => ({ sessionId: subscriber.sessionId, threadId: subscriber.threadId }));\n}\n\n/** How reports name an item: `#n`, else its quoted title, else its id. */\nasync function workItemReference(\n workItems: Pick<WorkItemsStorage, 'getForProject'>,\n { orgId, factoryProjectId, workItemId }: { orgId: string; factoryProjectId: string; workItemId: string },\n): Promise<{ label: string; ref: string }> {\n const item = await workItems.getForProject(orgId, factoryProjectId, workItemId);\n const number = item ? workItemNumber(item) : null;\n if (number !== null) return { label: `#${number}`, ref: `#${number}` };\n return { label: item ? `\"${item.title}\"` : workItemId, ref: workItemId };\n}\n\n/** Attributes that say which item and role a worker's report is about. */\nexport async function workItemReportAttributes(\n workItems: Pick<WorkItemsStorage, 'getForProject'>,\n {\n orgId,\n factoryProjectId,\n workItemId,\n role,\n }: { orgId: string; factoryProjectId: string; workItemId: string; role: string },\n): Promise<{ label: string; attributes: Record<string, string> }> {\n const { label, ref } = await workItemReference(workItems, { orgId, factoryProjectId, workItemId });\n return { label, attributes: { 'work-item': ref, role } };\n}\n\nexport type FactoryRunOutcome =\n | { status: 'done' }\n | { status: 'failed'; reason: string; failureCode?: string }\n | { status: 'blocked'; toolName?: string; approval?: boolean };\n\n/** `waiting`: the session is waiting for an answer, so a later run owes the final report. */\nexport interface FactoryRunEndResult {\n waiting: boolean;\n deliveries?: FactoryDeliveryResult[];\n}\n\n/**\n * The run-end backstop: send `outcome` to each recipient that hasn't had a\n * final report from this sender since the run started. An agent's own `done`\n * or `failed` report wins. A run that completes after the agent's own\n * `blocked` sends nothing: the agent ended its turn to wait for an answer, and\n * its `done` comes from a later run. A `failed` backstop still goes out after an\n * earlier `blocked`.\n */\nexport async function sendRunEndReport(\n reporting: FactoryReporting,\n {\n sender,\n recipients,\n since,\n outcome,\n subject,\n attributes = {},\n }: {\n sender: FactoryReportSender;\n recipients: FactoryDeliveryTarget[];\n since: Date;\n outcome: FactoryRunOutcome;\n /** Who finished, e.g. \"The work session for #12\". */\n subject: string;\n attributes?: Record<string, string>;\n },\n): Promise<FactoryRunEndResult> {\n const kinds = outcome.status === 'blocked' ? ['blocked'] : ['done', 'failed'];\n const message =\n outcome.status === 'done'\n ? `${subject} finished.`\n : outcome.status === 'failed'\n ? `${subject} failed${outcome.failureCode ? ` (${outcome.failureCode})` : ''}: ${outcome.reason}`\n : outcome.approval\n ? `${subject} is waiting for someone to approve ${outcome.toolName ?? 'a tool call'} in that session.`\n : `${subject} is waiting${outcome.toolName ? ` on ${outcome.toolName}` : ''} and needs an answer.`;\n const notification = reportNotification({\n sender,\n status: outcome.status,\n message,\n attributes: {\n ...attributes,\n backstop: 'true',\n ...(outcome.status === 'failed' && outcome.failureCode ? { 'failure-code': outcome.failureCode } : {}),\n ...(outcome.status === 'blocked' && outcome.toolName ? { 'parked-tool': outcome.toolName } : {}),\n ...(outcome.status === 'blocked' && outcome.approval ? { 'waiting-for': 'approval' } : {}),\n },\n });\n const report = (recipient: FactoryDeliveryTarget, reportKinds: string[], agentOnly = false) =>\n hasFinalReport(reporting.notifications, {\n senderSessionId: sender.sessionId,\n recipientThreadId: recipient.threadId,\n since,\n kinds: reportKinds,\n agentOnly,\n });\n let waiting = outcome.status === 'blocked';\n const deliveries: FactoryDeliveryResult[] = [];\n for (const recipient of recipients) {\n // Only the agent's own reports count: an earlier run's backstop lands after this\n // run's window opened. A reply already told that follower how a finished run went.\n if (await report(recipient, outcome.status === 'done' ? [...kinds, 'reply'] : kinds, true)) continue;\n // Only the agent's own `blocked` means it ended its turn to wait; a backstop `blocked` from a parked run doesn't.\n if (outcome.status === 'done' && (await report(recipient, ['blocked'], true))) {\n waiting = true;\n continue;\n }\n if (\n outcome.status === 'failed' &&\n (await repeatsLastFailure(reporting.notifications, {\n senderSessionId: sender.sessionId,\n recipientThreadId: recipient.threadId,\n summary: message,\n }))\n ) {\n continue;\n }\n deliveries.push(await reporting.deliver(recipient, notification));\n }\n return { waiting, deliveries };\n}\n\nexport interface FactoryRunEndInput {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n role: string;\n sender: FactoryReportSender;\n since: Date;\n outcome: FactoryRunOutcome;\n}\n\nexport type FactoryRunEndReporter = (input: FactoryRunEndInput) => Promise<FactoryRunEndResult>;\n\n/** The backstop for a work-item run: reports to the item's subscribers. Failures are logged, never thrown. */\nexport function createWorkItemRunEndReporter({\n workItems,\n reporting,\n}: {\n workItems: Pick<WorkItemsStorage, 'listWorkItemSubscribers' | 'getForProject'>;\n /** Resolved per report: the controller exists only once the mount is up. */\n reporting: () => Promise<FactoryReporting | null>;\n}): FactoryRunEndReporter {\n return async input => {\n try {\n const resolved = await reporting();\n if (!resolved) return { waiting: false };\n const recipients = await workItemReportRecipients(workItems, {\n orgId: input.orgId,\n workItemId: input.workItemId,\n senderSessionId: input.sender.sessionId,\n });\n if (recipients.length === 0) return { waiting: false };\n const { label, attributes } = await workItemReportAttributes(workItems, input);\n return await sendRunEndReport(resolved, {\n sender: input.sender,\n recipients,\n since: input.since,\n outcome: input.outcome,\n subject: `The ${input.role} session for ${label}`,\n attributes,\n });\n } catch (error) {\n console.warn('[Factory reports] Unable to send the run-end report.', error);\n return { waiting: false };\n }\n };\n}\n\nexport interface FactoryStageChange {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n fromStage: string | undefined;\n toStage: string;\n}\n\n/**\n * Tells a work item's subscribers it moved, at `low` priority. Only sessions\n * already live hear it: a stage change never brings a session online; an\n * offline subscriber sees the stage in its state signal on its next turn.\n */\nexport function createStageChangeNotifier({\n workItems,\n reporting,\n}: {\n workItems: Pick<WorkItemsStorage, 'listWorkItemSubscribers' | 'getForProject'>;\n reporting: () => Promise<FactoryReporting | null>;\n}): (change: FactoryStageChange) => Promise<void> {\n return async change => {\n const subscribers = await workItems.listWorkItemSubscribers({\n orgId: change.orgId,\n workItemId: change.workItemId,\n });\n if (subscribers.length === 0) return;\n const resolved = await reporting();\n if (!resolved) return;\n const { label, ref } = await workItemReference(workItems, change);\n const summary = change.fromStage\n ? `${label} moved from ${change.fromStage} to ${change.toStage}.`\n : `${label} moved to ${change.toStage}.`;\n const notification: Omit<SendNotificationSignalInput, 'source'> = {\n kind: 'stage',\n priority: 'low',\n summary,\n payload: { message: summary },\n sourceId: change.workItemId,\n coalesceKey: `factory-stage:${change.workItemId}`,\n attributes: {\n 'work-item': ref,\n ...(change.fromStage ? { 'from-stage': change.fromStage } : {}),\n 'to-stage': change.toStage,\n },\n };\n for (const subscriber of subscribers) {\n try {\n await resolved.deliver({ sessionId: subscriber.sessionId, threadId: subscriber.threadId }, notification, {\n onlyIfLive: true,\n });\n } catch (error) {\n console.warn(`[Factory reports] Unable to tell ${subscriber.sessionId} that ${label} moved.`, error);\n }\n }\n };\n}\n\n/** Reports one run end of a session; the thread is the one the run used. */\nexport type FactorySessionRunEndReporter = (input: {\n sessionId: string;\n threadId: string | null;\n outcome: FactoryRunOutcome;\n since: Date;\n}) => Promise<void>;\n\n/** A tool call a session's run is holding until someone approves or declines it. */\nexport interface FactoryApprovalRequest {\n sessionId: string;\n threadId: string | null;\n toolName: string;\n toolCallId: string;\n args: unknown;\n}\n\nexport type FactoryApprovalNotifier = (request: FactoryApprovalRequest) => Promise<void>;\n\ninterface SessionReportDependencies {\n sessions: Pick<SourceControlSessionLookup, 'getBySessionId'>;\n workItems: Pick<\n WorkItemsStorage,\n 'findActiveRunBindingForSession' | 'findRunBindingBySession' | 'listWorkItemSubscribers' | 'getForProject'\n >;\n reporting: () => Promise<FactoryReporting | null>;\n}\n\n/** How much of a gated call's arguments an approval notice quotes. */\nconst APPROVAL_ARGS_LENGTH = 3000;\n\n/**\n * Who hears from a session: a work session (one with an active run binding)\n * reports to its item's followers, a side session to the session that started\n * it. Any other session (a person's own) has nobody to tell.\n */\nasync function sessionAudience(\n { sessions, workItems }: Omit<SessionReportDependencies, 'reporting'>,\n sessionId: string,\n threadId: string | null,\n /** When the run started: a binding revoked since then (the run moved its card to a finished stage) still counts. */\n since?: Date,\n): Promise<\n | {\n kind: 'work-item';\n binding: NonNullable<Awaited<ReturnType<WorkItemsStorage['findActiveRunBindingForSession']>>>;\n sender: FactoryReportSender;\n }\n | {\n kind: 'origin';\n sender: FactoryReportSender;\n recipient: FactoryDeliveryTarget;\n subject: string;\n /** Set while the origin has muted this side session's reports (approval notices still go through). */\n mutedAt: Date | null;\n }\n | null\n> {\n const row = await sessions.getBySessionId(sessionId);\n if (!row?.factoryProjectId) return null;\n let binding = await workItems.findActiveRunBindingForSession({\n orgId: row.orgId,\n factoryProjectId: row.factoryProjectId,\n sessionId: row.sessionId,\n });\n if (!binding && since && threadId) {\n const latest = await workItems.findRunBindingBySession({\n factoryProjectId: row.factoryProjectId,\n threadId,\n resourceId: row.sessionId,\n sessionId: row.sessionId,\n });\n if (latest?.orgId === row.orgId && latest.revokedAt && latest.revokedAt >= since) binding = latest;\n }\n if (binding) {\n return {\n kind: 'work-item',\n binding,\n sender: { sessionId: row.sessionId, threadId: binding.threadId, userId: row.userId },\n };\n }\n if (!row.originSessionId || !row.originThreadId || !threadId) return null;\n return {\n kind: 'origin',\n sender: { sessionId: row.sessionId, threadId, userId: row.userId },\n recipient: { sessionId: row.originSessionId, threadId: row.originThreadId },\n subject: `The side session \"${row.title ?? row.sessionId}\"`,\n mutedAt: row.originMutedAt ?? null,\n };\n}\n\n/** Reports each run end of a work or side session to whoever follows it. */\nexport function createSessionRunEndReporter(deps: SessionReportDependencies): FactorySessionRunEndReporter {\n const workItemRunEnd = createWorkItemRunEndReporter(deps);\n return async ({ sessionId, threadId, outcome, since }) => {\n const audience = await sessionAudience(deps, sessionId, threadId, since);\n if (!audience) return;\n if (audience.kind === 'work-item') {\n const { binding, sender } = audience;\n await workItemRunEnd({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n workItemId: binding.workItemId,\n role: binding.role,\n sender,\n since,\n outcome,\n });\n return;\n }\n // A muted origin still hears when the session is blocked: nothing else would tell it the session is waiting.\n if (audience.mutedAt && outcome.status !== 'blocked') return;\n const resolved = await deps.reporting();\n if (!resolved) return;\n await sendRunEndReport(resolved, {\n sender: audience.sender,\n recipients: [audience.recipient],\n since,\n outcome,\n subject: audience.subject,\n });\n };\n}\n\n/**\n * Tells whoever follows a session, urgently, the moment its run holds a tool\n * call for approval. The run stays open on the gate, so no run end would say\n * so; the notice names the session and call so a follower can answer it with\n * `factory_answer_approval`.\n */\nexport function createSessionApprovalNotifier(deps: SessionReportDependencies): FactoryApprovalNotifier {\n return async ({ sessionId, threadId, toolName, toolCallId, args }) => {\n const audience = await sessionAudience(deps, sessionId, threadId);\n if (!audience) return;\n const resolved = await deps.reporting();\n if (!resolved) return;\n let subject: string;\n let recipients: FactoryDeliveryTarget[];\n let attributes: Record<string, string> = {};\n if (audience.kind === 'work-item') {\n const { binding } = audience;\n recipients = await workItemReportRecipients(deps.workItems, {\n orgId: binding.orgId,\n workItemId: binding.workItemId,\n senderSessionId: audience.sender.sessionId,\n });\n const named = await workItemReportAttributes(deps.workItems, binding);\n subject = `The ${binding.role} session for ${named.label}`;\n attributes = named.attributes;\n } else {\n recipients = [audience.recipient];\n subject = audience.subject;\n }\n if (recipients.length === 0) return;\n const argsText = JSON.stringify(args ?? {}) ?? '{}';\n const quoted = argsText.length > APPROVAL_ARGS_LENGTH ? `${argsText.slice(0, APPROVAL_ARGS_LENGTH)}…` : argsText;\n const message = [\n `${subject} is waiting for approval to run ${toolName} with ${quoted}.`,\n `Answer it with factory_answer_approval (sessionId \"${audience.sender.sessionId}\", toolCallId \"${toolCallId}\"), or open that session to approve or decline it there.`,\n ].join(' ');\n const notification = {\n ...reportNotification({\n sender: audience.sender,\n message,\n attributes: { ...attributes, 'waiting-for': 'approval', 'tool-name': toolName, 'tool-call-id': toolCallId },\n }),\n kind: 'approval',\n priority: 'urgent' as const,\n // One notice per held call, even if the gate is announced again.\n dedupeKey: `factory-approval:${toolCallId}`,\n };\n for (const recipient of recipients) await resolved.deliver(recipient, notification);\n };\n}\n"],"mappings":";;;;AAsBA,MAAa,uBAGT;CACF,QAAQ;EAAE,MAAM;EAAU,UAAU;EAAU,UAAU;CAAM;CAC9D,SAAS;EAAE,MAAM;EAAW,UAAU;EAAU,UAAU;CAAM;CAChE,MAAM;EAAE,MAAM;EAAQ,UAAU;EAAQ,UAAU;CAAM;CACxD,UAAU;EAAE,MAAM;EAAY,UAAU;EAAO,UAAU;CAAK;CAE9D,QAAQ;EAAE,MAAM;EAAU,UAAU;EAAQ,UAAU;CAAM;AAC9D;;AAGA,MAAM,oBAAoB;;;;;;;;AA0B1B,eAAsB,eACpB,eACA,EACE,iBACA,mBACA,OACA,OACA,YAAY,SASI;CAClB,MAAM,UAAU,MAAM,cAAc,kBAAkB;EACpD,UAAU;EACV,QAAQ;EACR,OAAO;CACT,CAAC;CACD,KAAK,MAAM,UAAU,SAAS;EAC5B,IAAI,OAAO,YAAY,OAAO;EAC9B,IACE,OAAO,aAAa,oBAAoB,mBACxC,EAAE,aAAa,OAAO,YAAY,aAAa,YAE9C,MAAM,SAAS,OAAO,IAAI,KACxB,OAAO,SAAS,WACf,OAAO,OAAO,YAAY,WAAW,YACrC,MAAM,SAAS,OAAO,WAAW,MAAM,MAC3C,OAAO,aAAa,OAEpB,OAAO;CAEX;CACA,OAAO;AACT;;;;;;;AAQA,eAAsB,mBACpB,eACA,EACE,iBACA,mBACA,WAEgB;CAQlB,MAAM,QAAO,MAPS,cAAc,kBAAkB;EACpD,UAAU;EACV,QAAQ;EACR,OAAO;CACT,CAAC,EAAA,CAIE,QAAO,WAAU,OAAO,aAAa,oBAAoB,eAAe,CAAC,CACzE,QACE,QAAQ,WAAY,CAAC,UAAU,OAAO,YAAY,OAAO,YAAY,SAAS,QAC/E,KAAA,CACF;CACF,OAAO,MAAM,SAAS,YAAY,KAAK,YAAY,aAAa,UAAU,KAAK,YAAY;AAC7F;AAEA,SAAgB,mBAAmB,EACjC,QACA,QACA,SACA,aAAa,CAAC,KAMgC;CAC9C,MAAM,OAAO,qBAAqB,UAAU;CAC5C,OAAO;EACL,MAAM,KAAK;EACX,UAAU,KAAK;EAGf,SAAS;EACT,SAAS,EAAE,QAAQ;EACnB,UAAU,OAAO;EACjB,GAAI,KAAK,WAAW,EAAE,aAAa,mBAAmB,OAAO,UAAU,GAAG,KAAK,OAAO,IAAI,CAAC;EAC3F,YAAY;GACV,GAAG;GACH,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;GAE3B,gBAAgB,OAAO;GACvB,eAAe,OAAO;GACtB,aAAa,OAAO;EACtB;CACF;AACF;;AAGA,eAAsB,yBACpB,WACA,EAAE,OAAO,YAAY,mBACa;CAElC,QAAO,MADmB,UAAU,wBAAwB;EAAE;EAAO;CAAW,CAAC,EAAA,CAE9E,QAAO,eAAc,WAAW,cAAc,eAAe,CAAC,CAC9D,KAAI,gBAAe;EAAE,WAAW,WAAW;EAAW,UAAU,WAAW;CAAS,EAAE;AAC3F;;AAGA,eAAe,kBACb,WACA,EAAE,OAAO,kBAAkB,cACc;CACzC,MAAM,OAAO,MAAM,UAAU,cAAc,OAAO,kBAAkB,UAAU;CAC9E,MAAM,SAAS,OAAO,eAAe,IAAI,IAAI;CAC7C,IAAI,WAAW,MAAM,OAAO;EAAE,OAAO,IAAI;EAAU,KAAK,IAAI;CAAS;CACrE,OAAO;EAAE,OAAO,OAAO,IAAI,KAAK,MAAM,KAAK;EAAY,KAAK;CAAW;AACzE;;AAGA,eAAsB,yBACpB,WACA,EACE,OACA,kBACA,YACA,QAE8D;CAChE,MAAM,EAAE,OAAO,QAAQ,MAAM,kBAAkB,WAAW;EAAE;EAAO;EAAkB;CAAW,CAAC;CACjG,OAAO;EAAE;EAAO,YAAY;GAAE,aAAa;GAAK;EAAK;CAAE;AACzD;;;;;;;;;AAqBA,eAAsB,iBACpB,WACA,EACE,QACA,YACA,OACA,SACA,SACA,aAAa,CAAC,KAUc;CAC9B,MAAM,QAAQ,QAAQ,WAAW,YAAY,CAAC,SAAS,IAAI,CAAC,QAAQ,QAAQ;CAC5E,MAAM,UACJ,QAAQ,WAAW,SACf,GAAG,QAAQ,cACX,QAAQ,WAAW,WACjB,GAAG,QAAQ,SAAS,QAAQ,cAAc,KAAK,QAAQ,YAAY,KAAK,GAAG,IAAI,QAAQ,WACvF,QAAQ,WACN,GAAG,QAAQ,qCAAqC,QAAQ,YAAY,cAAc,qBAClF,GAAG,QAAQ,aAAa,QAAQ,WAAW,OAAO,QAAQ,aAAa,GAAG;CACpF,MAAM,eAAe,mBAAmB;EACtC;EACA,QAAQ,QAAQ;EAChB;EACA,YAAY;GACV,GAAG;GACH,UAAU;GACV,GAAI,QAAQ,WAAW,YAAY,QAAQ,cAAc,EAAE,gBAAgB,QAAQ,YAAY,IAAI,CAAC;GACpG,GAAI,QAAQ,WAAW,aAAa,QAAQ,WAAW,EAAE,eAAe,QAAQ,SAAS,IAAI,CAAC;GAC9F,GAAI,QAAQ,WAAW,aAAa,QAAQ,WAAW,EAAE,eAAe,WAAW,IAAI,CAAC;EAC1F;CACF,CAAC;CACD,MAAM,UAAU,WAAkC,aAAuB,YAAY,UACnF,eAAe,UAAU,eAAe;EACtC,iBAAiB,OAAO;EACxB,mBAAmB,UAAU;EAC7B;EACA,OAAO;EACP;CACF,CAAC;CACH,IAAI,UAAU,QAAQ,WAAW;CACjC,MAAM,aAAsC,CAAC;CAC7C,KAAK,MAAM,aAAa,YAAY;EAGlC,IAAI,MAAM,OAAO,WAAW,QAAQ,WAAW,SAAS,CAAC,GAAG,OAAO,OAAO,IAAI,OAAO,IAAI,GAAG;EAE5F,IAAI,QAAQ,WAAW,UAAW,MAAM,OAAO,WAAW,CAAC,SAAS,GAAG,IAAI,GAAI;GAC7E,UAAU;GACV;EACF;EACA,IACE,QAAQ,WAAW,YAClB,MAAM,mBAAmB,UAAU,eAAe;GACjD,iBAAiB,OAAO;GACxB,mBAAmB,UAAU;GAC7B,SAAS;EACX,CAAC,GAED;EAEF,WAAW,KAAK,MAAM,UAAU,QAAQ,WAAW,YAAY,CAAC;CAClE;CACA,OAAO;EAAE;EAAS;CAAW;AAC/B;;AAeA,SAAgB,6BAA6B,EAC3C,WACA,aAKwB;CACxB,OAAO,OAAM,UAAS;EACpB,IAAI;GACF,MAAM,WAAW,MAAM,UAAU;GACjC,IAAI,CAAC,UAAU,OAAO,EAAE,SAAS,MAAM;GACvC,MAAM,aAAa,MAAM,yBAAyB,WAAW;IAC3D,OAAO,MAAM;IACb,YAAY,MAAM;IAClB,iBAAiB,MAAM,OAAO;GAChC,CAAC;GACD,IAAI,WAAW,WAAW,GAAG,OAAO,EAAE,SAAS,MAAM;GACrD,MAAM,EAAE,OAAO,eAAe,MAAM,yBAAyB,WAAW,KAAK;GAC7E,OAAO,MAAM,iBAAiB,UAAU;IACtC,QAAQ,MAAM;IACd;IACA,OAAO,MAAM;IACb,SAAS,MAAM;IACf,SAAS,OAAO,MAAM,KAAK,eAAe;IAC1C;GACF,CAAC;EACH,SAAS,OAAO;GACd,QAAQ,KAAK,wDAAwD,KAAK;GAC1E,OAAO,EAAE,SAAS,MAAM;EAC1B;CACF;AACF;;;;;;AAeA,SAAgB,0BAA0B,EACxC,WACA,aAIgD;CAChD,OAAO,OAAM,WAAU;EACrB,MAAM,cAAc,MAAM,UAAU,wBAAwB;GAC1D,OAAO,OAAO;GACd,YAAY,OAAO;EACrB,CAAC;EACD,IAAI,YAAY,WAAW,GAAG;EAC9B,MAAM,WAAW,MAAM,UAAU;EACjC,IAAI,CAAC,UAAU;EACf,MAAM,EAAE,OAAO,QAAQ,MAAM,kBAAkB,WAAW,MAAM;EAChE,MAAM,UAAU,OAAO,YACnB,GAAG,MAAM,cAAc,OAAO,UAAU,MAAM,OAAO,QAAQ,KAC7D,GAAG,MAAM,YAAY,OAAO,QAAQ;EACxC,MAAM,eAA4D;GAChE,MAAM;GACN,UAAU;GACV;GACA,SAAS,EAAE,SAAS,QAAQ;GAC5B,UAAU,OAAO;GACjB,aAAa,iBAAiB,OAAO;GACrC,YAAY;IACV,aAAa;IACb,GAAI,OAAO,YAAY,EAAE,cAAc,OAAO,UAAU,IAAI,CAAC;IAC7D,YAAY,OAAO;GACrB;EACF;EACA,KAAK,MAAM,cAAc,aACvB,IAAI;GACF,MAAM,SAAS,QAAQ;IAAE,WAAW,WAAW;IAAW,UAAU,WAAW;GAAS,GAAG,cAAc,EACvG,YAAY,KACd,CAAC;EACH,SAAS,OAAO;GACd,QAAQ,KAAK,oCAAoC,WAAW,UAAU,QAAQ,MAAM,UAAU,KAAK;EACrG;CAEJ;AACF;;AA+BA,MAAM,uBAAuB;;;;;;AAO7B,eAAe,gBACb,EAAE,UAAU,aACZ,WACA,UAEA,OAgBA;CACA,MAAM,MAAM,MAAM,SAAS,eAAe,SAAS;CACnD,IAAI,CAAC,KAAK,kBAAkB,OAAO;CACnC,IAAI,UAAU,MAAM,UAAU,+BAA+B;EAC3D,OAAO,IAAI;EACX,kBAAkB,IAAI;EACtB,WAAW,IAAI;CACjB,CAAC;CACD,IAAI,CAAC,WAAW,SAAS,UAAU;EACjC,MAAM,SAAS,MAAM,UAAU,wBAAwB;GACrD,kBAAkB,IAAI;GACtB;GACA,YAAY,IAAI;GAChB,WAAW,IAAI;EACjB,CAAC;EACD,IAAI,QAAQ,UAAU,IAAI,SAAS,OAAO,aAAa,OAAO,aAAa,OAAO,UAAU;CAC9F;CACA,IAAI,SACF,OAAO;EACL,MAAM;EACN;EACA,QAAQ;GAAE,WAAW,IAAI;GAAW,UAAU,QAAQ;GAAU,QAAQ,IAAI;EAAO;CACrF;CAEF,IAAI,CAAC,IAAI,mBAAmB,CAAC,IAAI,kBAAkB,CAAC,UAAU,OAAO;CACrE,OAAO;EACL,MAAM;EACN,QAAQ;GAAE,WAAW,IAAI;GAAW;GAAU,QAAQ,IAAI;EAAO;EACjE,WAAW;GAAE,WAAW,IAAI;GAAiB,UAAU,IAAI;EAAe;EAC1E,SAAS,qBAAqB,IAAI,SAAS,IAAI,UAAU;EACzD,SAAS,IAAI,iBAAiB;CAChC;AACF;;AAGA,SAAgB,4BAA4B,MAA+D;CACzG,MAAM,iBAAiB,6BAA6B,IAAI;CACxD,OAAO,OAAO,EAAE,WAAW,UAAU,SAAS,YAAY;EACxD,MAAM,WAAW,MAAM,gBAAgB,MAAM,WAAW,UAAU,KAAK;EACvE,IAAI,CAAC,UAAU;EACf,IAAI,SAAS,SAAS,aAAa;GACjC,MAAM,EAAE,SAAS,WAAW;GAC5B,MAAM,eAAe;IACnB,OAAO,QAAQ;IACf,kBAAkB,QAAQ;IAC1B,YAAY,QAAQ;IACpB,MAAM,QAAQ;IACd;IACA;IACA;GACF,CAAC;GACD;EACF;EAEA,IAAI,SAAS,WAAW,QAAQ,WAAW,WAAW;EACtD,MAAM,WAAW,MAAM,KAAK,UAAU;EACtC,IAAI,CAAC,UAAU;EACf,MAAM,iBAAiB,UAAU;GAC/B,QAAQ,SAAS;GACjB,YAAY,CAAC,SAAS,SAAS;GAC/B;GACA;GACA,SAAS,SAAS;EACpB,CAAC;CACH;AACF;;;;;;;AAQA,SAAgB,8BAA8B,MAA0D;CACtG,OAAO,OAAO,EAAE,WAAW,UAAU,UAAU,YAAY,WAAW;EACpE,MAAM,WAAW,MAAM,gBAAgB,MAAM,WAAW,QAAQ;EAChE,IAAI,CAAC,UAAU;EACf,MAAM,WAAW,MAAM,KAAK,UAAU;EACtC,IAAI,CAAC,UAAU;EACf,IAAI;EACJ,IAAI;EACJ,IAAI,aAAqC,CAAC;EAC1C,IAAI,SAAS,SAAS,aAAa;GACjC,MAAM,EAAE,YAAY;GACpB,aAAa,MAAM,yBAAyB,KAAK,WAAW;IAC1D,OAAO,QAAQ;IACf,YAAY,QAAQ;IACpB,iBAAiB,SAAS,OAAO;GACnC,CAAC;GACD,MAAM,QAAQ,MAAM,yBAAyB,KAAK,WAAW,OAAO;GACpE,UAAU,OAAO,QAAQ,KAAK,eAAe,MAAM;GACnD,aAAa,MAAM;EACrB,OAAO;GACL,aAAa,CAAC,SAAS,SAAS;GAChC,UAAU,SAAS;EACrB;EACA,IAAI,WAAW,WAAW,GAAG;EAC7B,MAAM,WAAW,KAAK,UAAU,QAAQ,CAAC,CAAC,KAAK;EAC/C,MAAM,SAAS,SAAS,SAAS,uBAAuB,GAAG,SAAS,MAAM,GAAG,oBAAoB,EAAE,KAAK;EACxG,MAAM,UAAU,CACd,GAAG,QAAQ,kCAAkC,SAAS,QAAQ,OAAO,IACrE,sDAAsD,SAAS,OAAO,UAAU,iBAAiB,WAAW,yDAC9G,CAAC,CAAC,KAAK,GAAG;EACV,MAAM,eAAe;GACnB,GAAG,mBAAmB;IACpB,QAAQ,SAAS;IACjB;IACA,YAAY;KAAE,GAAG;KAAY,eAAe;KAAY,aAAa;KAAU,gBAAgB;IAAW;GAC5G,CAAC;GACD,MAAM;GACN,UAAU;GAEV,WAAW,oBAAoB;EACjC;EACA,KAAK,MAAM,aAAa,YAAY,MAAM,SAAS,QAAQ,WAAW,YAAY;CACpF;AACF"}
@@ -44,4 +44,10 @@ export interface ResolveFactoryToolScopeOptions {
44
44
  * nothing clears it.
45
45
  */
46
46
  export declare function resolveFactoryToolScope({ requestContext, authEnabled, sessions, projects, workItems, }: ResolveFactoryToolScopeOptions): Promise<FactoryToolScope | null>;
47
+ /**
48
+ * Refuses an action only a signed-in person may take. Turns nobody signed in
49
+ * started (notification wakes, webhooks, dispatcher notices) carry text other
50
+ * people wrote, so they can't approve, resolve proposals, or waive plan approval.
51
+ */
52
+ export declare function requirePerson(scope: FactoryToolScope, action: string): void;
47
53
  //# sourceMappingURL=scope.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../src/tools/scope.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnE,OAAO,EACL,uBAAuB,EAIxB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAEhF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC/E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE9E,OAAO,EAAE,uBAAuB,EAAE,CAAC;AAEnC,wFAAwF;AACxF,eAAO,MAAM,wBAAwB,+BAA+B,CAAC;AAErE,sGAAsG;AACtG,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,qFAAqF;IACrF,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,+FAA+F;IAC/F,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC;IACxB,sGAAsG;IACtG,WAAW,EAAE,OAAO,CAAC;IACrB,6FAA6F;IAC7F,YAAY,CAAC,EAAE,sBAAsB,CAAC;IACtC,2EAA2E;IAC3E,MAAM,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAClD;AAED,MAAM,WAAW,8BAA8B;IAC7C,cAAc,EAAE,cAAc,CAAC;IAC/B,mFAAmF;IACnF,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,IAAI,CAAC,0BAA0B,EAAE,gBAAgB,CAAC,CAAC;IAC7D,QAAQ,EAAE,IAAI,CAAC,sBAAsB,EAAE,KAAK,CAAC,CAAC;IAC9C,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,yBAAyB,CAAC,CAAC;CAC9D;AAED;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAAC,EAC5C,cAAc,EACd,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,SAAS,GACV,EAAE,8BAA8B,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAiEnE"}
1
+ {"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../src/tools/scope.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnE,OAAO,EACL,uBAAuB,EAIxB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAEhF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC/E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE9E,OAAO,EAAE,uBAAuB,EAAE,CAAC;AAEnC,wFAAwF;AACxF,eAAO,MAAM,wBAAwB,+BAA+B,CAAC;AAErE,sGAAsG;AACtG,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,qFAAqF;IACrF,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,+FAA+F;IAC/F,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC;IACxB,sGAAsG;IACtG,WAAW,EAAE,OAAO,CAAC;IACrB,6FAA6F;IAC7F,YAAY,CAAC,EAAE,sBAAsB,CAAC;IACtC,2EAA2E;IAC3E,MAAM,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAClD;AAED,MAAM,WAAW,8BAA8B;IAC7C,cAAc,EAAE,cAAc,CAAC;IAC/B,mFAAmF;IACnF,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,IAAI,CAAC,0BAA0B,EAAE,gBAAgB,CAAC,CAAC;IAC7D,QAAQ,EAAE,IAAI,CAAC,sBAAsB,EAAE,KAAK,CAAC,CAAC;IAC9C,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,yBAAyB,CAAC,CAAC;CAC9D;AAED;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAAC,EAC5C,cAAc,EACd,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,SAAS,GACV,EAAE,8BAA8B,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAiEnE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAK3E"}
@@ -71,7 +71,16 @@ async function resolveFactoryToolScope({ requestContext, authEnabled, sessions,
71
71
  ...origin ? { origin } : {}
72
72
  };
73
73
  }
74
+ /**
75
+ * Refuses an action only a signed-in person may take. Turns nobody signed in
76
+ * started (notification wakes, webhooks, dispatcher notices) carry text other
77
+ * people wrote, so they can't approve, resolve proposals, or waive plan approval.
78
+ */
79
+ function requirePerson(scope, action) {
80
+ if (scope.interactive) return;
81
+ throw new Error(`Only a person can ${action}. Nobody signed in started this turn (it came from a notification, webhook or rule); ask the person to do it from the board or by messaging this session.`);
82
+ }
74
83
  //#endregion
75
- export { FACTORY_INTERACTIVE_KEY, FACTORY_SLACK_SENDER_KEY, resolveFactoryToolScope };
84
+ export { FACTORY_INTERACTIVE_KEY, FACTORY_SLACK_SENDER_KEY, requirePerson, resolveFactoryToolScope };
76
85
 
77
86
  //# sourceMappingURL=scope.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"scope.js","names":[],"sources":["../../src/tools/scope.ts"],"sourcesContent":["import type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\n\nimport {\n FACTORY_INTERACTIVE_KEY,\n factoryUserOrgId,\n getFactoryAuthUserFromContext,\n getFactoryAuthUserId,\n} from '../auth.js';\nimport type { SourceControlSessionLookup } from '../session/factory-session.js';\nimport { auditActorProfile } from '../storage/domains/audit/base.js';\nimport type { AuditActorProfileInput } from '../storage/domains/audit/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\n\nexport { FACTORY_INTERACTIVE_KEY };\n\n/** Linked Slack sender, separate from the session owner's model-credential identity. */\nexport const FACTORY_SLACK_SENDER_KEY = 'mastra__factorySlackSender';\n\n/** Who is calling Factory tools, and from which session. Everything a tool needs to stay in scope. */\nexport interface FactoryToolScope {\n orgId: string;\n factoryProjectId: string;\n /** The signed-in caller for a user session; the session row's owner for a worker. */\n userId: string;\n /** The source-control session id; equal to the controller resourceId. */\n sessionId: string;\n resourceId: string;\n threadId: string;\n /** `worker` sessions are bound to a work item run; only `user` sessions manage the Factory. */\n kind: 'user' | 'worker';\n /** True only for turns a signed-in person started over HTTP (see {@link FACTORY_INTERACTIVE_KEY}). */\n interactive: boolean;\n /** The caller's name and avatar, stamped on audit events so the board can show who acted. */\n actorProfile?: AuditActorProfileInput;\n /** Set for a side session: the user session and thread that started it. */\n origin?: { sessionId: string; threadId: string };\n}\n\nexport interface ResolveFactoryToolScopeOptions {\n requestContext: RequestContext;\n /** `routeAuth.enabled()`; when false every caller is the shared `local` tenant. */\n authEnabled: boolean;\n sessions: Pick<SourceControlSessionLookup, 'getBySessionId'>;\n projects: Pick<FactoryProjectsStorage, 'get'>;\n workItems: Pick<WorkItemsStorage, 'hasRunBindingForSession'>;\n}\n\n/**\n * Resolve the Factory a session's agent may act in. The scope comes from the\n * stored source-control session row and the authenticated caller, never from\n * the `factoryProjectId` thread tag alone: the browser sets that tag and\n * nothing clears it.\n */\nexport async function resolveFactoryToolScope({\n requestContext,\n authEnabled,\n sessions,\n projects,\n workItems,\n}: ResolveFactoryToolScopeOptions): Promise<FactoryToolScope | null> {\n if (typeof requestContext?.get !== 'function') return null;\n const controller = requestContext.get('controller') as AgentControllerRequestContext<unknown> | undefined;\n const resourceId = controller?.resourceId;\n const threadId = controller?.threadId;\n if (!resourceId || !threadId) return null;\n\n let caller: { orgId: string; userId: string; actorProfile?: AuditActorProfileInput } | null = null;\n if (authEnabled) {\n const slackSender = requestContext.get(FACTORY_SLACK_SENDER_KEY) as { userId: string; orgId: string } | undefined;\n const user = getFactoryAuthUserFromContext(requestContext);\n if (slackSender && factoryUserOrgId(user) !== slackSender.orgId) return null;\n const userId = slackSender?.userId ?? getFactoryAuthUserId(user);\n const orgId = slackSender?.orgId ?? factoryUserOrgId(user);\n const actorProfile = slackSender ? undefined : auditActorProfile(user);\n if (userId && orgId) caller = { orgId, userId, ...(actorProfile ? { actorProfile } : {}) };\n } else {\n caller = { orgId: 'local', userId: 'local' };\n }\n\n const row = await sessions.getBySessionId(resourceId);\n if (!row?.factoryProjectId) return null;\n // A caller from another org never acts here, worker or not.\n if (caller && row.orgId !== caller.orgId) return null;\n\n // A side session works for the session that started it. Otherwise any\n // binding, active or revoked, on any thread: a session that ever ran a role stays a worker.\n const origin =\n row.originSessionId && row.originThreadId\n ? { sessionId: row.originSessionId, threadId: row.originThreadId }\n : undefined;\n const worker =\n origin !== undefined ||\n (await workItems.hasRunBindingForSession({\n orgId: row.orgId,\n factoryProjectId: row.factoryProjectId,\n sessionId: row.sessionId,\n }));\n\n // A worker acts as its session row: dispatcher-driven runs carry no signed-in user.\n // A user session acts as the signed-in caller, who must be able to see it.\n let identity: { orgId: string; userId: string; actorProfile?: AuditActorProfileInput };\n if (worker) {\n identity = { orgId: row.orgId, userId: row.userId };\n } else {\n if (!caller) return null;\n if (row.visibility === 'private' && row.userId !== caller.userId) return null;\n identity = caller;\n }\n\n const project = await projects.get({ orgId: identity.orgId, id: row.factoryProjectId });\n if (!project) return null;\n\n return {\n orgId: identity.orgId,\n factoryProjectId: row.factoryProjectId,\n userId: identity.userId,\n sessionId: row.sessionId,\n resourceId,\n threadId,\n kind: worker ? 'worker' : 'user',\n interactive: requestContext.get(FACTORY_INTERACTIVE_KEY) === true,\n ...(identity.actorProfile ? { actorProfile: identity.actorProfile } : {}),\n ...(origin ? { origin } : {}),\n };\n}\n"],"mappings":";;;;AAkBA,MAAa,2BAA2B;;;;;;;AAqCxC,eAAsB,wBAAwB,EAC5C,gBACA,aACA,UACA,UACA,aACmE;CACnE,IAAI,OAAO,gBAAgB,QAAQ,YAAY,OAAO;CACtD,MAAM,aAAa,eAAe,IAAI,YAAY;CAClD,MAAM,aAAa,YAAY;CAC/B,MAAM,WAAW,YAAY;CAC7B,IAAI,CAAC,cAAc,CAAC,UAAU,OAAO;CAErC,IAAI,SAA0F;CAC9F,IAAI,aAAa;EACf,MAAM,cAAc,eAAe,IAAI,wBAAwB;EAC/D,MAAM,OAAO,8BAA8B,cAAc;EACzD,IAAI,eAAe,iBAAiB,IAAI,MAAM,YAAY,OAAO,OAAO;EACxE,MAAM,SAAS,aAAa,UAAU,qBAAqB,IAAI;EAC/D,MAAM,QAAQ,aAAa,SAAS,iBAAiB,IAAI;EACzD,MAAM,eAAe,cAAc,KAAA,IAAY,kBAAkB,IAAI;EACrE,IAAI,UAAU,OAAO,SAAS;GAAE;GAAO;GAAQ,GAAI,eAAe,EAAE,aAAa,IAAI,CAAC;EAAG;CAC3F,OACE,SAAS;EAAE,OAAO;EAAS,QAAQ;CAAQ;CAG7C,MAAM,MAAM,MAAM,SAAS,eAAe,UAAU;CACpD,IAAI,CAAC,KAAK,kBAAkB,OAAO;CAEnC,IAAI,UAAU,IAAI,UAAU,OAAO,OAAO,OAAO;CAIjD,MAAM,SACJ,IAAI,mBAAmB,IAAI,iBACvB;EAAE,WAAW,IAAI;EAAiB,UAAU,IAAI;CAAe,IAC/D,KAAA;CACN,MAAM,SACJ,WAAW,KAAA,KACV,MAAM,UAAU,wBAAwB;EACvC,OAAO,IAAI;EACX,kBAAkB,IAAI;EACtB,WAAW,IAAI;CACjB,CAAC;CAIH,IAAI;CACJ,IAAI,QACF,WAAW;EAAE,OAAO,IAAI;EAAO,QAAQ,IAAI;CAAO;MAC7C;EACL,IAAI,CAAC,QAAQ,OAAO;EACpB,IAAI,IAAI,eAAe,aAAa,IAAI,WAAW,OAAO,QAAQ,OAAO;EACzE,WAAW;CACb;CAGA,IAAI,CAAC,MADiB,SAAS,IAAI;EAAE,OAAO,SAAS;EAAO,IAAI,IAAI;CAAiB,CAAC,GACxE,OAAO;CAErB,OAAO;EACL,OAAO,SAAS;EAChB,kBAAkB,IAAI;EACtB,QAAQ,SAAS;EACjB,WAAW,IAAI;EACf;EACA;EACA,MAAM,SAAS,WAAW;EAC1B,aAAa,eAAe,IAAI,uBAAuB,MAAM;EAC7D,GAAI,SAAS,eAAe,EAAE,cAAc,SAAS,aAAa,IAAI,CAAC;EACvE,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;CAC7B;AACF"}
1
+ {"version":3,"file":"scope.js","names":[],"sources":["../../src/tools/scope.ts"],"sourcesContent":["import type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\n\nimport {\n FACTORY_INTERACTIVE_KEY,\n factoryUserOrgId,\n getFactoryAuthUserFromContext,\n getFactoryAuthUserId,\n} from '../auth.js';\nimport type { SourceControlSessionLookup } from '../session/factory-session.js';\nimport { auditActorProfile } from '../storage/domains/audit/base.js';\nimport type { AuditActorProfileInput } from '../storage/domains/audit/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\n\nexport { FACTORY_INTERACTIVE_KEY };\n\n/** Linked Slack sender, separate from the session owner's model-credential identity. */\nexport const FACTORY_SLACK_SENDER_KEY = 'mastra__factorySlackSender';\n\n/** Who is calling Factory tools, and from which session. Everything a tool needs to stay in scope. */\nexport interface FactoryToolScope {\n orgId: string;\n factoryProjectId: string;\n /** The signed-in caller for a user session; the session row's owner for a worker. */\n userId: string;\n /** The source-control session id; equal to the controller resourceId. */\n sessionId: string;\n resourceId: string;\n threadId: string;\n /** `worker` sessions are bound to a work item run; only `user` sessions manage the Factory. */\n kind: 'user' | 'worker';\n /** True only for turns a signed-in person started over HTTP (see {@link FACTORY_INTERACTIVE_KEY}). */\n interactive: boolean;\n /** The caller's name and avatar, stamped on audit events so the board can show who acted. */\n actorProfile?: AuditActorProfileInput;\n /** Set for a side session: the user session and thread that started it. */\n origin?: { sessionId: string; threadId: string };\n}\n\nexport interface ResolveFactoryToolScopeOptions {\n requestContext: RequestContext;\n /** `routeAuth.enabled()`; when false every caller is the shared `local` tenant. */\n authEnabled: boolean;\n sessions: Pick<SourceControlSessionLookup, 'getBySessionId'>;\n projects: Pick<FactoryProjectsStorage, 'get'>;\n workItems: Pick<WorkItemsStorage, 'hasRunBindingForSession'>;\n}\n\n/**\n * Resolve the Factory a session's agent may act in. The scope comes from the\n * stored source-control session row and the authenticated caller, never from\n * the `factoryProjectId` thread tag alone: the browser sets that tag and\n * nothing clears it.\n */\nexport async function resolveFactoryToolScope({\n requestContext,\n authEnabled,\n sessions,\n projects,\n workItems,\n}: ResolveFactoryToolScopeOptions): Promise<FactoryToolScope | null> {\n if (typeof requestContext?.get !== 'function') return null;\n const controller = requestContext.get('controller') as AgentControllerRequestContext<unknown> | undefined;\n const resourceId = controller?.resourceId;\n const threadId = controller?.threadId;\n if (!resourceId || !threadId) return null;\n\n let caller: { orgId: string; userId: string; actorProfile?: AuditActorProfileInput } | null = null;\n if (authEnabled) {\n const slackSender = requestContext.get(FACTORY_SLACK_SENDER_KEY) as { userId: string; orgId: string } | undefined;\n const user = getFactoryAuthUserFromContext(requestContext);\n if (slackSender && factoryUserOrgId(user) !== slackSender.orgId) return null;\n const userId = slackSender?.userId ?? getFactoryAuthUserId(user);\n const orgId = slackSender?.orgId ?? factoryUserOrgId(user);\n const actorProfile = slackSender ? undefined : auditActorProfile(user);\n if (userId && orgId) caller = { orgId, userId, ...(actorProfile ? { actorProfile } : {}) };\n } else {\n caller = { orgId: 'local', userId: 'local' };\n }\n\n const row = await sessions.getBySessionId(resourceId);\n if (!row?.factoryProjectId) return null;\n // A caller from another org never acts here, worker or not.\n if (caller && row.orgId !== caller.orgId) return null;\n\n // A side session works for the session that started it. Otherwise any\n // binding, active or revoked, on any thread: a session that ever ran a role stays a worker.\n const origin =\n row.originSessionId && row.originThreadId\n ? { sessionId: row.originSessionId, threadId: row.originThreadId }\n : undefined;\n const worker =\n origin !== undefined ||\n (await workItems.hasRunBindingForSession({\n orgId: row.orgId,\n factoryProjectId: row.factoryProjectId,\n sessionId: row.sessionId,\n }));\n\n // A worker acts as its session row: dispatcher-driven runs carry no signed-in user.\n // A user session acts as the signed-in caller, who must be able to see it.\n let identity: { orgId: string; userId: string; actorProfile?: AuditActorProfileInput };\n if (worker) {\n identity = { orgId: row.orgId, userId: row.userId };\n } else {\n if (!caller) return null;\n if (row.visibility === 'private' && row.userId !== caller.userId) return null;\n identity = caller;\n }\n\n const project = await projects.get({ orgId: identity.orgId, id: row.factoryProjectId });\n if (!project) return null;\n\n return {\n orgId: identity.orgId,\n factoryProjectId: row.factoryProjectId,\n userId: identity.userId,\n sessionId: row.sessionId,\n resourceId,\n threadId,\n kind: worker ? 'worker' : 'user',\n interactive: requestContext.get(FACTORY_INTERACTIVE_KEY) === true,\n ...(identity.actorProfile ? { actorProfile: identity.actorProfile } : {}),\n ...(origin ? { origin } : {}),\n };\n}\n\n/**\n * Refuses an action only a signed-in person may take. Turns nobody signed in\n * started (notification wakes, webhooks, dispatcher notices) carry text other\n * people wrote, so they can't approve, resolve proposals, or waive plan approval.\n */\nexport function requirePerson(scope: FactoryToolScope, action: string): void {\n if (scope.interactive) return;\n throw new Error(\n `Only a person can ${action}. Nobody signed in started this turn (it came from a notification, webhook or rule); ask the person to do it from the board or by messaging this session.`,\n );\n}\n"],"mappings":";;;;AAkBA,MAAa,2BAA2B;;;;;;;AAqCxC,eAAsB,wBAAwB,EAC5C,gBACA,aACA,UACA,UACA,aACmE;CACnE,IAAI,OAAO,gBAAgB,QAAQ,YAAY,OAAO;CACtD,MAAM,aAAa,eAAe,IAAI,YAAY;CAClD,MAAM,aAAa,YAAY;CAC/B,MAAM,WAAW,YAAY;CAC7B,IAAI,CAAC,cAAc,CAAC,UAAU,OAAO;CAErC,IAAI,SAA0F;CAC9F,IAAI,aAAa;EACf,MAAM,cAAc,eAAe,IAAI,wBAAwB;EAC/D,MAAM,OAAO,8BAA8B,cAAc;EACzD,IAAI,eAAe,iBAAiB,IAAI,MAAM,YAAY,OAAO,OAAO;EACxE,MAAM,SAAS,aAAa,UAAU,qBAAqB,IAAI;EAC/D,MAAM,QAAQ,aAAa,SAAS,iBAAiB,IAAI;EACzD,MAAM,eAAe,cAAc,KAAA,IAAY,kBAAkB,IAAI;EACrE,IAAI,UAAU,OAAO,SAAS;GAAE;GAAO;GAAQ,GAAI,eAAe,EAAE,aAAa,IAAI,CAAC;EAAG;CAC3F,OACE,SAAS;EAAE,OAAO;EAAS,QAAQ;CAAQ;CAG7C,MAAM,MAAM,MAAM,SAAS,eAAe,UAAU;CACpD,IAAI,CAAC,KAAK,kBAAkB,OAAO;CAEnC,IAAI,UAAU,IAAI,UAAU,OAAO,OAAO,OAAO;CAIjD,MAAM,SACJ,IAAI,mBAAmB,IAAI,iBACvB;EAAE,WAAW,IAAI;EAAiB,UAAU,IAAI;CAAe,IAC/D,KAAA;CACN,MAAM,SACJ,WAAW,KAAA,KACV,MAAM,UAAU,wBAAwB;EACvC,OAAO,IAAI;EACX,kBAAkB,IAAI;EACtB,WAAW,IAAI;CACjB,CAAC;CAIH,IAAI;CACJ,IAAI,QACF,WAAW;EAAE,OAAO,IAAI;EAAO,QAAQ,IAAI;CAAO;MAC7C;EACL,IAAI,CAAC,QAAQ,OAAO;EACpB,IAAI,IAAI,eAAe,aAAa,IAAI,WAAW,OAAO,QAAQ,OAAO;EACzE,WAAW;CACb;CAGA,IAAI,CAAC,MADiB,SAAS,IAAI;EAAE,OAAO,SAAS;EAAO,IAAI,IAAI;CAAiB,CAAC,GACxE,OAAO;CAErB,OAAO;EACL,OAAO,SAAS;EAChB,kBAAkB,IAAI;EACtB,QAAQ,SAAS;EACjB,WAAW,IAAI;EACf;EACA;EACA,MAAM,SAAS,WAAW;EAC1B,aAAa,eAAe,IAAI,uBAAuB,MAAM;EAC7D,GAAI,SAAS,eAAe,EAAE,cAAc,SAAS,aAAa,IAAI,CAAC;EACvE,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;CAC7B;AACF;;;;;;AAOA,SAAgB,cAAc,OAAyB,QAAsB;CAC3E,IAAI,MAAM,aAAa;CACvB,MAAM,IAAI,MACR,qBAAqB,OAAO,0JAC9B;AACF"}
@@ -38,6 +38,6 @@ export interface FactorySessionToolDependencies {
38
38
  /** The browser Start's coordinator; absent until the Factory runtime is ready. */
39
39
  startCoordinator: () => Pick<FactoryStartCoordinator, 'prepare'> | undefined;
40
40
  }
41
- /** Tools that start sessions on the caller's behalf. Interactive user turns only. */
41
+ /** Tools that start sessions on the caller's behalf, or switch their models. User sessions only. */
42
42
  export declare function createFactorySessionTools(scope: FactoryToolScope, deps: FactorySessionToolDependencies): IntegrationTools;
43
43
  //# sourceMappingURL=session-tools.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-tools.d.ts","sourceRoot":"","sources":["../../src/tools/session-tools.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAKhE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAE7E,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAQhF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AACtF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6CAA6C,CAAC;AAC1F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEtF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAElF,OAAO,KAAK,EAAe,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE3F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAInD,sGAAsG;AACtG,eAAO,MAAM,sBAAsB,QAAoD,CAAC;AAExF,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,EAAE,IAAI,CAAC,0BAA0B,EAAE,gBAAgB,GAAG,6BAA6B,CAAC,CAAC;IAC7F,YAAY,EAAE;QACZ,QAAQ,EAAE,sBAAsB,CAAC;QACjC,aAAa,EAAE,oBAAoB,CAAC;QACpC;;;;WAIG;QACH,aAAa,CAAC,EAAE;YACd,WAAW,EAAE,IAAI,CAAC,uBAAuB,EAAE,iBAAiB,CAAC,CAAC;YAC9D,eAAe,CAAC,EAAE,IAAI,CAAC,sBAAsB,EAAE,aAAa,GAAG,MAAM,CAAC,CAAC;YACvE,mBAAmB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;SAC3C,CAAC;KACH,CAAC;IACF,wEAAwE;IACxE,SAAS,EAAE,MAAM,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IAClD,SAAS,EAAE,IAAI,CACb,gBAAgB,EACd,MAAM,GACN,eAAe,GACf,iBAAiB,GACjB,gCAAgC,GAChC,wBAAwB,GACxB,qBAAqB,GACrB,yBAAyB,GACzB,0BAA0B,GAC1B,yBAAyB,CAC5B,CAAC;IACF,MAAM,EAAE,aAAa,CAAC;IACtB,aAAa,EAAE,aAAa,CAAC;IAC7B,MAAM,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;IAC1C,kFAAkF;IAClF,gBAAgB,EAAE,MAAM,IAAI,CAAC,uBAAuB,EAAE,SAAS,CAAC,GAAG,SAAS,CAAC;CAC9E;AA0BD,qFAAqF;AACrF,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,8BAA8B,GACnC,gBAAgB,CAmXlB"}
1
+ {"version":3,"file":"session-tools.d.ts","sourceRoot":"","sources":["../../src/tools/session-tools.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAKhE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAE7E,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAQhF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AACtF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6CAA6C,CAAC;AAC1F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEtF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAElF,OAAO,KAAK,EAAe,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE3F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAInD,sGAAsG;AACtG,eAAO,MAAM,sBAAsB,QAAoD,CAAC;AAExF,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,EAAE,IAAI,CAAC,0BAA0B,EAAE,gBAAgB,GAAG,6BAA6B,CAAC,CAAC;IAC7F,YAAY,EAAE;QACZ,QAAQ,EAAE,sBAAsB,CAAC;QACjC,aAAa,EAAE,oBAAoB,CAAC;QACpC;;;;WAIG;QACH,aAAa,CAAC,EAAE;YACd,WAAW,EAAE,IAAI,CAAC,uBAAuB,EAAE,iBAAiB,CAAC,CAAC;YAC9D,eAAe,CAAC,EAAE,IAAI,CAAC,sBAAsB,EAAE,aAAa,GAAG,MAAM,CAAC,CAAC;YACvE,mBAAmB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;SAC3C,CAAC;KACH,CAAC;IACF,wEAAwE;IACxE,SAAS,EAAE,MAAM,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IAClD,SAAS,EAAE,IAAI,CACb,gBAAgB,EACd,MAAM,GACN,eAAe,GACf,iBAAiB,GACjB,gCAAgC,GAChC,wBAAwB,GACxB,qBAAqB,GACrB,yBAAyB,GACzB,0BAA0B,GAC1B,yBAAyB,CAC5B,CAAC;IACF,MAAM,EAAE,aAAa,CAAC;IACtB,aAAa,EAAE,aAAa,CAAC;IAC7B,MAAM,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;IAC1C,kFAAkF;IAClF,gBAAgB,EAAE,MAAM,IAAI,CAAC,uBAAuB,EAAE,SAAS,CAAC,GAAG,SAAS,CAAC;CAC9E;AA0BD,oGAAoG;AACpG,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,8BAA8B,GACnC,gBAAgB,CAiYlB"}
@@ -35,7 +35,7 @@ function workItemLabel(item) {
35
35
  function titleFrom(prompt) {
36
36
  return (prompt.split("\n").find((line) => line.trim()) ?? prompt).trim().slice(0, TITLE_LENGTH);
37
37
  }
38
- /** Tools that start sessions on the caller's behalf. Interactive user turns only. */
38
+ /** Tools that start sessions on the caller's behalf, or switch their models. User sessions only. */
39
39
  function createFactorySessionTools(scope, deps) {
40
40
  /** The models a session can switch to, resolved from the target session's owner and project policy. */
41
41
  async function sessionModelCatalog(controller, sessionId, threadId) {
@@ -96,6 +96,7 @@ function createFactorySessionTools(scope, deps) {
96
96
  execute: async ({ sessionId, modelId }) => {
97
97
  const row = await deps.sessions.getBySessionId(sessionId);
98
98
  if (!row || row.orgId !== scope.orgId || row.factoryProjectId !== scope.factoryProjectId || row.userId !== scope.userId) throw new Error("Session not found or not owned by you in this Factory.");
99
+ if (!scope.interactive && sessionId !== scope.sessionId && row.originSessionId !== scope.sessionId) throw new Error("Without a person in this turn, only this session and the side sessions it started can switch models.");
99
100
  const messaging = await deps.messaging();
100
101
  if (!messaging) throw new Error("Session model switching is not available on this server.");
101
102
  const threadId = await resolveSessionThread({
@@ -141,12 +142,15 @@ function createFactorySessionTools(scope, deps) {
141
142
  path: ["role"]
142
143
  }),
143
144
  execute: async ({ prompt, title, workItem, role }) => {
144
- if (workItem !== void 0) return startWorkItemRole({
145
- prompt,
146
- title,
147
- workItem,
148
- role
149
- });
145
+ if (workItem !== void 0) {
146
+ if (!scope.interactive) throw new Error("Starting a role on a work item needs a person in this turn; start a side session, or ask the person to use the board.");
147
+ return startWorkItemRole({
148
+ prompt,
149
+ title,
150
+ workItem,
151
+ role
152
+ });
153
+ }
150
154
  const parent = await deps.sessions.getBySessionId(scope.sessionId);
151
155
  const sourceControl = await deps.sessions.getSourceControlBySessionId(scope.sessionId);
152
156
  if (!parent || !sourceControl) throw new Error("This session has no Factory session row to start from.");