@mastra/factory 0.25.0-alpha.5 → 0.25.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/boards/work-transition-policy.d.ts.map +1 -1
- package/dist/boards/work-transition-policy.js +3 -3
- package/dist/boards/work-transition-policy.js.map +1 -1
- package/dist/default-thinking-level.d.ts +3 -0
- package/dist/default-thinking-level.d.ts.map +1 -0
- package/dist/default-thinking-level.js +5 -0
- package/dist/default-thinking-level.js.map +1 -0
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +19 -5
- package/dist/factory.js.map +1 -1
- package/dist/integrations/gitlab/integration.d.ts +4 -0
- package/dist/integrations/gitlab/integration.d.ts.map +1 -1
- package/dist/integrations/gitlab/integration.js +10 -1
- package/dist/integrations/gitlab/integration.js.map +1 -1
- package/dist/integrations/gitlab/routes.d.ts +6 -0
- package/dist/integrations/gitlab/routes.d.ts.map +1 -1
- package/dist/integrations/gitlab/routes.js +153 -1
- package/dist/integrations/gitlab/routes.js.map +1 -1
- package/dist/integrations/gitlab/rules.d.ts.map +1 -1
- package/dist/integrations/gitlab/rules.js +4 -1
- package/dist/integrations/gitlab/rules.js.map +1 -1
- package/dist/integrations/jira/api.d.ts +1 -1
- package/dist/integrations/jira/api.d.ts.map +1 -1
- package/dist/integrations/jira/api.js +1 -1
- package/dist/integrations/jira/api.js.map +1 -1
- package/dist/integrations/jira/integration.d.ts +3 -1
- package/dist/integrations/jira/integration.d.ts.map +1 -1
- package/dist/integrations/jira/integration.js +17 -2
- package/dist/integrations/jira/integration.js.map +1 -1
- package/dist/integrations/pull-request-head.d.ts +1 -0
- package/dist/integrations/pull-request-head.d.ts.map +1 -1
- package/dist/integrations/pull-request-head.js +2 -1
- package/dist/integrations/pull-request-head.js.map +1 -1
- package/dist/integrations/slack/channel-reply-processor.d.ts +1 -0
- package/dist/integrations/slack/channel-reply-processor.d.ts.map +1 -1
- package/dist/integrations/slack/channel-reply-processor.js +11 -3
- package/dist/integrations/slack/channel-reply-processor.js.map +1 -1
- package/dist/integrations/slack/channel-session-link-processor.d.ts +1 -0
- package/dist/integrations/slack/channel-session-link-processor.d.ts.map +1 -1
- package/dist/integrations/slack/channel-session-link-processor.js +1 -1
- package/dist/integrations/slack/channel-session-link-processor.js.map +1 -1
- package/dist/integrations/slack/reply-tool.d.ts.map +1 -1
- package/dist/integrations/slack/reply-tool.js +3 -11
- package/dist/integrations/slack/reply-tool.js.map +1 -1
- package/dist/integrations/slack/run-error-card.d.ts +3 -0
- package/dist/integrations/slack/run-error-card.d.ts.map +1 -0
- package/dist/integrations/slack/run-error-card.js +44 -0
- package/dist/integrations/slack/run-error-card.js.map +1 -0
- package/dist/integrations/slack/slack.d.ts.map +1 -1
- package/dist/integrations/slack/slack.js +5 -1
- package/dist/integrations/slack/slack.js.map +1 -1
- package/dist/links.d.ts +24 -0
- package/dist/links.d.ts.map +1 -0
- package/dist/links.js +40 -0
- package/dist/links.js.map +1 -0
- package/dist/routes/config.d.ts +3 -0
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +22 -1
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/intake.js +5 -2
- package/dist/routes/intake.js.map +1 -1
- package/dist/routes/surface.js +1 -1
- package/dist/routes/surface.js.map +1 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +8 -1
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/run-error.d.ts +13 -0
- package/dist/run-error.d.ts.map +1 -0
- package/dist/run-error.js +81 -0
- package/dist/run-error.js.map +1 -0
- package/dist/session/thinking-default.d.ts +1 -1
- package/dist/session/thinking-default.d.ts.map +1 -1
- package/dist/session/thinking-default.js +10 -11
- package/dist/session/thinking-default.js.map +1 -1
- package/dist/storage/domains/intake/base.d.ts +5 -0
- package/dist/storage/domains/intake/base.d.ts.map +1 -1
- package/dist/storage/domains/intake/base.js +9 -0
- package/dist/storage/domains/intake/base.js.map +1 -1
- package/dist/storage/domains/projects/base.d.ts +0 -1
- package/dist/storage/domains/projects/base.d.ts.map +1 -1
- package/dist/storage/domains/projects/base.js +4 -4
- package/dist/storage/domains/projects/base.js.map +1 -1
- package/dist/tools/read-tools.d.ts.map +1 -1
- package/dist/tools/read-tools.js +18 -2
- package/dist/tools/read-tools.js.map +1 -1
- package/dist/tools/run-watch.d.ts +0 -2
- package/dist/tools/run-watch.d.ts.map +1 -1
- package/dist/tools/run-watch.js +3 -42
- package/dist/tools/run-watch.js.map +1 -1
- package/dist/tools/scope.js +1 -1
- package/dist/tools/scope.js.map +1 -1
- package/dist/tools/session-tools.d.ts.map +1 -1
- package/dist/tools/session-tools.js +21 -5
- package/dist/tools/session-tools.js.map +1 -1
- package/dist/tools/work-item-tools.d.ts.map +1 -1
- package/dist/tools/work-item-tools.js +7 -1
- package/dist/tools/work-item-tools.js.map +1 -1
- package/factory-skills/factory-gitlab-review/SKILL.md +1 -1
- package/factory-skills/factory-plan/SKILL.md +1 -1
- package/factory-skills/factory-rereview/SKILL.md +2 -1
- package/factory-skills/factory-review/SKILL.md +2 -1
- package/factory-skills/factory-self-review/SKILL.md +1 -1
- package/factory-skills/factory-triage/SKILL.md +46 -23
- package/package.json +12 -12
|
@@ -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;
|
|
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;AAIhE,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;AA8ED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,2BAA2B,GAAG,gBAAgB,CA6NnH"}
|
package/dist/tools/read-tools.js
CHANGED
|
@@ -2,6 +2,7 @@ import { boardForWorkItem } from "../boards/semantics.js";
|
|
|
2
2
|
import "../boards/index.js";
|
|
3
3
|
import { factoryDispatchFailureMetadata } from "../rules/dispatch-errors.js";
|
|
4
4
|
import { sessionActivity } from "./deliver.js";
|
|
5
|
+
import { factorySessionUrl, factoryWorkItemUrl } from "../links.js";
|
|
5
6
|
import { decisionRetryable, overtakenDecisionIds } from "../rules/decision-applicability.js";
|
|
6
7
|
import { z } from "zod";
|
|
7
8
|
import { createTool } from "@mastra/core/tools";
|
|
@@ -46,6 +47,14 @@ function activeWorker(bindings, itemId) {
|
|
|
46
47
|
sessionId: binding.sessionId
|
|
47
48
|
} : null;
|
|
48
49
|
}
|
|
50
|
+
/** The card on its Factory board; null when the server has no public URL configured. */
|
|
51
|
+
function workItemUrl(item) {
|
|
52
|
+
return factoryWorkItemUrl({
|
|
53
|
+
factoryProjectId: item.factoryProjectId,
|
|
54
|
+
board: boardForWorkItem(item),
|
|
55
|
+
workItemId: item.id
|
|
56
|
+
}) ?? null;
|
|
57
|
+
}
|
|
49
58
|
function summarizeItem(item, bindings) {
|
|
50
59
|
return {
|
|
51
60
|
id: item.id,
|
|
@@ -53,6 +62,7 @@ function summarizeItem(item, bindings) {
|
|
|
53
62
|
title: truncate(item.title, 600),
|
|
54
63
|
board: boardForWorkItem(item),
|
|
55
64
|
stage: workItemStage(item),
|
|
65
|
+
url: workItemUrl(item),
|
|
56
66
|
activeWorker: activeWorker(bindings, item.id),
|
|
57
67
|
requestedBy: item.createdBy,
|
|
58
68
|
updatedAt: iso(item.updatedAt)
|
|
@@ -176,6 +186,11 @@ function createFactoryReadTools(scope, deps) {
|
|
|
176
186
|
role,
|
|
177
187
|
sessionId: ref.sessionId,
|
|
178
188
|
threadId: ref.threadId,
|
|
189
|
+
url: factorySessionUrl({
|
|
190
|
+
factoryProjectId: item.factoryProjectId,
|
|
191
|
+
sessionId: ref.sessionId,
|
|
192
|
+
threadId: ref.threadId
|
|
193
|
+
}) ?? null,
|
|
179
194
|
startedBy: ref.startedBy,
|
|
180
195
|
active: bindings.some((binding) => binding.role === role && binding.sessionId === ref.sessionId && binding.status === "active"),
|
|
181
196
|
...await activity(ref.sessionId, ref.threadId) ?? {
|
|
@@ -187,7 +202,7 @@ function createFactoryReadTools(scope, deps) {
|
|
|
187
202
|
item: {
|
|
188
203
|
...summarizeItem(item, bindings),
|
|
189
204
|
source: item.externalSource ? `${item.externalSource.integrationId}:${item.externalSource.type}` : "manual",
|
|
190
|
-
|
|
205
|
+
sourceUrl: typeof item.metadata?.url === "string" ? item.metadata.url : null,
|
|
191
206
|
triageType: item.triageType,
|
|
192
207
|
revision: item.revision
|
|
193
208
|
},
|
|
@@ -248,7 +263,8 @@ function createFactoryReadTools(scope, deps) {
|
|
|
248
263
|
return {
|
|
249
264
|
workItemId,
|
|
250
265
|
number: item ? workItemNumber(item) : null,
|
|
251
|
-
title: item ? truncate(item.title, 600) : null
|
|
266
|
+
title: item ? truncate(item.title, 600) : null,
|
|
267
|
+
url: item ? workItemUrl(item) : null
|
|
252
268
|
};
|
|
253
269
|
};
|
|
254
270
|
const allIncidents = [...groups.values()].sort((a, b) => b.decisions.length - a.decisions.length);
|
|
@@ -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 { 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
|
+
{"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 { factorySessionUrl, factoryWorkItemUrl } from '../links.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\n/** The card on its Factory board; null when the server has no public URL configured. */\nfunction workItemUrl(item: WorkItemRow) {\n return (\n factoryWorkItemUrl({\n factoryProjectId: item.factoryProjectId,\n board: boardForWorkItem(item),\n workItemId: item.id,\n }) ?? null\n );\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 url: workItemUrl(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 url:\n factorySessionUrl({\n factoryProjectId: item.factoryProjectId,\n sessionId: ref.sessionId,\n threadId: ref.threadId,\n }) ?? null,\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 sourceUrl: 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 url: item ? workItemUrl(item) : 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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"}
|
|
@@ -21,8 +21,6 @@ export type SessionApprovalReport = (request: {
|
|
|
21
21
|
toolCallId: string;
|
|
22
22
|
args: unknown;
|
|
23
23
|
}) => Promise<void>;
|
|
24
|
-
/** The provider's own usage-limit text, when the error chain carries one. Status text alone is not enough. */
|
|
25
|
-
export declare function usageLimitReason(error: unknown): string | undefined;
|
|
26
24
|
/**
|
|
27
25
|
* Report every run end of a session, however the run started: the board, a
|
|
28
26
|
* typed message, a wake, or a run after a restart. Attached to each Factory
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-watch.d.ts","sourceRoot":"","sources":["../../src/tools/run-watch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"run-watch.d.ts","sourceRoot":"","sources":["../../src/tools/run-watch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAI1E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD,KAAK,YAAY,GAAG,OAAO,CAAC,oBAAoB,EAAE;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;AAEnF,wFAAwF;AACxF,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACxE;AAED,wFAAwF;AACxF,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,EAAE,iBAAiB,EAAE,KAAK,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE7F,sFAAsF;AACtF,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAKxH;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,mBAAmB,EAC3B,EAAE,mBAAmB,EAAE,GAAE;IAAE,mBAAmB,CAAC,EAAE,qBAAqB,CAAA;CAAO,GAC5E,MAAM,IAAI,CAwCZ;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,oBAAoB,EAC7B,SAAS,EAAE,MAAM,GAChB;IAAE,SAAS,EAAE,MAAM,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,IAAI,CAAA;CAAE,CAqBrD;AAED,yFAAyF;AACzF,wBAAgB,UAAU,CACxB,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,CAAC,GAAG,SAAS,EAC7D,YAAY,CAAC,EAAE,MAAM,GACpB,iBAAiB,GAAG,IAAI,CAqB1B"}
|
package/dist/tools/run-watch.js
CHANGED
|
@@ -1,48 +1,9 @@
|
|
|
1
1
|
import { FACTORY_OPEN_RUN_STALE_MS, heartbeatSessionOpenRun } from "../session/run-audit.js";
|
|
2
|
+
import { toPersistedErrorIdentity } from "@mastra/core/error";
|
|
2
3
|
// src/tools/run-watch.ts
|
|
3
4
|
/** How much of a run's error message a `failed` report quotes. */
|
|
4
5
|
const ERROR_MESSAGE_LENGTH = 400;
|
|
5
6
|
/**
|
|
6
|
-
* A usage or quota rejection, not a rate limit or a request-size limit. The
|
|
7
|
-
* wording is the same set account rotation uses, so a provider that reports
|
|
8
|
-
* the cap as a plain 400 still counts.
|
|
9
|
-
*/
|
|
10
|
-
const USAGE_LIMIT_MESSAGE_PATTERN = /usage limit|usage cap|insufficient_quota|quota exceeded|quota exhausted|quota reached|exceeded your current quota|insufficient balance|insufficient credits|credits exhausted|rate limit exceeded|rate limit reached|weekly limit|monthly limit/i;
|
|
11
|
-
const ERROR_CHAIN_DEPTH = 5;
|
|
12
|
-
function isRecord(value) {
|
|
13
|
-
return typeof value === "object" && value !== null;
|
|
14
|
-
}
|
|
15
|
-
/** The provider's own usage-limit text, when the error chain carries one. Status text alone is not enough. */
|
|
16
|
-
function usageLimitReason(error) {
|
|
17
|
-
const visited = /* @__PURE__ */ new Set();
|
|
18
|
-
const found = [];
|
|
19
|
-
const add = (value) => {
|
|
20
|
-
if (typeof value !== "string") return;
|
|
21
|
-
const text = value.trim();
|
|
22
|
-
if (text && USAGE_LIMIT_MESSAGE_PATTERN.test(text)) found.push(text);
|
|
23
|
-
};
|
|
24
|
-
function walk(value, depth) {
|
|
25
|
-
if (depth > ERROR_CHAIN_DEPTH || !isRecord(value) || visited.has(value)) return;
|
|
26
|
-
visited.add(value);
|
|
27
|
-
add(value.message);
|
|
28
|
-
if (typeof value.responseBody === "string") try {
|
|
29
|
-
const body = JSON.parse(value.responseBody);
|
|
30
|
-
if (isRecord(body)) {
|
|
31
|
-
add(body.message);
|
|
32
|
-
add(body.detail);
|
|
33
|
-
if (isRecord(body.error)) add(body.error.message);
|
|
34
|
-
else add(body.error);
|
|
35
|
-
}
|
|
36
|
-
} catch {}
|
|
37
|
-
if (value.cause !== void 0) walk(value.cause, depth + 1);
|
|
38
|
-
if (value.error !== void 0) walk(value.error, depth + 1);
|
|
39
|
-
}
|
|
40
|
-
try {
|
|
41
|
-
walk(error, 0);
|
|
42
|
-
} catch {}
|
|
43
|
-
return (found.find((text) => text !== "Bad Request") ?? found[0])?.slice(0, ERROR_MESSAGE_LENGTH);
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
7
|
* Report every run end of a session, however the run started: the board, a
|
|
47
8
|
* typed message, a wake, or a run after a restart. Attached to each Factory
|
|
48
9
|
* session when it is created, so it outlives any one start. A run someone
|
|
@@ -71,7 +32,7 @@ function observeSessionRunReports(session, report, { onApprovalRequested } = {})
|
|
|
71
32
|
return;
|
|
72
33
|
}
|
|
73
34
|
if (event.type === "error") {
|
|
74
|
-
lastError =
|
|
35
|
+
lastError = toPersistedErrorIdentity(event.error).reason ?? event.error?.message;
|
|
75
36
|
return;
|
|
76
37
|
}
|
|
77
38
|
if (event.type === "tool_suspension_cancelled" && parked?.toolCallId === event.toolCallId) {
|
|
@@ -136,6 +97,6 @@ function runOutcome(reason, parked, errorMessage) {
|
|
|
136
97
|
default: return { status: "done" };
|
|
137
98
|
}
|
|
138
99
|
}
|
|
139
|
-
export { heartbeatOpenRunUntilEnd, observeSessionRunReports, runOutcome
|
|
100
|
+
export { heartbeatOpenRunUntilEnd, observeSessionRunReports, runOutcome };
|
|
140
101
|
|
|
141
102
|
//# sourceMappingURL=run-watch.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-watch.js","names":[],"sources":["../../src/tools/run-watch.ts"],"sourcesContent":["import type { AgentControllerEvent } from '@mastra/core/agent-controller';\n\nimport { FACTORY_OPEN_RUN_STALE_MS, heartbeatSessionOpenRun } from '../session/run-audit.js';\nimport type { RunEndCaptureSession } from '../session/run-audit.js';\nimport type { FactoryRunOutcome } from './reports.js';\n\ntype RunEndReason = Extract<AgentControllerEvent, { type: 'agent_end' }>['reason'];\n\n/** What a run stopped on: a tool waiting for an answer, or one waiting for approval. */\nexport interface ParkedTool {\n toolName: string;\n toolCallId: string;\n approval: boolean;\n}\n\nexport interface RunReportSession {\n subscribe(listener: (event: AgentControllerEvent) => void): () => void;\n}\n\n/** Reports one run's end; `since` is when the run's window opened. Should not throw. */\nexport type SessionRunEndReport = (outcome: FactoryRunOutcome, since: Date) => Promise<void>;\n\n/** Told when the run holds a tool call for approval; the run stays open meanwhile. */\nexport type SessionApprovalReport = (request: { toolName: string; toolCallId: string; args: unknown }) => Promise<void>;\n\n/** How much of a run's error message a `failed` report quotes. */\nconst ERROR_MESSAGE_LENGTH = 400;\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"run-watch.js","names":[],"sources":["../../src/tools/run-watch.ts"],"sourcesContent":["import type { AgentControllerEvent } from '@mastra/core/agent-controller';\nimport { toPersistedErrorIdentity } from '@mastra/core/error';\n\nimport { FACTORY_OPEN_RUN_STALE_MS, heartbeatSessionOpenRun } from '../session/run-audit.js';\nimport type { RunEndCaptureSession } from '../session/run-audit.js';\nimport type { FactoryRunOutcome } from './reports.js';\n\ntype RunEndReason = Extract<AgentControllerEvent, { type: 'agent_end' }>['reason'];\n\n/** What a run stopped on: a tool waiting for an answer, or one waiting for approval. */\nexport interface ParkedTool {\n toolName: string;\n toolCallId: string;\n approval: boolean;\n}\n\nexport interface RunReportSession {\n subscribe(listener: (event: AgentControllerEvent) => void): () => void;\n}\n\n/** Reports one run's end; `since` is when the run's window opened. Should not throw. */\nexport type SessionRunEndReport = (outcome: FactoryRunOutcome, since: Date) => Promise<void>;\n\n/** Told when the run holds a tool call for approval; the run stays open meanwhile. */\nexport type SessionApprovalReport = (request: { toolName: string; toolCallId: string; args: unknown }) => Promise<void>;\n\n/** How much of a run's error message a `failed` report quotes. */\nconst ERROR_MESSAGE_LENGTH = 400;\n\n/**\n * Report every run end of a session, however the run started: the board, a\n * typed message, a wake, or a run after a restart. Attached to each Factory\n * session when it is created, so it outlives any one start. A run someone\n * stopped or steered says nothing, and its window carries into the next run,\n * so reports the agent made in it still count.\n */\nexport function observeSessionRunReports(\n session: RunReportSession,\n report: SessionRunEndReport,\n { onApprovalRequested }: { onApprovalRequested?: SessionApprovalReport } = {},\n): () => void {\n let parked: ParkedTool | undefined;\n let lastError: string | undefined;\n let since = new Date();\n const settle = (work: Promise<void>) =>\n void work.catch(error => {\n console.warn('[Factory reports] Unable to report on a session run.', error);\n });\n return session.subscribe(event => {\n if (event.type === 'tool_suspended' || event.type === 'tool_approval_required') {\n parked = {\n toolName: event.toolName,\n toolCallId: event.toolCallId,\n approval: event.type === 'tool_approval_required',\n };\n // An approval gate holds the run open, so it is announced now, not at the run's end.\n if (event.type === 'tool_approval_required' && onApprovalRequested) {\n settle(onApprovalRequested({ toolName: event.toolName, toolCallId: event.toolCallId, args: event.args }));\n }\n return;\n }\n if (event.type === 'error') {\n lastError = toPersistedErrorIdentity(event.error).reason ?? event.error?.message;\n return;\n }\n if (event.type === 'tool_suspension_cancelled' && parked?.toolCallId === event.toolCallId) {\n parked = undefined;\n return;\n }\n if (event.type !== 'agent_end') return;\n const outcome = runOutcome(event.reason, parked, lastError);\n parked = undefined;\n lastError = undefined;\n if (!outcome) return;\n // Move the window now, so a run that starts before this report settles is judged on its own;\n // reports made up to the end, including its last millisecond, belong to the run that ended.\n const runSince = since;\n since = new Date(Date.now() + 1);\n settle(report(outcome, runSince));\n });\n}\n\n/**\n * Renew a kicked-off run's open-run entry until the run ends, like the\n * dispatcher's heartbeat, so no dispatcher starts the role again meanwhile.\n * `endReason` is how that run ended, once it has; `stop` is for a kickoff that fails.\n */\nexport function heartbeatOpenRunUntilEnd(\n session: RunEndCaptureSession,\n kickoffId: string,\n): { endReason: () => RunEndReason; stop: () => void } {\n let endReason: RunEndReason;\n let unsubscribe = () => {};\n const timer = setInterval(\n () => void heartbeatSessionOpenRun(session, kickoffId),\n Math.floor(FACTORY_OPEN_RUN_STALE_MS / 3),\n );\n timer.unref?.();\n let stopped = false;\n const stop = () => {\n if (stopped) return;\n stopped = true;\n clearInterval(timer);\n unsubscribe();\n };\n unsubscribe = session.subscribe(event => {\n if (event.type !== 'agent_end') return;\n endReason ??= event.reason;\n stop();\n });\n return { endReason: () => endReason, stop };\n}\n\n/** `null` for an aborted run: someone stopped or steered it, which is not an outcome. */\nexport function runOutcome(\n reason: RunEndReason,\n parked: Pick<ParkedTool, 'toolName' | 'approval'> | undefined,\n errorMessage?: string,\n): FactoryRunOutcome | null {\n switch (reason) {\n case 'error': {\n const message = errorMessage?.trim();\n return {\n status: 'failed',\n reason: message\n ? `the run ended in error: ${message.slice(0, ERROR_MESSAGE_LENGTH)}`\n : 'the run ended in error.',\n };\n }\n case 'aborted':\n return null;\n case 'suspended':\n return {\n status: 'blocked',\n ...(parked ? { toolName: parked.toolName, ...(parked.approval ? { approval: true } : {}) } : {}),\n };\n default:\n return { status: 'done' };\n }\n}\n"],"mappings":";;;;AA2BA,MAAM,uBAAuB;;;;;;;;AAS7B,SAAgB,yBACd,SACA,QACA,EAAE,wBAAyE,CAAC,GAChE;CACZ,IAAI;CACJ,IAAI;CACJ,IAAI,wBAAQ,IAAI,KAAK;CACrB,MAAM,UAAU,SACd,KAAK,KAAK,OAAM,UAAS;EACvB,QAAQ,KAAK,wDAAwD,KAAK;CAC5E,CAAC;CACH,OAAO,QAAQ,WAAU,UAAS;EAChC,IAAI,MAAM,SAAS,oBAAoB,MAAM,SAAS,0BAA0B;GAC9E,SAAS;IACP,UAAU,MAAM;IAChB,YAAY,MAAM;IAClB,UAAU,MAAM,SAAS;GAC3B;GAEA,IAAI,MAAM,SAAS,4BAA4B,qBAC7C,OAAO,oBAAoB;IAAE,UAAU,MAAM;IAAU,YAAY,MAAM;IAAY,MAAM,MAAM;GAAK,CAAC,CAAC;GAE1G;EACF;EACA,IAAI,MAAM,SAAS,SAAS;GAC1B,YAAY,yBAAyB,MAAM,KAAK,CAAC,CAAC,UAAU,MAAM,OAAO;GACzE;EACF;EACA,IAAI,MAAM,SAAS,+BAA+B,QAAQ,eAAe,MAAM,YAAY;GACzF,SAAS,KAAA;GACT;EACF;EACA,IAAI,MAAM,SAAS,aAAa;EAChC,MAAM,UAAU,WAAW,MAAM,QAAQ,QAAQ,SAAS;EAC1D,SAAS,KAAA;EACT,YAAY,KAAA;EACZ,IAAI,CAAC,SAAS;EAGd,MAAM,WAAW;EACjB,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC;EAC/B,OAAO,OAAO,SAAS,QAAQ,CAAC;CAClC,CAAC;AACH;;;;;;AAOA,SAAgB,yBACd,SACA,WACqD;CACrD,IAAI;CACJ,IAAI,oBAAoB,CAAC;CACzB,MAAM,QAAQ,kBACN,KAAK,wBAAwB,SAAS,SAAS,GACrD,KAAK,MAAM,4BAA4B,CAAC,CAC1C;CACA,MAAM,QAAQ;CACd,IAAI,UAAU;CACd,MAAM,aAAa;EACjB,IAAI,SAAS;EACb,UAAU;EACV,cAAc,KAAK;EACnB,YAAY;CACd;CACA,cAAc,QAAQ,WAAU,UAAS;EACvC,IAAI,MAAM,SAAS,aAAa;EAChC,cAAc,MAAM;EACpB,KAAK;CACP,CAAC;CACD,OAAO;EAAE,iBAAiB;EAAW;CAAK;AAC5C;;AAGA,SAAgB,WACd,QACA,QACA,cAC0B;CAC1B,QAAQ,QAAR;EACE,KAAK,SAAS;GACZ,MAAM,UAAU,cAAc,KAAK;GACnC,OAAO;IACL,QAAQ;IACR,QAAQ,UACJ,2BAA2B,QAAQ,MAAM,GAAG,oBAAoB,MAChE;GACN;EACF;EACA,KAAK,WACH,OAAO;EACT,KAAK,aACH,OAAO;GACL,QAAQ;GACR,GAAI,SAAS;IAAE,UAAU,OAAO;IAAU,GAAI,OAAO,WAAW,EAAE,UAAU,KAAK,IAAI,CAAC;GAAG,IAAI,CAAC;EAChG;EACF,SACE,OAAO,EAAE,QAAQ,OAAO;CAC5B;AACF"}
|
package/dist/tools/scope.js
CHANGED
|
@@ -76,7 +76,7 @@ async function resolveFactoryToolScope({ requestContext, authEnabled, sessions,
|
|
|
76
76
|
*/
|
|
77
77
|
function requirePerson(scope, action) {
|
|
78
78
|
if (scope.interactive) return;
|
|
79
|
-
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
|
|
79
|
+
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 reply in their channel or message this session, and link the item so they can find it.`);
|
|
80
80
|
}
|
|
81
81
|
export { FACTORY_INTERACTIVE_KEY, FACTORY_SLACK_SENDER_KEY, requirePerson, resolveFactoryToolScope };
|
|
82
82
|
|
package/dist/tools/scope.js.map
CHANGED
|
@@ -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 { isWorkerSession } from '../session/session-threads.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. */\n sessionId: string;\n /** The memory resource the session runs under. */\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 sessionId = controller?.sessionId;\n const resourceId = controller?.resourceId;\n const threadId = controller?.threadId;\n if (!sessionId || !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(sessionId);\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 const origin =\n row.originSessionId && row.originThreadId\n ? { sessionId: row.originSessionId, threadId: row.originThreadId }\n : undefined;\n const worker = await isWorkerSession(row, workItems);\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
|
|
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 { isWorkerSession } from '../session/session-threads.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. */\n sessionId: string;\n /** The memory resource the session runs under. */\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 sessionId = controller?.sessionId;\n const resourceId = controller?.resourceId;\n const threadId = controller?.threadId;\n if (!sessionId || !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(sessionId);\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 const origin =\n row.originSessionId && row.originThreadId\n ? { sessionId: row.originSessionId, threadId: row.originThreadId }\n : undefined;\n const worker = await isWorkerSession(row, workItems);\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 reply in their channel or message this session, and link the item so they can find it.`,\n );\n}\n"],"mappings":";;;;;AAmBA,MAAa,2BAA2B;;;;;;;AAsCxC,eAAsB,wBAAwB,EAC5C,gBACA,aACA,UACA,UACA,aACmE;CACnE,IAAI,OAAO,gBAAgB,QAAQ,YAAY,OAAO;CACtD,MAAM,aAAa,eAAe,IAAI,YAAY;CAClD,MAAM,YAAY,YAAY;CAC9B,MAAM,aAAa,YAAY;CAC/B,MAAM,WAAW,YAAY;CAC7B,IAAI,CAAC,aAAa,CAAC,cAAc,CAAC,UAAU,OAAO;CAEnD,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,SAAS;CACnD,IAAI,CAAC,KAAK,kBAAkB,OAAO;CAEnC,IAAI,UAAU,IAAI,UAAU,OAAO,OAAO,OAAO;CAEjD,MAAM,SACJ,IAAI,mBAAmB,IAAI,iBACvB;EAAE,WAAW,IAAI;EAAiB,UAAU,IAAI;CAAe,IAC/D,KAAA;CACN,MAAM,SAAS,MAAM,gBAAgB,KAAK,SAAS;CAInD,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,8LAC9B;AACF"}
|
|
@@ -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;
|
|
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;AAMhE,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;AAKnD,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,yBAAyB,GACzB,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,CA0clB"}
|
|
@@ -4,6 +4,7 @@ import { primeTenantCredentialsForTenant } from "../routes/tenant-credentials.js
|
|
|
4
4
|
import { effectiveModelScope } from "../storage/domains/projects/base.js";
|
|
5
5
|
import { ensureFactorySourceSession, hydrateFactorySession, resolveFactorySessionModel } from "../session/factory-session.js";
|
|
6
6
|
import { resolveSessionThread } from "./deliver.js";
|
|
7
|
+
import { factorySessionUrl, factoryWorkItemUrl } from "../links.js";
|
|
7
8
|
import { findWorkItem, workItemNotFound, workItemNumber, workItemRefSchema } from "./read-tools.js";
|
|
8
9
|
import { sessionModel } from "./side-sessions.js";
|
|
9
10
|
import { resolveFactoryModelPolicy } from "../session/credential-policy.js";
|
|
@@ -139,7 +140,7 @@ function createFactorySessionTools(scope, deps) {
|
|
|
139
140
|
"A side session uses this session’s model. Pass `model` to start it on another model the session credentials can run.",
|
|
140
141
|
"Use it for self-contained investigations; when the result should land as a pull request, file a work item instead.",
|
|
141
142
|
"With `workItem` and `role` it starts that role on the work item instead, without moving the card, and this session follows the item.",
|
|
142
|
-
"A role that already has a session on the item (current or past) can't be started again from here: message that session with factory_signal_session, or ask the person to
|
|
143
|
+
"A role that already has a session on the item (current or past) can't be started again from here: message that session with factory_signal_session, or ask the person to start it from a signed-in chat."
|
|
143
144
|
].join(" "),
|
|
144
145
|
inputSchema: z.object({
|
|
145
146
|
prompt: z.string().trim().min(1).max(8e3).describe("The first message the new session receives."),
|
|
@@ -153,7 +154,7 @@ function createFactorySessionTools(scope, deps) {
|
|
|
153
154
|
}),
|
|
154
155
|
execute: async ({ prompt, title, model, workItem, role }) => {
|
|
155
156
|
if (workItem !== void 0) {
|
|
156
|
-
if (!scope.interactive) throw new Error("Starting a role on a work item needs a person in this turn; start a side session, or ask the person to
|
|
157
|
+
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 ask for it in a signed-in chat.");
|
|
157
158
|
return startWorkItemRole({
|
|
158
159
|
prompt,
|
|
159
160
|
title,
|
|
@@ -255,7 +256,12 @@ function createFactorySessionTools(scope, deps) {
|
|
|
255
256
|
mode: "side",
|
|
256
257
|
sessionId: ensured.sessionId,
|
|
257
258
|
threadId,
|
|
258
|
-
title: threadTitle
|
|
259
|
+
title: threadTitle,
|
|
260
|
+
url: factorySessionUrl({
|
|
261
|
+
factoryProjectId: scope.factoryProjectId,
|
|
262
|
+
sessionId: ensured.sessionId,
|
|
263
|
+
threadId
|
|
264
|
+
}) ?? null
|
|
259
265
|
};
|
|
260
266
|
}
|
|
261
267
|
});
|
|
@@ -346,7 +352,7 @@ function createFactorySessionTools(scope, deps) {
|
|
|
346
352
|
}
|
|
347
353
|
const replaced = (await deps.workItems.listRunBindings(scope.orgId, scope.factoryProjectId, item.id)).find((binding) => binding.role === role && binding.id !== prepared.bindingId && binding.revokedAt !== null && binding.revokedAt >= startedAt);
|
|
348
354
|
const session = await messaging.controller.getSession(prepared.sessionId);
|
|
349
|
-
if (!session) throw new Error(`The ${role} session was bound but is not running;
|
|
355
|
+
if (!session) throw new Error(`The ${role} session ${prepared.sessionId} was bound but is not running; message it with factory_signal_session to wake it.`);
|
|
350
356
|
const kickoffId = globalThis.crypto.randomUUID();
|
|
351
357
|
const heartbeat = heartbeatOpenRunUntilEnd(session, kickoffId);
|
|
352
358
|
const kickoffContext = new RequestContext();
|
|
@@ -393,11 +399,21 @@ function createFactorySessionTools(scope, deps) {
|
|
|
393
399
|
mode: "work-item",
|
|
394
400
|
workItem: {
|
|
395
401
|
id: item.id,
|
|
396
|
-
...number === null ? {} : { number }
|
|
402
|
+
...number === null ? {} : { number },
|
|
403
|
+
url: factoryWorkItemUrl({
|
|
404
|
+
factoryProjectId: scope.factoryProjectId,
|
|
405
|
+
board: boardForWorkItem(item),
|
|
406
|
+
workItemId: item.id
|
|
407
|
+
}) ?? null
|
|
397
408
|
},
|
|
398
409
|
role,
|
|
399
410
|
sessionId: prepared.sessionId,
|
|
400
411
|
threadId: prepared.threadId,
|
|
412
|
+
url: factorySessionUrl({
|
|
413
|
+
factoryProjectId: scope.factoryProjectId,
|
|
414
|
+
sessionId: prepared.sessionId,
|
|
415
|
+
threadId: prepared.threadId
|
|
416
|
+
}) ?? null,
|
|
401
417
|
bindingId: prepared.bindingId,
|
|
402
418
|
...replaced ? { replacedSessionId: replaced.sessionId } : {}
|
|
403
419
|
};
|