@mastra/factory 0.24.0-alpha.1 → 0.24.0-alpha.3
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 +103 -4
- package/dist/auth.d.ts +14 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +45 -9
- package/dist/auth.js.map +1 -1
- package/dist/boards/work-transition-policy.d.ts.map +1 -1
- package/dist/boards/work-transition-policy.js +10 -0
- package/dist/boards/work-transition-policy.js.map +1 -1
- package/dist/boards/work.d.ts +14 -0
- package/dist/boards/work.d.ts.map +1 -1
- package/dist/boards/work.js +70 -7
- package/dist/boards/work.js.map +1 -1
- package/dist/capabilities/version-control.d.ts +26 -0
- package/dist/capabilities/version-control.d.ts.map +1 -1
- package/dist/factory.d.ts +8 -0
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +191 -16
- package/dist/factory.js.map +1 -1
- package/dist/integrations/github/integration.d.ts.map +1 -1
- package/dist/integrations/github/integration.js +37 -1
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/gitlab/api.d.ts +15 -0
- package/dist/integrations/gitlab/api.d.ts.map +1 -1
- package/dist/integrations/gitlab/api.js +9 -0
- package/dist/integrations/gitlab/api.js.map +1 -1
- package/dist/integrations/gitlab/version-control.d.ts.map +1 -1
- package/dist/integrations/gitlab/version-control.js +37 -3
- package/dist/integrations/gitlab/version-control.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +27 -1
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/integrations/subscription-session.d.ts +15 -0
- package/dist/integrations/subscription-session.d.ts.map +1 -1
- package/dist/integrations/subscription-session.js +30 -13
- package/dist/integrations/subscription-session.js.map +1 -1
- package/dist/mcp/disconnect.d.ts +10 -0
- package/dist/mcp/disconnect.d.ts.map +1 -0
- package/dist/mcp/disconnect.js +44 -0
- package/dist/mcp/disconnect.js.map +1 -0
- package/dist/mcp/routes.d.ts +46 -0
- package/dist/mcp/routes.d.ts.map +1 -0
- package/dist/mcp/routes.js +243 -0
- package/dist/mcp/routes.js.map +1 -0
- package/dist/provider-display-name.d.ts +2 -0
- package/dist/provider-display-name.d.ts.map +1 -0
- package/dist/provider-display-name.js +16 -0
- package/dist/provider-display-name.js.map +1 -0
- package/dist/routes/config.js +2 -2
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/contracts.d.ts +4 -0
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/contracts.js +3 -1
- package/dist/routes/contracts.js.map +1 -1
- package/dist/routes/oauth.js +1 -1
- package/dist/routes/oauth.js.map +1 -1
- package/dist/routes/provider-credentials.d.ts +6 -2
- package/dist/routes/provider-credentials.d.ts.map +1 -1
- package/dist/routes/provider-credentials.js +20 -7
- package/dist/routes/provider-credentials.js.map +1 -1
- package/dist/routes/source-control-sessions.d.ts +1 -0
- package/dist/routes/source-control-sessions.d.ts.map +1 -1
- package/dist/routes/source-control-sessions.js +1 -1
- package/dist/routes/source-control-sessions.js.map +1 -1
- package/dist/routes/surface.d.ts +1 -0
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +4 -3
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +61 -121
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/dispatcher.d.ts +7 -0
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +62 -5
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/processor.d.ts.map +1 -1
- package/dist/rules/processor.js +2 -1
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/tools.d.ts.map +1 -1
- package/dist/rules/tools.js +82 -3
- package/dist/rules/tools.js.map +1 -1
- package/dist/rules/transition-service.d.ts +17 -0
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +25 -0
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/rules/types.d.ts +2 -4
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/types.js +1 -4
- package/dist/rules/types.js.map +1 -1
- package/dist/sandbox/local-factory-sandbox.d.ts.map +1 -1
- package/dist/sandbox/local-factory-sandbox.js +1 -0
- package/dist/sandbox/local-factory-sandbox.js.map +1 -1
- package/dist/session/factory-session.d.ts +15 -1
- package/dist/session/factory-session.d.ts.map +1 -1
- package/dist/session/factory-session.js +4 -2
- package/dist/session/factory-session.js.map +1 -1
- package/dist/session/reply-credential-processor.d.ts +15 -0
- package/dist/session/reply-credential-processor.d.ts.map +1 -0
- package/dist/session/reply-credential-processor.js +40 -0
- package/dist/session/reply-credential-processor.js.map +1 -0
- package/dist/session/source-control-tools.d.ts +19 -1
- package/dist/session/source-control-tools.d.ts.map +1 -1
- package/dist/session/source-control-tools.js +70 -3
- package/dist/session/source-control-tools.js.map +1 -1
- package/dist/storage/domains/audit/domain.js +1 -1
- package/dist/storage/domains/projects/base.d.ts +6 -0
- package/dist/storage/domains/projects/base.d.ts.map +1 -1
- package/dist/storage/domains/projects/base.js +12 -0
- package/dist/storage/domains/projects/base.js.map +1 -1
- package/dist/storage/domains/source-control/base.d.ts +17 -0
- package/dist/storage/domains/source-control/base.d.ts.map +1 -1
- package/dist/storage/domains/source-control/base.js +23 -0
- package/dist/storage/domains/source-control/base.js.map +1 -1
- package/dist/storage/domains/source-control/inmemory.d.ts +4 -0
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/source-control/inmemory.js +5 -1
- package/dist/storage/domains/source-control/inmemory.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +56 -0
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +139 -25
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/tools/annotations.d.ts +7 -0
- package/dist/tools/annotations.d.ts.map +1 -0
- package/dist/tools/annotations.js +102 -0
- package/dist/tools/annotations.js.map +1 -0
- package/dist/tools/approval-tools.d.ts +15 -0
- package/dist/tools/approval-tools.d.ts.map +1 -0
- package/dist/tools/approval-tools.js +77 -0
- package/dist/tools/approval-tools.js.map +1 -0
- package/dist/tools/deliver.d.ts +54 -0
- package/dist/tools/deliver.d.ts.map +1 -0
- package/dist/tools/deliver.js +105 -0
- package/dist/tools/deliver.js.map +1 -0
- package/dist/tools/index.d.ts +29 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +39 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/messaging-tools.d.ts +28 -0
- package/dist/tools/messaging-tools.d.ts.map +1 -0
- package/dist/tools/messaging-tools.js +312 -0
- package/dist/tools/messaging-tools.js.map +1 -0
- package/dist/tools/permissions.d.ts +31 -0
- package/dist/tools/permissions.d.ts.map +1 -0
- package/dist/tools/permissions.js +44 -0
- package/dist/tools/permissions.js.map +1 -0
- package/dist/tools/read-tools.d.ts +27 -0
- package/dist/tools/read-tools.d.ts.map +1 -0
- package/dist/tools/read-tools.js +285 -0
- package/dist/tools/read-tools.js.map +1 -0
- package/dist/tools/reports.d.ts +163 -0
- package/dist/tools/reports.d.ts.map +1 -0
- package/dist/tools/reports.js +352 -0
- package/dist/tools/reports.js.map +1 -0
- package/dist/tools/run-watch.d.ts +46 -0
- package/dist/tools/run-watch.d.ts.map +1 -0
- package/dist/tools/run-watch.js +102 -0
- package/dist/tools/run-watch.js.map +1 -0
- package/dist/tools/scope.d.ts +45 -0
- package/dist/tools/scope.d.ts.map +1 -0
- package/dist/tools/scope.js +73 -0
- package/dist/tools/scope.js.map +1 -0
- package/dist/tools/session-tools.d.ts +31 -0
- package/dist/tools/session-tools.d.ts.map +1 -0
- package/dist/tools/session-tools.js +312 -0
- package/dist/tools/session-tools.js.map +1 -0
- package/dist/tools/sessions-state-processor.d.ts +37 -0
- package/dist/tools/sessions-state-processor.d.ts.map +1 -0
- package/dist/tools/sessions-state-processor.js +206 -0
- package/dist/tools/sessions-state-processor.js.map +1 -0
- package/dist/tools/work-item-tools.d.ts +28 -0
- package/dist/tools/work-item-tools.d.ts.map +1 -0
- package/dist/tools/work-item-tools.js +322 -0
- package/dist/tools/work-item-tools.js.map +1 -0
- package/dist/work-items/create-work-item.d.ts +55 -0
- package/dist/work-items/create-work-item.d.ts.map +1 -0
- package/dist/work-items/create-work-item.js +101 -0
- package/dist/work-items/create-work-item.js.map +1 -0
- package/dist/work-items/decision-actions.d.ts +42 -0
- package/dist/work-items/decision-actions.d.ts.map +1 -0
- package/dist/work-items/decision-actions.js +70 -0
- package/dist/work-items/decision-actions.js.map +1 -0
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +3 -1
- package/dist/workspace.js.map +1 -1
- package/factory-skills/factory-gitlab-rereview/SKILL.md +1 -1
- package/factory-skills/factory-gitlab-review/SKILL.md +1 -1
- package/factory-skills/factory-plan/SKILL.md +4 -4
- package/factory-skills/factory-rereview/SKILL.md +3 -1
- package/factory-skills/factory-review/SKILL.md +3 -1
- package/factory-skills/factory-self-review/SKILL.md +46 -0
- package/package.json +13 -11
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { FACTORY_INTERACTIVE_KEY, factoryUserOrgId, getFactoryAuthUserFromContext, getFactoryAuthUserId } from "../auth.js";
|
|
2
|
+
import { auditActorProfile } from "../storage/domains/audit/base.js";
|
|
3
|
+
//#region src/tools/scope.ts
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the Factory a session's agent may act in. The scope comes from the
|
|
6
|
+
* stored source-control session row and the authenticated caller, never from
|
|
7
|
+
* the `factoryProjectId` thread tag alone: the browser sets that tag and
|
|
8
|
+
* nothing clears it.
|
|
9
|
+
*/
|
|
10
|
+
async function resolveFactoryToolScope({ requestContext, authEnabled, sessions, projects, workItems }) {
|
|
11
|
+
if (typeof requestContext?.get !== "function") return null;
|
|
12
|
+
const controller = requestContext.get("controller");
|
|
13
|
+
const resourceId = controller?.resourceId;
|
|
14
|
+
const threadId = controller?.threadId;
|
|
15
|
+
if (!resourceId || !threadId) return null;
|
|
16
|
+
let caller = null;
|
|
17
|
+
if (authEnabled) {
|
|
18
|
+
const user = getFactoryAuthUserFromContext(requestContext);
|
|
19
|
+
const userId = getFactoryAuthUserId(user);
|
|
20
|
+
const orgId = factoryUserOrgId(user);
|
|
21
|
+
const actorProfile = auditActorProfile(user);
|
|
22
|
+
if (userId && orgId) caller = {
|
|
23
|
+
orgId,
|
|
24
|
+
userId,
|
|
25
|
+
...actorProfile ? { actorProfile } : {}
|
|
26
|
+
};
|
|
27
|
+
} else caller = {
|
|
28
|
+
orgId: "local",
|
|
29
|
+
userId: "local"
|
|
30
|
+
};
|
|
31
|
+
const row = await sessions.getBySessionId(resourceId);
|
|
32
|
+
if (!row?.factoryProjectId) return null;
|
|
33
|
+
if (caller && row.orgId !== caller.orgId) return null;
|
|
34
|
+
const origin = row.originSessionId && row.originThreadId ? {
|
|
35
|
+
sessionId: row.originSessionId,
|
|
36
|
+
threadId: row.originThreadId
|
|
37
|
+
} : void 0;
|
|
38
|
+
const worker = origin !== void 0 || await workItems.hasRunBindingForSession({
|
|
39
|
+
orgId: row.orgId,
|
|
40
|
+
factoryProjectId: row.factoryProjectId,
|
|
41
|
+
sessionId: row.sessionId
|
|
42
|
+
});
|
|
43
|
+
let identity;
|
|
44
|
+
if (worker) identity = {
|
|
45
|
+
orgId: row.orgId,
|
|
46
|
+
userId: row.userId
|
|
47
|
+
};
|
|
48
|
+
else {
|
|
49
|
+
if (!caller) return null;
|
|
50
|
+
if (row.visibility === "private" && row.userId !== caller.userId) return null;
|
|
51
|
+
identity = caller;
|
|
52
|
+
}
|
|
53
|
+
if (!await projects.get({
|
|
54
|
+
orgId: identity.orgId,
|
|
55
|
+
id: row.factoryProjectId
|
|
56
|
+
})) return null;
|
|
57
|
+
return {
|
|
58
|
+
orgId: identity.orgId,
|
|
59
|
+
factoryProjectId: row.factoryProjectId,
|
|
60
|
+
userId: identity.userId,
|
|
61
|
+
sessionId: row.sessionId,
|
|
62
|
+
resourceId,
|
|
63
|
+
threadId,
|
|
64
|
+
kind: worker ? "worker" : "user",
|
|
65
|
+
interactive: requestContext.get(FACTORY_INTERACTIVE_KEY) === true,
|
|
66
|
+
...identity.actorProfile ? { actorProfile: identity.actorProfile } : {},
|
|
67
|
+
...origin ? { origin } : {}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { FACTORY_INTERACTIVE_KEY, resolveFactoryToolScope };
|
|
72
|
+
|
|
73
|
+
//# sourceMappingURL=scope.js.map
|
|
@@ -0,0 +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/** 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 user = getFactoryAuthUserFromContext(requestContext);\n const userId = getFactoryAuthUserId(user);\n const orgId = factoryUserOrgId(user);\n const actorProfile = 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":";;;;;;;;;AAoDA,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,OAAO,8BAA8B,cAAc;EACzD,MAAM,SAAS,qBAAqB,IAAI;EACxC,MAAM,QAAQ,iBAAiB,IAAI;EACnC,MAAM,eAAe,kBAAkB,IAAI;EAC3C,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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { BoardRegistry } from '../boards/registry.js';
|
|
2
|
+
import type { IntegrationTools } from '../integrations/base.js';
|
|
3
|
+
import { type FactoryStartCoordinator } from '../rules/start-coordinator.js';
|
|
4
|
+
import type { SourceControlSessionLookup } from '../session/factory-session.js';
|
|
5
|
+
import type { AuditRecorder } from '../storage/domains/audit/domain.js';
|
|
6
|
+
import type { IntakeStorage } from '../storage/domains/intake/base.js';
|
|
7
|
+
import type { ModelDefaultsStorage } from '../storage/domains/model-defaults/base.js';
|
|
8
|
+
import type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';
|
|
9
|
+
import type { WorkItemsStorage } from '../storage/domains/work-items/base.js';
|
|
10
|
+
import type { FactoryMessaging } from './messaging-tools.js';
|
|
11
|
+
import type { FactoryToolScope } from './scope.js';
|
|
12
|
+
/** Who renews the open-run entries this process writes; one id per process, like the dispatcher's. */
|
|
13
|
+
export declare const FACTORY_TOOLS_OWNER_ID: string;
|
|
14
|
+
export interface FactorySessionToolDependencies {
|
|
15
|
+
sessions: Pick<SourceControlSessionLookup, 'getBySessionId' | 'getSourceControlBySessionId'>;
|
|
16
|
+
sessionModel: {
|
|
17
|
+
projects: FactoryProjectsStorage;
|
|
18
|
+
modelDefaults: ModelDefaultsStorage;
|
|
19
|
+
};
|
|
20
|
+
/** Resolved lazily: the controller only exists once the mount is up. */
|
|
21
|
+
messaging: () => Promise<FactoryMessaging | null>;
|
|
22
|
+
workItems: Pick<WorkItemsStorage, 'list' | 'getForProject' | 'listRunBindings' | 'subscribeToWorkItem' | 'unsubscribeFromWorkItem' | 'listSessionSubscriptions' | 'listWorkItemSubscribers'>;
|
|
23
|
+
boards: BoardRegistry;
|
|
24
|
+
auditRecorder: AuditRecorder;
|
|
25
|
+
intake?: Pick<IntakeStorage, 'getConfig'>;
|
|
26
|
+
/** The browser Start's coordinator; absent until the Factory runtime is ready. */
|
|
27
|
+
startCoordinator: () => Pick<FactoryStartCoordinator, 'prepare'> | undefined;
|
|
28
|
+
}
|
|
29
|
+
/** Tools that start sessions on the caller's behalf. Interactive user turns only. */
|
|
30
|
+
export declare function createFactorySessionTools(scope: FactoryToolScope, deps: FactorySessionToolDependencies): IntegrationTools;
|
|
31
|
+
//# sourceMappingURL=session-tools.d.ts.map
|
|
@@ -0,0 +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;AAGhE,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAQhF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACxE,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;QAAE,QAAQ,EAAE,sBAAsB,CAAC;QAAC,aAAa,EAAE,oBAAoB,CAAA;KAAE,CAAC;IACxF,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,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;AAwBD,qFAAqF;AACrF,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,8BAA8B,GACnC,gBAAgB,CAuRlB"}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { boardForWorkItem } from "../boards/semantics.js";
|
|
2
|
+
import "../boards/index.js";
|
|
3
|
+
import { primeTenantCredentialsForTenant } from "../routes/tenant-credentials.js";
|
|
4
|
+
import { effectiveModelScope } from "../storage/domains/projects/base.js";
|
|
5
|
+
import { ensureFactorySourceSession, hydrateFactorySession, resolveFactorySessionModel } from "../session/factory-session.js";
|
|
6
|
+
import "./deliver.js";
|
|
7
|
+
import { findWorkItem, workItemNotFound, workItemNumber, workItemRefSchema } from "./read-tools.js";
|
|
8
|
+
import { USER_SESSION_BRANCH_PREFIX } from "../routes/source-control-sessions.js";
|
|
9
|
+
import { resolveWorkItemRepository } from "../session/work-item-repository.js";
|
|
10
|
+
import { FactoryStartTransitionError, WorkItemRepositoryError } from "../rules/start-coordinator.js";
|
|
11
|
+
import { recordSessionRunStart } from "../session/run-audit.js";
|
|
12
|
+
import { heartbeatOpenRunUntilEnd } from "./run-watch.js";
|
|
13
|
+
import { RequestContext } from "@mastra/core/request-context";
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import { createTool } from "@mastra/core/tools";
|
|
16
|
+
//#region src/tools/session-tools.ts
|
|
17
|
+
const TITLE_LENGTH = 120;
|
|
18
|
+
/** Who renews the open-run entries this process writes; one id per process, like the dispatcher's. */
|
|
19
|
+
const FACTORY_TOOLS_OWNER_ID = `factory-tools:${globalThis.crypto.randomUUID()}`;
|
|
20
|
+
function ambiguousRepository(candidates) {
|
|
21
|
+
return `This item has several repositories (${candidates.join(", ")}); v1 can't choose one.`;
|
|
22
|
+
}
|
|
23
|
+
function startErrorMessage(error) {
|
|
24
|
+
if (error instanceof WorkItemRepositoryError) return error.code === "repository_ambiguous" ? ambiguousRepository(error.candidates) : error.message;
|
|
25
|
+
if (error instanceof FactoryStartTransitionError) return `The start was refused: ${error.result.reason}`;
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
function workItemLabel(item) {
|
|
29
|
+
const number = workItemNumber(item);
|
|
30
|
+
return number === null ? `"${item.title}"` : `#${number}`;
|
|
31
|
+
}
|
|
32
|
+
function titleFrom(prompt) {
|
|
33
|
+
return (prompt.split("\n").find((line) => line.trim()) ?? prompt).trim().slice(0, TITLE_LENGTH);
|
|
34
|
+
}
|
|
35
|
+
/** Tools that start sessions on the caller's behalf. Interactive user turns only. */
|
|
36
|
+
function createFactorySessionTools(scope, deps) {
|
|
37
|
+
const start = createTool({
|
|
38
|
+
id: "factory_start_session",
|
|
39
|
+
description: [
|
|
40
|
+
"Start a side session in this Factory with a prompt. It runs on its own sandbox and branch as you, and reports back to this session with `factory-session` notifications.",
|
|
41
|
+
"Use it for self-contained investigations; when the result should land as a pull request, file a work item instead.",
|
|
42
|
+
"With `workItem` and `role` it starts that role on the work item instead, without moving the card, and this session follows the item.",
|
|
43
|
+
"A role that already has a session on the item (current or past) can't be started again from here: message that session with factory_signal_session, or ask the person to use the board."
|
|
44
|
+
].join(" "),
|
|
45
|
+
inputSchema: z.object({
|
|
46
|
+
prompt: z.string().trim().min(1).max(8e3).describe("The first message the new session receives."),
|
|
47
|
+
title: z.string().trim().min(1).max(TITLE_LENGTH).optional(),
|
|
48
|
+
workItem: workItemRefSchema.optional(),
|
|
49
|
+
role: z.string().trim().min(1).max(64).optional()
|
|
50
|
+
}).strict().refine((input) => input.role === void 0 || input.workItem !== void 0, {
|
|
51
|
+
message: "Pass workItem together with role.",
|
|
52
|
+
path: ["role"]
|
|
53
|
+
}),
|
|
54
|
+
execute: async ({ prompt, title, workItem, role }) => {
|
|
55
|
+
if (workItem !== void 0) return startWorkItemRole({
|
|
56
|
+
prompt,
|
|
57
|
+
title,
|
|
58
|
+
workItem,
|
|
59
|
+
role
|
|
60
|
+
});
|
|
61
|
+
const parent = await deps.sessions.getBySessionId(scope.sessionId);
|
|
62
|
+
const sourceControl = await deps.sessions.getSourceControlBySessionId(scope.sessionId);
|
|
63
|
+
if (!parent || !sourceControl) throw new Error("This session has no Factory session row to start from.");
|
|
64
|
+
const messaging = await deps.messaging();
|
|
65
|
+
if (!messaging) throw new Error("Starting sessions is not available on this server.");
|
|
66
|
+
const sessionId = globalThis.crypto.randomUUID();
|
|
67
|
+
const ensured = await ensureFactorySourceSession({
|
|
68
|
+
sourceControl,
|
|
69
|
+
orgId: scope.orgId,
|
|
70
|
+
factoryProjectId: scope.factoryProjectId,
|
|
71
|
+
sessionId,
|
|
72
|
+
branch: `${USER_SESSION_BRANCH_PREFIX}${sessionId}`,
|
|
73
|
+
attributeToUserId: scope.userId,
|
|
74
|
+
...parent.projectRepositoryId ? { projectRepositoryId: parent.projectRepositoryId } : {},
|
|
75
|
+
visibility: parent.visibility,
|
|
76
|
+
origin: {
|
|
77
|
+
sessionId: scope.sessionId,
|
|
78
|
+
threadId: scope.threadId
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
const sessionModel = await resolveFactorySessionModel({
|
|
82
|
+
...deps.sessionModel,
|
|
83
|
+
factoryProjectId: scope.factoryProjectId,
|
|
84
|
+
orgId: scope.orgId,
|
|
85
|
+
ownerUserId: scope.userId,
|
|
86
|
+
context: "session"
|
|
87
|
+
});
|
|
88
|
+
const kickoffContext = new RequestContext();
|
|
89
|
+
kickoffContext.set("user", {
|
|
90
|
+
workosId: scope.userId,
|
|
91
|
+
organizationId: scope.orgId
|
|
92
|
+
});
|
|
93
|
+
const tags = {
|
|
94
|
+
factoryProjectId: scope.factoryProjectId,
|
|
95
|
+
projectRepositoryId: ensured.projectRepositoryId
|
|
96
|
+
};
|
|
97
|
+
const session = await messaging.controller.createSession({
|
|
98
|
+
id: ensured.sessionId,
|
|
99
|
+
ownerId: scope.userId,
|
|
100
|
+
resourceId: ensured.sessionId,
|
|
101
|
+
threadId: ensured.sessionId,
|
|
102
|
+
requestContext: kickoffContext,
|
|
103
|
+
tags
|
|
104
|
+
});
|
|
105
|
+
await session.state.set(tags);
|
|
106
|
+
await hydrateFactorySession(session, {
|
|
107
|
+
orgId: scope.orgId,
|
|
108
|
+
modelScope: sessionModel.modelScope,
|
|
109
|
+
ownerUserId: scope.userId,
|
|
110
|
+
defaultModelId: sessionModel.modelId
|
|
111
|
+
});
|
|
112
|
+
const threadId = session.thread.requireId();
|
|
113
|
+
const threadTitle = title ?? titleFrom(prompt);
|
|
114
|
+
await session.thread.rename({
|
|
115
|
+
title: threadTitle,
|
|
116
|
+
pin: false
|
|
117
|
+
});
|
|
118
|
+
await sourceControl.sessions.rename({
|
|
119
|
+
sessionId: ensured.sessionId,
|
|
120
|
+
title: threadTitle
|
|
121
|
+
});
|
|
122
|
+
await session.thread.setSetting({
|
|
123
|
+
key: "factorySessionId",
|
|
124
|
+
value: ensured.sessionId
|
|
125
|
+
});
|
|
126
|
+
await primeTenantCredentialsForTenant({
|
|
127
|
+
orgId: scope.orgId,
|
|
128
|
+
userId: scope.userId,
|
|
129
|
+
credentialScope: sessionModel.modelScope
|
|
130
|
+
});
|
|
131
|
+
await session.sendSignal({
|
|
132
|
+
id: globalThis.crypto.randomUUID(),
|
|
133
|
+
type: "user",
|
|
134
|
+
tagName: "user",
|
|
135
|
+
contents: prompt
|
|
136
|
+
}, {
|
|
137
|
+
requestContext: kickoffContext,
|
|
138
|
+
requireDelivery: true
|
|
139
|
+
}).accepted;
|
|
140
|
+
return {
|
|
141
|
+
mode: "side",
|
|
142
|
+
sessionId: ensured.sessionId,
|
|
143
|
+
threadId,
|
|
144
|
+
title: threadTitle
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
async function startWorkItemRole({ prompt, title, workItem, role }) {
|
|
149
|
+
if (!role) throw new Error("Pass the role to start on the work item.");
|
|
150
|
+
const coordinator = deps.startCoordinator();
|
|
151
|
+
if (!coordinator) throw new Error("Factory sessions can't be started yet; try again shortly.");
|
|
152
|
+
const item = await findWorkItem(deps.workItems, scope, workItem);
|
|
153
|
+
if (!item) throw workItemNotFound(workItem);
|
|
154
|
+
const label = workItemLabel(item);
|
|
155
|
+
if (!deps.boards.get(boardForWorkItem(item))?.phaseForRole(role)) throw new Error(`${label}'s board has no ${role} role.`);
|
|
156
|
+
const seated = item.sessions[role];
|
|
157
|
+
if (seated) throw new Error(`${label} already has a ${role} session ${seated.sessionId}; message it with factory_signal_session.`);
|
|
158
|
+
const parent = await deps.sessions.getBySessionId(scope.sessionId);
|
|
159
|
+
const sourceControl = await deps.sessions.getSourceControlBySessionId(scope.sessionId);
|
|
160
|
+
if (!parent || !sourceControl) throw new Error("This session has no Factory session row to start from.");
|
|
161
|
+
const messaging = await deps.messaging();
|
|
162
|
+
if (!messaging) throw new Error("Starting sessions is not available on this server.");
|
|
163
|
+
const project = await deps.sessionModel.projects.get({
|
|
164
|
+
orgId: scope.orgId,
|
|
165
|
+
id: scope.factoryProjectId
|
|
166
|
+
});
|
|
167
|
+
if (!project) throw new Error("This Factory no longer exists.");
|
|
168
|
+
const modelScope = effectiveModelScope(project.modelScope, "run");
|
|
169
|
+
const projectRepositoryId = await resolveRepository(sourceControl, item);
|
|
170
|
+
const sessionId = globalThis.crypto.randomUUID();
|
|
171
|
+
const ensured = await ensureFactorySourceSession({
|
|
172
|
+
sourceControl,
|
|
173
|
+
orgId: scope.orgId,
|
|
174
|
+
factoryProjectId: scope.factoryProjectId,
|
|
175
|
+
sessionId,
|
|
176
|
+
branch: `${USER_SESSION_BRANCH_PREFIX}${sessionId}`,
|
|
177
|
+
projectRepositoryId,
|
|
178
|
+
attributeToUserId: scope.userId,
|
|
179
|
+
visibility: parent.visibility
|
|
180
|
+
});
|
|
181
|
+
const followScope = {
|
|
182
|
+
orgId: scope.orgId,
|
|
183
|
+
factoryProjectId: scope.factoryProjectId
|
|
184
|
+
};
|
|
185
|
+
const alreadyFollowing = (await deps.workItems.listSessionSubscriptions({
|
|
186
|
+
...followScope,
|
|
187
|
+
sessionId: scope.sessionId
|
|
188
|
+
})).some((subscription) => subscription.workItemId === item.id);
|
|
189
|
+
await deps.workItems.subscribeToWorkItem({
|
|
190
|
+
...followScope,
|
|
191
|
+
workItemId: item.id,
|
|
192
|
+
sessionId: scope.sessionId,
|
|
193
|
+
threadId: scope.threadId,
|
|
194
|
+
resourceId: scope.resourceId,
|
|
195
|
+
userId: scope.userId,
|
|
196
|
+
reason: "started"
|
|
197
|
+
});
|
|
198
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
199
|
+
let prepared;
|
|
200
|
+
try {
|
|
201
|
+
prepared = await coordinator.prepare({
|
|
202
|
+
orgId: scope.orgId,
|
|
203
|
+
userId: scope.userId,
|
|
204
|
+
factoryProjectId: scope.factoryProjectId,
|
|
205
|
+
sessionId: ensured.sessionId,
|
|
206
|
+
threadTitle: title ?? `${label} ${role}`,
|
|
207
|
+
kickoffKey: globalThis.crypto.randomUUID(),
|
|
208
|
+
modelScope,
|
|
209
|
+
defaultModelId: modelScope === "factory" ? project.defaultModelId ?? void 0 : void 0,
|
|
210
|
+
workItem: {
|
|
211
|
+
id: item.id,
|
|
212
|
+
role,
|
|
213
|
+
input: {
|
|
214
|
+
externalSource: item.externalSource,
|
|
215
|
+
parentWorkItemId: item.parentWorkItemId,
|
|
216
|
+
title: item.title,
|
|
217
|
+
stages: item.stages,
|
|
218
|
+
sessions: item.sessions,
|
|
219
|
+
metadata: item.metadata
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
} catch (error) {
|
|
224
|
+
if (!alreadyFollowing) await deps.workItems.unsubscribeFromWorkItem({
|
|
225
|
+
orgId: scope.orgId,
|
|
226
|
+
workItemId: item.id,
|
|
227
|
+
sessionId: scope.sessionId
|
|
228
|
+
});
|
|
229
|
+
const message = startErrorMessage(error);
|
|
230
|
+
if (message) throw new Error(message);
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
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);
|
|
234
|
+
const session = await messaging.controller.getSessionByResource(prepared.resourceId);
|
|
235
|
+
if (!session) throw new Error(`The ${role} session was bound but is not running; open it from the board.`);
|
|
236
|
+
const kickoffId = globalThis.crypto.randomUUID();
|
|
237
|
+
const heartbeat = heartbeatOpenRunUntilEnd(session, kickoffId);
|
|
238
|
+
const kickoffContext = new RequestContext();
|
|
239
|
+
kickoffContext.set("user", {
|
|
240
|
+
workosId: scope.userId,
|
|
241
|
+
organizationId: scope.orgId
|
|
242
|
+
});
|
|
243
|
+
try {
|
|
244
|
+
await session.sendSignal({
|
|
245
|
+
id: kickoffId,
|
|
246
|
+
type: "user",
|
|
247
|
+
tagName: "user",
|
|
248
|
+
contents: prompt
|
|
249
|
+
}, {
|
|
250
|
+
requestContext: kickoffContext,
|
|
251
|
+
requireDelivery: true
|
|
252
|
+
}).accepted;
|
|
253
|
+
} catch (error) {
|
|
254
|
+
heartbeat.stop();
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
await recordSessionRunStart(session, {
|
|
258
|
+
audit: deps.auditRecorder,
|
|
259
|
+
actorType: "human",
|
|
260
|
+
observedEnd: heartbeat.endReason,
|
|
261
|
+
run: {
|
|
262
|
+
kickoffId,
|
|
263
|
+
ownerId: FACTORY_TOOLS_OWNER_ID,
|
|
264
|
+
heartbeatAt: Date.now(),
|
|
265
|
+
bindingId: prepared.bindingId,
|
|
266
|
+
role,
|
|
267
|
+
startedBy: scope.userId,
|
|
268
|
+
orgId: scope.orgId,
|
|
269
|
+
factoryProjectId: scope.factoryProjectId,
|
|
270
|
+
workItemId: item.id,
|
|
271
|
+
workItemName: item.title,
|
|
272
|
+
sessionId: prepared.sessionId,
|
|
273
|
+
threadId: prepared.threadId,
|
|
274
|
+
branch: prepared.branch
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
const number = workItemNumber(item);
|
|
278
|
+
return {
|
|
279
|
+
mode: "work-item",
|
|
280
|
+
workItem: {
|
|
281
|
+
id: item.id,
|
|
282
|
+
...number === null ? {} : { number }
|
|
283
|
+
},
|
|
284
|
+
role,
|
|
285
|
+
sessionId: prepared.sessionId,
|
|
286
|
+
threadId: prepared.threadId,
|
|
287
|
+
bindingId: prepared.bindingId,
|
|
288
|
+
...replaced ? { replacedSessionId: replaced.sessionId } : {}
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
async function resolveRepository(sourceControl, item) {
|
|
292
|
+
const intakeConfig = await deps.intake?.getConfig({
|
|
293
|
+
orgId: scope.orgId,
|
|
294
|
+
integrationIds: ["linear"]
|
|
295
|
+
});
|
|
296
|
+
const repository = await resolveWorkItemRepository({
|
|
297
|
+
sourceControl,
|
|
298
|
+
orgId: scope.orgId,
|
|
299
|
+
factoryProjectId: scope.factoryProjectId,
|
|
300
|
+
item,
|
|
301
|
+
linearRepositoryMap: intakeConfig?.linear?.repositoryByLinearProject
|
|
302
|
+
});
|
|
303
|
+
if (repository.status === "ambiguous") throw new Error(ambiguousRepository(repository.candidates));
|
|
304
|
+
if (repository.status === "unlinked") throw new Error(repository.hint);
|
|
305
|
+
return repository.projectRepositoryId;
|
|
306
|
+
}
|
|
307
|
+
return { factory_start_session: start };
|
|
308
|
+
}
|
|
309
|
+
//#endregion
|
|
310
|
+
export { FACTORY_TOOLS_OWNER_ID, createFactorySessionTools };
|
|
311
|
+
|
|
312
|
+
//# sourceMappingURL=session-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-tools.js","names":[],"sources":["../../src/tools/session-tools.ts"],"sourcesContent":["import { RequestContext } from '@mastra/core/request-context';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport { boardForWorkItem } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/registry.js';\nimport type { IntegrationTools } from '../integrations/base.js';\nimport { USER_SESSION_BRANCH_PREFIX } from '../routes/source-control-sessions.js';\nimport { primeTenantCredentialsForTenant } from '../routes/tenant-credentials.js';\nimport {\n FactoryStartTransitionError,\n WorkItemRepositoryError,\n type FactoryStartCoordinator,\n} from '../rules/start-coordinator.js';\nimport type { SourceControlSessionLookup } from '../session/factory-session.js';\nimport {\n ensureFactorySourceSession,\n hydrateFactorySession,\n resolveFactorySessionModel,\n} from '../session/factory-session.js';\nimport { recordSessionRunStart } from '../session/run-audit.js';\nimport { resolveWorkItemRepository } from '../session/work-item-repository.js';\nimport type { AuditRecorder } from '../storage/domains/audit/domain.js';\nimport type { IntakeStorage } from '../storage/domains/intake/base.js';\nimport type { ModelDefaultsStorage } from '../storage/domains/model-defaults/base.js';\nimport { effectiveModelScope } from '../storage/domains/projects/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { deliverFactorySessionNotification } from './deliver.js';\nimport type { FactoryMessaging } from './messaging-tools.js';\nimport { findWorkItem, workItemNotFound, workItemNumber, workItemRefSchema } from './read-tools.js';\nimport { heartbeatOpenRunUntilEnd } from './run-watch.js';\nimport type { FactoryToolScope } from './scope.js';\n\nconst TITLE_LENGTH = 120;\n\n/** Who renews the open-run entries this process writes; one id per process, like the dispatcher's. */\nexport const FACTORY_TOOLS_OWNER_ID = `factory-tools:${globalThis.crypto.randomUUID()}`;\n\nexport interface FactorySessionToolDependencies {\n sessions: Pick<SourceControlSessionLookup, 'getBySessionId' | 'getSourceControlBySessionId'>;\n sessionModel: { projects: FactoryProjectsStorage; modelDefaults: ModelDefaultsStorage };\n /** Resolved lazily: the controller only exists once the mount is up. */\n messaging: () => Promise<FactoryMessaging | null>;\n workItems: Pick<\n WorkItemsStorage,\n | 'list'\n | 'getForProject'\n | 'listRunBindings'\n | 'subscribeToWorkItem'\n | 'unsubscribeFromWorkItem'\n | 'listSessionSubscriptions'\n | 'listWorkItemSubscribers'\n >;\n boards: BoardRegistry;\n auditRecorder: AuditRecorder;\n intake?: Pick<IntakeStorage, 'getConfig'>;\n /** The browser Start's coordinator; absent until the Factory runtime is ready. */\n startCoordinator: () => Pick<FactoryStartCoordinator, 'prepare'> | undefined;\n}\n\nfunction ambiguousRepository(candidates: readonly string[]): string {\n return `This item has several repositories (${candidates.join(', ')}); v1 can't choose one.`;\n}\n\nfunction startErrorMessage(error: unknown): string | null {\n if (error instanceof WorkItemRepositoryError) {\n return error.code === 'repository_ambiguous' ? ambiguousRepository(error.candidates) : error.message;\n }\n if (error instanceof FactoryStartTransitionError) return `The start was refused: ${error.result.reason}`;\n return null;\n}\n\nfunction workItemLabel(item: WorkItemRow): string {\n const number = workItemNumber(item);\n return number === null ? `\"${item.title}\"` : `#${number}`;\n}\n\nfunction titleFrom(prompt: string): string {\n const firstLine = prompt.split('\\n').find(line => line.trim()) ?? prompt;\n return firstLine.trim().slice(0, TITLE_LENGTH);\n}\n\n/** Tools that start sessions on the caller's behalf. Interactive user turns only. */\nexport function createFactorySessionTools(\n scope: FactoryToolScope,\n deps: FactorySessionToolDependencies,\n): IntegrationTools {\n const start = createTool({\n id: 'factory_start_session',\n description: [\n 'Start a side session in this Factory with a prompt. It runs on its own sandbox and branch as you, and reports back to this session with `factory-session` notifications.',\n 'Use it for self-contained investigations; when the result should land as a pull request, file a work item instead.',\n 'With `workItem` and `role` it starts that role on the work item instead, without moving the card, and this session follows the item.',\n \"A role that already has a session on the item (current or past) can't be started again from here: message that session with factory_signal_session, or ask the person to use the board.\",\n ].join(' '),\n inputSchema: z\n .object({\n prompt: z.string().trim().min(1).max(8000).describe('The first message the new session receives.'),\n title: z.string().trim().min(1).max(TITLE_LENGTH).optional(),\n workItem: workItemRefSchema.optional(),\n role: z.string().trim().min(1).max(64).optional(),\n })\n .strict()\n .refine(input => input.role === undefined || input.workItem !== undefined, {\n message: 'Pass workItem together with role.',\n path: ['role'],\n }),\n execute: async ({ prompt, title, workItem, role }) => {\n if (workItem !== undefined) return startWorkItemRole({ prompt, title, workItem, role });\n\n const parent = await deps.sessions.getBySessionId(scope.sessionId);\n const sourceControl = await deps.sessions.getSourceControlBySessionId(scope.sessionId);\n if (!parent || !sourceControl) throw new Error('This session has no Factory session row to start from.');\n const messaging = await deps.messaging();\n if (!messaging) throw new Error('Starting sessions is not available on this server.');\n\n const sessionId = globalThis.crypto.randomUUID();\n const ensured = await ensureFactorySourceSession({\n sourceControl,\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n sessionId,\n branch: `${USER_SESSION_BRANCH_PREFIX}${sessionId}`,\n // Bills the caller: the credential tenant follows the row's user.\n attributeToUserId: scope.userId,\n // Same repository as the session that started it.\n ...(parent.projectRepositoryId ? { projectRepositoryId: parent.projectRepositoryId } : {}),\n visibility: parent.visibility,\n origin: { sessionId: scope.sessionId, threadId: scope.threadId },\n });\n const sessionModel = await resolveFactorySessionModel({\n ...deps.sessionModel,\n factoryProjectId: scope.factoryProjectId,\n orgId: scope.orgId,\n ownerUserId: scope.userId,\n // No work item, so an interactive session: a hybrid Factory bills it as one.\n context: 'session',\n });\n\n // Only the new row's owner as `user`. The tool call's context carries the\n // interactive marker and the parent's controller entry; neither may reach the child.\n const kickoffContext = new RequestContext();\n kickoffContext.set('user', { workosId: scope.userId, organizationId: scope.orgId });\n\n const tags = { factoryProjectId: scope.factoryProjectId, projectRepositoryId: ensured.projectRepositoryId };\n const session = await messaging.controller.createSession({\n id: ensured.sessionId,\n ownerId: scope.userId,\n resourceId: ensured.sessionId,\n threadId: ensured.sessionId,\n requestContext: kickoffContext,\n tags,\n });\n await session.state.set(tags);\n await hydrateFactorySession(session, {\n orgId: scope.orgId,\n modelScope: sessionModel.modelScope,\n ownerUserId: scope.userId,\n defaultModelId: sessionModel.modelId,\n });\n\n const threadId = session.thread.requireId();\n const threadTitle = title ?? titleFrom(prompt);\n await session.thread.rename({ title: threadTitle, pin: false });\n // On the row too, so its run-end reports name it without waiting for the title mirror.\n await sourceControl.sessions.rename({ sessionId: ensured.sessionId, title: threadTitle });\n await session.thread.setSetting({ key: 'factorySessionId', value: ensured.sessionId });\n\n await primeTenantCredentialsForTenant({\n orgId: scope.orgId,\n userId: scope.userId,\n credentialScope: sessionModel.modelScope,\n });\n // How its runs end reaches this session through the listener every Factory\n // session gets (`observeSessionRunReports`), which follows the row's origin.\n // `requireDelivery` makes `accepted` reject when the wake fails; the run itself is not awaited.\n await session.sendSignal(\n { id: globalThis.crypto.randomUUID(), type: 'user', tagName: 'user', contents: prompt },\n { requestContext: kickoffContext, requireDelivery: true },\n ).accepted;\n\n return { mode: 'side' as const, sessionId: ensured.sessionId, threadId, title: threadTitle };\n },\n });\n\n async function startWorkItemRole({\n prompt,\n title,\n workItem,\n role,\n }: {\n prompt: string;\n title?: string;\n workItem: string;\n role?: string;\n }) {\n if (!role) throw new Error('Pass the role to start on the work item.');\n const coordinator = deps.startCoordinator();\n if (!coordinator) throw new Error(\"Factory sessions can't be started yet; try again shortly.\");\n const item = await findWorkItem(deps.workItems, scope, workItem);\n if (!item) throw workItemNotFound(workItem);\n const label = workItemLabel(item);\n const board = deps.boards.get(boardForWorkItem(item));\n if (!board?.phaseForRole(role)) throw new Error(`${label}'s board has no ${role} role.`);\n const seated = item.sessions[role];\n if (seated) {\n throw new Error(\n `${label} already has a ${role} session ${seated.sessionId}; message it with factory_signal_session.`,\n );\n }\n\n const parent = await deps.sessions.getBySessionId(scope.sessionId);\n const sourceControl = await deps.sessions.getSourceControlBySessionId(scope.sessionId);\n if (!parent || !sourceControl) throw new Error('This session has no Factory session row to start from.');\n const messaging = await deps.messaging();\n if (!messaging) throw new Error('Starting sessions is not available on this server.');\n const project = await deps.sessionModel.projects.get({ orgId: scope.orgId, id: scope.factoryProjectId });\n if (!project) throw new Error('This Factory no longer exists.');\n // A work-item start is a run, so a hybrid Factory uses the Factory's scope (as the Start route does).\n const modelScope = effectiveModelScope(project.modelScope, 'run');\n const projectRepositoryId = await resolveRepository(sourceControl, item);\n\n const sessionId = globalThis.crypto.randomUUID();\n const ensured = await ensureFactorySourceSession({\n sourceControl,\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n sessionId,\n branch: `${USER_SESSION_BRANCH_PREFIX}${sessionId}`,\n projectRepositoryId,\n attributeToUserId: scope.userId,\n visibility: parent.visibility,\n });\n\n // Follow first, so a fast run's first report already has somewhere to go.\n const followScope = { orgId: scope.orgId, factoryProjectId: scope.factoryProjectId };\n const alreadyFollowing = (\n await deps.workItems.listSessionSubscriptions({ ...followScope, sessionId: scope.sessionId })\n ).some(subscription => subscription.workItemId === item.id);\n await deps.workItems.subscribeToWorkItem({\n ...followScope,\n workItemId: item.id,\n sessionId: scope.sessionId,\n threadId: scope.threadId,\n resourceId: scope.resourceId,\n userId: scope.userId,\n reason: 'started',\n });\n\n const startedAt = new Date();\n let prepared;\n try {\n prepared = await coordinator.prepare({\n orgId: scope.orgId,\n userId: scope.userId,\n factoryProjectId: scope.factoryProjectId,\n sessionId: ensured.sessionId,\n threadTitle: title ?? `${label} ${role}`,\n kickoffKey: globalThis.crypto.randomUUID(),\n modelScope,\n defaultModelId: modelScope === 'factory' ? (project.defaultModelId ?? undefined) : undefined,\n workItem: {\n id: item.id,\n role,\n input: {\n externalSource: item.externalSource,\n parentWorkItemId: item.parentWorkItemId,\n title: item.title,\n stages: item.stages,\n sessions: item.sessions,\n metadata: item.metadata,\n },\n },\n });\n } catch (error) {\n if (!alreadyFollowing) {\n await deps.workItems.unsubscribeFromWorkItem({\n orgId: scope.orgId,\n workItemId: item.id,\n sessionId: scope.sessionId,\n });\n }\n const message = startErrorMessage(error);\n if (message) throw new Error(message);\n throw error;\n }\n\n const bindings = await deps.workItems.listRunBindings(scope.orgId, scope.factoryProjectId, item.id);\n const replaced = bindings.find(\n binding =>\n binding.role === role &&\n binding.id !== prepared.bindingId &&\n binding.revokedAt !== null &&\n binding.revokedAt >= startedAt,\n );\n\n const session = await messaging.controller.getSessionByResource(prepared.resourceId);\n if (!session) throw new Error(`The ${role} session was bound but is not running; open it from the board.`);\n const kickoffId = globalThis.crypto.randomUUID();\n // Heartbeat from before the kickoff so a fast run's end is seen; the open-run\n // entry and audit follow the accepted kickoff, as the dispatcher records them.\n // The run's end reaches this item's followers through the session's listener.\n const heartbeat = heartbeatOpenRunUntilEnd(session, kickoffId);\n const kickoffContext = new RequestContext();\n kickoffContext.set('user', { workosId: scope.userId, organizationId: scope.orgId });\n try {\n await session.sendSignal(\n { id: kickoffId, type: 'user', tagName: 'user', contents: prompt },\n { requestContext: kickoffContext, requireDelivery: true },\n ).accepted;\n } catch (error) {\n heartbeat.stop();\n throw error;\n }\n\n await recordSessionRunStart(session, {\n audit: deps.auditRecorder,\n actorType: 'human',\n observedEnd: heartbeat.endReason,\n run: {\n kickoffId,\n ownerId: FACTORY_TOOLS_OWNER_ID,\n heartbeatAt: Date.now(),\n bindingId: prepared.bindingId,\n role,\n startedBy: scope.userId,\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n workItemId: item.id,\n workItemName: item.title,\n sessionId: prepared.sessionId,\n threadId: prepared.threadId,\n branch: prepared.branch,\n },\n });\n\n const number = workItemNumber(item);\n return {\n mode: 'work-item' as const,\n workItem: { id: item.id, ...(number === null ? {} : { number }) },\n role,\n sessionId: prepared.sessionId,\n threadId: prepared.threadId,\n bindingId: prepared.bindingId,\n ...(replaced ? { replacedSessionId: replaced.sessionId } : {}),\n };\n }\n\n async function resolveRepository(sourceControl: SourceControlStorageHandle, item: WorkItemRow): Promise<string> {\n const intakeConfig = await deps.intake?.getConfig({ orgId: scope.orgId, integrationIds: ['linear'] });\n const repository = await resolveWorkItemRepository({\n sourceControl,\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n item,\n linearRepositoryMap: intakeConfig?.linear?.repositoryByLinearProject,\n });\n if (repository.status === 'ambiguous') {\n throw new Error(ambiguousRepository(repository.candidates));\n }\n if (repository.status === 'unlinked') throw new Error(repository.hint);\n return repository.projectRepositoryId;\n }\n\n return { factory_start_session: start };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { NotificationsStorage } from '@mastra/core/notifications';
|
|
2
|
+
import type { ComputeStateSignalArgs, ComputeStateSignalResult, Processor } from '@mastra/core/processors';
|
|
3
|
+
import type { BoardRegistry } from '../boards/registry.js';
|
|
4
|
+
import type { SourceControlSessionLookup } from '../session/factory-session.js';
|
|
5
|
+
import type { WorkItemsStorage } from '../storage/domains/work-items/base.js';
|
|
6
|
+
import type { ResolveFactoryToolScopeOptions } from './scope.js';
|
|
7
|
+
/** Finished side sessions and items that reached a terminal stage drop out after this long. */
|
|
8
|
+
export declare const FACTORY_SESSIONS_MAX_AGE_MS: number;
|
|
9
|
+
/** Rows shown before the rest collapse into "+N more". */
|
|
10
|
+
export declare const FACTORY_SESSIONS_MAX_ROWS = 20;
|
|
11
|
+
export type FactorySessionRunState = 'running' | 'idle' | 'parked';
|
|
12
|
+
export interface FactorySessionsStateDependencies {
|
|
13
|
+
authEnabled: boolean;
|
|
14
|
+
sessions: Pick<SourceControlSessionLookup, 'getBySessionId' | 'listByOrigin'>;
|
|
15
|
+
projects: ResolveFactoryToolScopeOptions['projects'];
|
|
16
|
+
workItems: Pick<WorkItemsStorage, 'hasRunBindingForSession' | 'listSessionSubscriptions' | 'getForProject' | 'listRunBindings' | 'findActiveRunBindingForSession' | 'listWorkItemSubscribers'>;
|
|
17
|
+
boards: BoardRegistry;
|
|
18
|
+
/** Whether the session's agent is mid-run in this process; `idle` when it isn't live here. */
|
|
19
|
+
isRunning: (sessionId: string) => Promise<boolean>;
|
|
20
|
+
notifications: () => Promise<Pick<NotificationsStorage, 'listNotifications'> | null>;
|
|
21
|
+
now?: () => number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The `<factory-sessions>` state signal. A user session sees the side sessions
|
|
25
|
+
* it started and the work items it follows; a side session or item worker sees
|
|
26
|
+
* who it reports to. Sessions it only messaged never appear. Silent until there
|
|
27
|
+
* is something to show; when the last entry ages out it sends one final empty
|
|
28
|
+
* snapshot, then stays silent.
|
|
29
|
+
*/
|
|
30
|
+
export declare class FactorySessionsStateProcessor implements Processor<'factory-sessions'> {
|
|
31
|
+
#private;
|
|
32
|
+
readonly id = "factory-sessions";
|
|
33
|
+
readonly stateId = "factory-sessions";
|
|
34
|
+
constructor(deps: FactorySessionsStateDependencies);
|
|
35
|
+
computeStateSignal(args: ComputeStateSignalArgs): Promise<ComputeStateSignalResult>;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=sessions-state-processor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions-state-processor.d.ts","sourceRoot":"","sources":["../../src/tools/sessions-state-processor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAsB,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAC3F,OAAO,KAAK,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAG3G,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAChF,OAAO,KAAK,EAAe,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAI3F,OAAO,KAAK,EAAoB,8BAA8B,EAAE,MAAM,YAAY,CAAC;AAGnF,+FAA+F;AAC/F,eAAO,MAAM,2BAA2B,QAAsB,CAAC;AAC/D,0DAA0D;AAC1D,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAI5C,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEnE,MAAM,WAAW,gCAAgC;IAC/C,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,IAAI,CAAC,0BAA0B,EAAE,gBAAgB,GAAG,cAAc,CAAC,CAAC;IAC9E,QAAQ,EAAE,8BAA8B,CAAC,UAAU,CAAC,CAAC;IACrD,SAAS,EAAE,IAAI,CACb,gBAAgB,EACd,yBAAyB,GACzB,0BAA0B,GAC1B,eAAe,GACf,iBAAiB,GACjB,gCAAgC,GAChC,yBAAyB,CAC5B,CAAC;IACF,MAAM,EAAE,aAAa,CAAC;IACtB,8FAA8F;IAC9F,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,oBAAoB,EAAE,mBAAmB,CAAC,GAAG,IAAI,CAAC,CAAC;IACrF,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAsDD;;;;;;GAMG;AACH,qBAAa,6BAA8B,YAAW,SAAS,CAAC,kBAAkB,CAAC;;IACjF,QAAQ,CAAC,EAAE,sBAAY;IACvB,QAAQ,CAAC,OAAO,sBAAY;gBAGhB,IAAI,EAAE,gCAAgC;IAI5C,kBAAkB,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,wBAAwB,CAAC;CAoM1F"}
|