@mastra/factory 0.14.0-alpha.2 → 0.14.0-alpha.4
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/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +8 -3
- package/dist/factory.js.map +1 -1
- package/dist/integrations/github/session-subscriptions.d.ts +0 -1
- package/dist/integrations/github/session-subscriptions.d.ts.map +1 -1
- package/dist/integrations/github/session-subscriptions.js +3 -16
- package/dist/integrations/github/session-subscriptions.js.map +1 -1
- package/dist/integrations/workos/integration.js +1 -1
- package/dist/integrations/workos/integration.js.map +1 -1
- package/dist/routes/contracts.d.ts +2 -2
- package/dist/routes/surface.d.ts +2 -2
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +2 -1
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +10 -87
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/dispatcher.d.ts +2 -0
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +29 -0
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/transition-service.d.ts +11 -0
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +72 -2
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/session/run-audit.d.ts +49 -0
- package/dist/session/run-audit.d.ts.map +1 -0
- package/dist/session/run-audit.js +99 -0
- package/dist/session/run-audit.js.map +1 -0
- package/dist/session/shell-commands.d.ts +5 -0
- package/dist/session/shell-commands.d.ts.map +1 -0
- package/dist/session/shell-commands.js +25 -0
- package/dist/session/shell-commands.js.map +1 -0
- package/dist/storage/domains/audit/actions.d.ts +24 -0
- package/dist/storage/domains/audit/actions.d.ts.map +1 -0
- package/dist/storage/domains/audit/actions.js +67 -0
- package/dist/storage/domains/audit/actions.js.map +1 -0
- package/dist/storage/domains/audit/actors.d.ts +5 -0
- package/dist/storage/domains/audit/actors.d.ts.map +1 -0
- package/dist/storage/domains/audit/actors.js +25 -0
- package/dist/storage/domains/audit/actors.js.map +1 -0
- package/dist/storage/domains/audit/agent-audit.d.ts +5 -7
- package/dist/storage/domains/audit/agent-audit.d.ts.map +1 -1
- package/dist/storage/domains/audit/agent-audit.js +33 -24
- package/dist/storage/domains/audit/agent-audit.js.map +1 -1
- package/dist/storage/domains/audit/base.d.ts +28 -33
- package/dist/storage/domains/audit/base.d.ts.map +1 -1
- package/dist/storage/domains/audit/base.js +58 -31
- package/dist/storage/domains/audit/base.js.map +1 -1
- package/dist/storage/domains/audit/domain.d.ts +12 -9
- package/dist/storage/domains/audit/domain.d.ts.map +1 -1
- package/dist/storage/domains/audit/domain.js +32 -48
- package/dist/storage/domains/audit/domain.js.map +1 -1
- package/dist/storage/domains/audit/wire.d.ts +24 -0
- package/dist/storage/domains/audit/wire.d.ts.map +1 -0
- package/dist/storage/domains/audit/wire.js +16 -0
- package/dist/storage/domains/audit/wire.js.map +1 -0
- package/dist/supervisor/write-tools.d.ts +2 -2
- package/dist/supervisor/write-tools.d.ts.map +1 -1
- package/dist/supervisor/write-tools.js +1 -17
- package/dist/supervisor/write-tools.js.map +1 -1
- package/package.json +7 -7
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import { getFactoryAuthUser } from "../../../auth.js";
|
|
2
|
+
import { auditActionsInNamespaces, isAuditNamespace } from "./actions.js";
|
|
3
|
+
import { isHumanActorId } from "./actors.js";
|
|
4
|
+
import { ACTOR_PROFILE_METADATA_KEY, auditActorProfile, auditAgentName } from "./base.js";
|
|
5
|
+
import { toWireAuditEvent } from "./wire.js";
|
|
2
6
|
import { registerApiRoute } from "@mastra/core/server";
|
|
3
7
|
//#region src/storage/domains/audit/domain.ts
|
|
4
|
-
/**
|
|
5
|
-
* Reserved metadata key on `AuditEventRow.metadata` that carries the acting
|
|
6
|
-
* human user's display name and avatar captured from the auth context at
|
|
7
|
-
* record time. Read back by `#resolveActorProfiles` so names/avatars work
|
|
8
|
-
* for every auth provider without requiring an `IUserProvider.getUser(id)`
|
|
9
|
-
* implementation — the Studio provider proxies through the shared API and
|
|
10
|
-
* cannot resolve arbitrary users by id.
|
|
11
|
-
*/
|
|
12
|
-
const ACTOR_PROFILE_METADATA_KEY = "__actorProfile";
|
|
13
8
|
function readStoredActorProfile(metadata) {
|
|
14
9
|
if (!metadata) return void 0;
|
|
15
10
|
const raw = metadata[ACTOR_PROFILE_METADATA_KEY];
|
|
@@ -23,36 +18,15 @@ function readStoredActorProfile(metadata) {
|
|
|
23
18
|
...avatarUrl ? { avatarUrl } : {}
|
|
24
19
|
};
|
|
25
20
|
}
|
|
26
|
-
function buildActorProfileMetadata(user) {
|
|
27
|
-
const name = user.name?.trim() || user.email?.trim();
|
|
28
|
-
const avatarUrl = user.avatarUrl?.trim();
|
|
29
|
-
if (!name && !avatarUrl) return void 0;
|
|
30
|
-
return {
|
|
31
|
-
...name ? { name } : {},
|
|
32
|
-
...avatarUrl ? { avatarUrl } : {}
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
21
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
36
|
-
const MAX_ACTION_FILTERS = 16;
|
|
37
22
|
const MAX_ACTOR_PROFILES = 100;
|
|
38
|
-
const AUTOMATION_ACTORS = /* @__PURE__ */ new Set([
|
|
39
|
-
"factory",
|
|
40
|
-
"system",
|
|
41
|
-
"automation",
|
|
42
|
-
"factory-rule-dispatcher",
|
|
43
|
-
"factory-tool-result-rule"
|
|
44
|
-
]);
|
|
45
23
|
function loose(c) {
|
|
46
24
|
return c;
|
|
47
25
|
}
|
|
48
|
-
|
|
26
|
+
/** The actions a `namespaces=` query names: none when it names only unknown ones, `undefined` when it names nothing. */
|
|
27
|
+
function actionsInRequestedNamespaces(raw) {
|
|
49
28
|
if (!raw) return void 0;
|
|
50
|
-
|
|
51
|
-
return actions.length > 0 ? actions : void 0;
|
|
52
|
-
}
|
|
53
|
-
function isHumanActorId(actorId) {
|
|
54
|
-
if (!actorId) return false;
|
|
55
|
-
return !AUTOMATION_ACTORS.has(actorId) && !actorId.startsWith("agent:") && !actorId.startsWith("github:");
|
|
29
|
+
return auditActionsInNamespaces(raw.split(",").map((namespace) => namespace.trim()).filter(isAuditNamespace));
|
|
56
30
|
}
|
|
57
31
|
function parseActorIdsParam(raw) {
|
|
58
32
|
if (!raw) return [];
|
|
@@ -71,6 +45,13 @@ function auditRequestContext(c) {
|
|
|
71
45
|
...userAgent ? { userAgent } : {}
|
|
72
46
|
};
|
|
73
47
|
}
|
|
48
|
+
/** Who a browser request is and where it came from: the pair every funnel row carries. */
|
|
49
|
+
function auditRequestOrigin(c) {
|
|
50
|
+
return {
|
|
51
|
+
actorProfile: auditActorProfile(getFactoryAuthUser(c)),
|
|
52
|
+
context: auditRequestContext(c)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
74
55
|
/** Factory-owned audit behavior backed by the audit storage domain. */
|
|
75
56
|
var AuditDomain = class {
|
|
76
57
|
#auth;
|
|
@@ -96,7 +77,11 @@ var AuditDomain = class {
|
|
|
96
77
|
async record(input) {
|
|
97
78
|
try {
|
|
98
79
|
await this.#audit.ensureReady();
|
|
99
|
-
const row = await this.#audit.
|
|
80
|
+
const { event: row, created } = input.idempotencyKey ? await this.#audit.recordOnce(input) : {
|
|
81
|
+
event: await this.#audit.record(input),
|
|
82
|
+
created: true
|
|
83
|
+
};
|
|
84
|
+
if (!created) return row;
|
|
100
85
|
for (const sink of this.#sinks) {
|
|
101
86
|
if (!sink.audit) continue;
|
|
102
87
|
Promise.resolve().then(() => sink.audit?.({ event: row })).catch((err) => {
|
|
@@ -124,18 +109,13 @@ var AuditDomain = class {
|
|
|
124
109
|
try {
|
|
125
110
|
const tenant = this.#auth.tenant(context);
|
|
126
111
|
if (!tenant?.orgId) return;
|
|
127
|
-
const user = getFactoryAuthUser(context);
|
|
128
|
-
const actorProfile = user ? buildActorProfileMetadata(user) : void 0;
|
|
129
|
-
const metadata = actorProfile ? {
|
|
130
|
-
...input.metadata,
|
|
131
|
-
[ACTOR_PROFILE_METADATA_KEY]: actorProfile
|
|
132
|
-
} : input.metadata;
|
|
133
112
|
await this.record({
|
|
134
113
|
orgId: tenant.orgId,
|
|
135
114
|
actorId: tenant.userId,
|
|
115
|
+
actorProfile: auditActorProfile(getFactoryAuthUser(context)),
|
|
136
116
|
action: input.action,
|
|
137
117
|
targets: input.targets,
|
|
138
|
-
metadata,
|
|
118
|
+
metadata: input.metadata,
|
|
139
119
|
factoryProjectId: input.factoryProjectId,
|
|
140
120
|
projectRepositoryId: input.projectRepositoryId,
|
|
141
121
|
context: auditRequestContext(context)
|
|
@@ -167,7 +147,7 @@ var AuditDomain = class {
|
|
|
167
147
|
metadata: {
|
|
168
148
|
...input.metadata,
|
|
169
149
|
startedBy: userId,
|
|
170
|
-
...modeId ? { agentName:
|
|
150
|
+
...modeId ? { agentName: auditAgentName(modeId) } : {},
|
|
171
151
|
...modelId ? { modelId } : {}
|
|
172
152
|
},
|
|
173
153
|
factoryProjectId: state.factoryProjectId,
|
|
@@ -230,19 +210,23 @@ var AuditDomain = class {
|
|
|
230
210
|
orgId: tenant.orgId,
|
|
231
211
|
id: projectId
|
|
232
212
|
})) return c.json({ error: "Project not found" }, 404);
|
|
213
|
+
const actions = actionsInRequestedNamespaces(c.req.query("namespaces"));
|
|
214
|
+
if (actions?.length === 0) return c.json({ error: "unknown_namespaces" }, 400);
|
|
233
215
|
const page = await this.list({
|
|
234
216
|
orgId: tenant.orgId,
|
|
235
217
|
factoryProjectId: projectId,
|
|
236
|
-
actions
|
|
218
|
+
actions,
|
|
237
219
|
actorId: c.req.query("actor") || void 0,
|
|
238
220
|
before: c.req.query("before") || void 0,
|
|
239
221
|
limit: parseLimitParam(c.req.query("limit"))
|
|
240
222
|
});
|
|
241
223
|
const actors = await this.#resolveActorProfiles(page.events, parseActorIdsParam(c.req.query("actorIds")));
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
actors
|
|
245
|
-
|
|
224
|
+
const body = {
|
|
225
|
+
events: page.events.map(toWireAuditEvent),
|
|
226
|
+
actors,
|
|
227
|
+
...page.nextCursor ? { nextCursor: page.nextCursor } : {}
|
|
228
|
+
};
|
|
229
|
+
return c.json(body);
|
|
246
230
|
}
|
|
247
231
|
})];
|
|
248
232
|
}
|
|
@@ -261,6 +245,6 @@ var AuditDomain = class {
|
|
|
261
245
|
}
|
|
262
246
|
};
|
|
263
247
|
//#endregion
|
|
264
|
-
export { AuditDomain, auditRequestContext };
|
|
248
|
+
export { AuditDomain, auditRequestContext, auditRequestOrigin };
|
|
265
249
|
|
|
266
250
|
//# sourceMappingURL=domain.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domain.js","names":["#auth","#audit","#projects","#users","#sinks","#agentTenant","#resolveActorProfiles","#resolveTenant"],"sources":["../../../../src/storage/domains/audit/domain.ts"],"sourcesContent":["import type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport type { ApiRoute, IUserProvider } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport { getFactoryAuthUser } from '../../../auth.js';\nimport type { RouteAuth } from '../../../routes/route.js';\nimport type { FactoryProjectsStorage } from '../projects/base.js';\nimport type {\n AuditContext,\n AuditEventPage,\n AuditEventRow,\n AuditStorage,\n AuditTarget,\n ListAuditEventsInput,\n RecordAuditEventInput,\n} from './base.js';\n\nexport interface EmitAuditInput {\n action: string;\n factoryProjectId?: string;\n projectRepositoryId?: string;\n targets: AuditTarget[];\n metadata?: Record<string, unknown>;\n}\n\nexport interface EmitAgentAuditInput {\n action: string;\n targets: AuditTarget[];\n metadata?: Record<string, unknown>;\n}\n\nexport interface AuditEmitter {\n emit(args: { context: Context; input: EmitAuditInput }): Promise<void>;\n}\n\nexport interface AuditAgentEmitter {\n emitAgent(args: { requestContext: RequestContext; input: EmitAgentAuditInput }): Promise<void>;\n}\n\n/** Best-effort destination for locally persisted audit events (e.g. an integration's audit log). */\nexport interface AuditSink {\n id: string;\n audit?(args: { event: AuditEventRow }): Promise<void>;\n}\n\ninterface FactorySessionState {\n factoryProjectId?: string;\n projectRepositoryId?: string;\n}\n\nexport interface AuditActorProfile {\n id: string;\n name: string;\n avatarUrl?: string;\n}\n\n/**\n * Reserved metadata key on `AuditEventRow.metadata` that carries the acting\n * human user's display name and avatar captured from the auth context at\n * record time. Read back by `#resolveActorProfiles` so names/avatars work\n * for every auth provider without requiring an `IUserProvider.getUser(id)`\n * implementation — the Studio provider proxies through the shared API and\n * cannot resolve arbitrary users by id.\n */\nconst ACTOR_PROFILE_METADATA_KEY = '__actorProfile';\n\ninterface StoredActorProfile {\n name?: string;\n avatarUrl?: string;\n}\n\nfunction readStoredActorProfile(metadata: Record<string, unknown> | undefined): StoredActorProfile | undefined {\n if (!metadata) return undefined;\n const raw = metadata[ACTOR_PROFILE_METADATA_KEY];\n if (!raw || typeof raw !== 'object') return undefined;\n const profile = raw as Record<string, unknown>;\n const name = typeof profile.name === 'string' && profile.name.trim() ? profile.name.trim() : undefined;\n const avatarUrl =\n typeof profile.avatarUrl === 'string' && profile.avatarUrl.trim() ? profile.avatarUrl.trim() : undefined;\n if (!name && !avatarUrl) return undefined;\n return { ...(name ? { name } : {}), ...(avatarUrl ? { avatarUrl } : {}) };\n}\n\nfunction buildActorProfileMetadata(user: {\n name?: string;\n email?: string;\n avatarUrl?: string;\n}): StoredActorProfile | undefined {\n const name = user.name?.trim() || user.email?.trim();\n const avatarUrl = user.avatarUrl?.trim();\n if (!name && !avatarUrl) return undefined;\n return { ...(name ? { name } : {}), ...(avatarUrl ? { avatarUrl } : {}) };\n}\n\nexport interface AuditDomainOptions {\n auth: RouteAuth;\n /** Audit storage domain handle. */\n audit: AuditStorage;\n /** Projects domain handle, used to scope the audit trail route. */\n projects: FactoryProjectsStorage;\n /** Resolve persisted human actor ids to display names and profile images. */\n users?: Pick<IUserProvider, 'getUser' | 'getUsers'>;\n /** Best-effort fan-out destinations notified after each recorded event. */\n sinks?: AuditSink[];\n /** Resolve the acting tenant for agent-emitted events from the request context. */\n agentTenant?: (requestContext: RequestContext) => { orgId?: string; userId?: string } | undefined;\n}\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst MAX_ACTION_FILTERS = 16;\nconst MAX_ACTOR_PROFILES = 100;\nconst AUTOMATION_ACTORS = new Set([\n 'factory',\n 'system',\n 'automation',\n 'factory-rule-dispatcher',\n 'factory-tool-result-rule',\n]);\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\nfunction parseActionsParam(raw: string | undefined): string[] | undefined {\n if (!raw) return undefined;\n const actions = raw\n .split(',')\n .map(action => action.trim())\n .filter(Boolean)\n .slice(0, MAX_ACTION_FILTERS);\n return actions.length > 0 ? actions : undefined;\n}\n\nfunction isHumanActorId(actorId: string | undefined): actorId is string {\n if (!actorId) return false;\n return !AUTOMATION_ACTORS.has(actorId) && !actorId.startsWith('agent:') && !actorId.startsWith('github:');\n}\n\nfunction parseActorIdsParam(raw: string | undefined): string[] {\n if (!raw) return [];\n return [\n ...new Set(\n raw\n .split(',')\n .map(actorId => actorId.trim())\n .filter(isHumanActorId),\n ),\n ].slice(0, MAX_ACTOR_PROFILES);\n}\n\nfunction parseLimitParam(raw: string | undefined): number | undefined {\n if (!raw) return undefined;\n const limit = Number.parseInt(raw, 10);\n return Number.isFinite(limit) ? limit : undefined;\n}\n\nexport function auditRequestContext(c: Context): AuditContext {\n const location = c.req.header('x-forwarded-for')?.split(',')[0]?.trim();\n const userAgent = c.req.header('user-agent');\n return {\n ...(location ? { location } : {}),\n ...(userAgent ? { userAgent } : {}),\n };\n}\n\n/** Factory-owned audit behavior backed by the audit storage domain. */\nexport class AuditDomain implements AuditEmitter, AuditAgentEmitter {\n readonly #auth: RouteAuth;\n readonly #audit: AuditStorage;\n readonly #projects: FactoryProjectsStorage;\n readonly #users: AuditDomainOptions['users'];\n readonly #sinks: AuditSink[];\n readonly #agentTenant: AuditDomainOptions['agentTenant'];\n\n constructor({ auth, audit, projects, users, sinks = [], agentTenant }: AuditDomainOptions) {\n this.#auth = auth;\n this.#audit = audit;\n this.#projects = projects;\n this.#users = users;\n this.#sinks = sinks;\n this.#agentTenant = agentTenant;\n\n const ids = new Set<string>();\n for (const sink of this.#sinks) {\n if (!sink.id) throw new Error('Audit integration id must not be empty');\n if (ids.has(sink.id)) throw new Error(`Duplicate audit integration id '${sink.id}'`);\n ids.add(sink.id);\n }\n }\n\n async record(input: RecordAuditEventInput): Promise<AuditEventRow | null> {\n try {\n await this.#audit.ensureReady();\n const row = await this.#audit.record(input);\n for (const sink of this.#sinks) {\n if (!sink.audit) continue;\n void Promise.resolve()\n .then(() => sink.audit?.({ event: row }))\n .catch(err => {\n console.warn('[Audit] Audit integration failed', {\n integration: sink.id,\n action: row.action,\n error: err instanceof Error ? err.message : String(err),\n });\n });\n }\n return row;\n } catch (err) {\n console.warn('[Audit] Failed to record audit event', {\n action: input.action,\n error: err instanceof Error ? err.message : String(err),\n });\n return null;\n }\n }\n\n async list(input: ListAuditEventsInput): Promise<AuditEventPage> {\n await this.#audit.ensureReady();\n return this.#audit.list(input);\n }\n\n async emit({ context, input }: { context: Context; input: EmitAuditInput }): Promise<void> {\n try {\n const tenant = this.#auth.tenant(context);\n if (!tenant?.orgId) return;\n const user = getFactoryAuthUser(context);\n const actorProfile = user ? buildActorProfileMetadata(user) : undefined;\n const metadata = actorProfile\n ? { ...input.metadata, [ACTOR_PROFILE_METADATA_KEY]: actorProfile }\n : input.metadata;\n await this.record({\n orgId: tenant.orgId,\n actorId: tenant.userId,\n action: input.action,\n targets: input.targets,\n metadata,\n factoryProjectId: input.factoryProjectId,\n projectRepositoryId: input.projectRepositoryId,\n context: auditRequestContext(context),\n });\n } catch (err) {\n console.warn('[Audit] Failed to emit audit event', {\n action: input.action,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n async emitAgent({\n requestContext,\n input,\n }: {\n requestContext: RequestContext;\n input: EmitAgentAuditInput;\n }): Promise<void> {\n try {\n const context = requestContext.get('controller') as\n | AgentControllerRequestContext<FactorySessionState>\n | undefined;\n const tenant = this.#agentTenant?.(requestContext);\n const orgId = tenant?.orgId;\n const userId = tenant?.userId;\n const threadId = context?.threadId;\n const state = context?.getState();\n if (!orgId || !userId || !threadId || !state?.factoryProjectId) return;\n\n const modeId = context.session.modeId?.trim();\n const modelId = context.session.modelId?.trim();\n await this.record({\n orgId,\n actorId: `agent:${threadId}`,\n actorType: 'agent',\n action: input.action,\n targets: input.targets,\n metadata: {\n ...input.metadata,\n startedBy: userId,\n ...(modeId ? { agentName: `${modeId} agent` } : {}),\n ...(modelId ? { modelId } : {}),\n },\n factoryProjectId: state.factoryProjectId,\n projectRepositoryId: state.projectRepositoryId,\n context: {},\n });\n } catch (err) {\n console.warn('[Audit] Failed to emit agent audit event', {\n action: input.action,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n async #resolveActorProfiles(\n events: AuditEventRow[],\n requestedActorIds: string[] = [],\n ): Promise<Record<string, AuditActorProfile>> {\n const humanActorIds = [\n ...new Set(\n [\n ...requestedActorIds,\n ...events.filter(event => event.actorType === 'human').map(event => event.actorId),\n ].filter(isHumanActorId),\n ),\n ].slice(0, MAX_ACTOR_PROFILES);\n if (humanActorIds.length === 0) return {};\n\n // Prefer the profile stamped into event metadata at record time: it works\n // regardless of the auth provider's ability to resolve a user by id\n // (MastraAuthStudio's getUser() always returns null, WorkOS looks up by\n // id but every provider stamps here uniformly).\n const profiles: Record<string, AuditActorProfile> = {};\n for (const event of events) {\n if (event.actorType !== 'human') continue;\n if (!isHumanActorId(event.actorId)) continue;\n if (profiles[event.actorId]) continue;\n const stored = readStoredActorProfile(event.metadata);\n if (!stored?.name) continue;\n profiles[event.actorId] = {\n id: event.actorId,\n name: stored.name,\n ...(stored.avatarUrl ? { avatarUrl: stored.avatarUrl } : {}),\n };\n }\n\n const unresolved = humanActorIds.filter(actorId => !profiles[actorId]);\n if (unresolved.length === 0 || !this.#users) return profiles;\n\n try {\n const users = this.#users.getUsers\n ? await this.#users.getUsers(unresolved)\n : await Promise.all(unresolved.map(actorId => this.#users?.getUser(actorId) ?? null));\n for (const [index, user] of users.entries()) {\n if (!user) continue;\n const name = user.name?.trim() || user.email?.trim() || user.id;\n const actorId = unresolved[index] ?? user.id;\n profiles[actorId] = {\n id: user.id,\n name,\n ...(user.avatarUrl ? { avatarUrl: user.avatarUrl } : {}),\n };\n }\n } catch (err) {\n console.warn('[Audit] Failed to resolve audit actor profiles', {\n error: err instanceof Error ? err.message : String(err),\n });\n }\n return profiles;\n }\n\n routes(): ApiRoute[] {\n return [\n registerApiRoute('/web/factory/projects/:id/audit', {\n method: 'GET',\n handler: async cc => {\n const c = loose(cc);\n const tenant = await this.#resolveTenant(c);\n if ('response' in tenant) return tenant.response;\n\n const projectId = c.req.param('id');\n if (!projectId || !UUID_RE.test(projectId)) return c.json({ error: 'Project not found' }, 404);\n await this.#projects.ensureReady();\n const project = await this.#projects.get({ orgId: tenant.orgId, id: projectId });\n if (!project) return c.json({ error: 'Project not found' }, 404);\n\n const page = await this.list({\n orgId: tenant.orgId,\n factoryProjectId: projectId,\n actions: parseActionsParam(c.req.query('actions')),\n actorId: c.req.query('actor') || undefined,\n before: c.req.query('before') || undefined,\n limit: parseLimitParam(c.req.query('limit')),\n });\n const actors = await this.#resolveActorProfiles(page.events, parseActorIdsParam(c.req.query('actorIds')));\n return c.json({ ...page, actors });\n },\n }),\n ];\n }\n\n async #resolveTenant(c: Context): Promise<{ orgId: string; userId: string } | { response: Response }> {\n await this.#auth.ensureUser(c);\n const tenant = this.#auth.tenant(c);\n if (!tenant) return { response: c.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: c.json({ error: 'organization_required', message: 'The audit trail requires an organization.' }, 403),\n };\n }\n return { orgId: tenant.orgId, userId: tenant.userId };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"domain.js","names":["#auth","#audit","#projects","#users","#sinks","#agentTenant","#resolveActorProfiles","#resolveTenant"],"sources":["../../../../src/storage/domains/audit/domain.ts"],"sourcesContent":["import type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport type { ApiRoute, IUserProvider } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport { getFactoryAuthUser } from '../../../auth.js';\nimport type { RouteAuth } from '../../../routes/route.js';\nimport type { FactoryProjectsStorage } from '../projects/base.js';\nimport { auditActionsInNamespaces, isAuditNamespace } from './actions.js';\nimport type { AuditAction } from './actions.js';\nimport { isHumanActorId } from './actors.js';\nimport { ACTOR_PROFILE_METADATA_KEY, auditActorProfile, auditAgentName } from './base.js';\nimport type {\n AuditActorProfileInput,\n AuditContext,\n AuditEventPage,\n AuditEventRow,\n AuditStorage,\n AuditTarget,\n ListAuditEventsInput,\n RecordAuditEventInput,\n} from './base.js';\nimport { toWireAuditEvent } from './wire.js';\nimport type { WireAuditActor, WireAuditPage } from './wire.js';\n\nexport interface EmitAuditInput {\n action: AuditAction;\n factoryProjectId?: string;\n projectRepositoryId?: string;\n targets: AuditTarget[];\n metadata?: Record<string, unknown>;\n}\n\nexport interface EmitAgentAuditInput {\n action: AuditAction;\n targets: AuditTarget[];\n metadata?: Record<string, unknown>;\n}\n\nexport interface AuditEmitter {\n emit(args: { context: Context; input: EmitAuditInput }): Promise<void>;\n}\n\nexport interface AuditAgentEmitter {\n emitAgent(args: { requestContext: RequestContext; input: EmitAgentAuditInput }): Promise<void>;\n}\n\n/** Records with an explicit actor, for the paths that have no request: rule transitions, run starts, run ends, supervisor tools. */\nexport type AuditRecorder = Pick<AuditDomain, 'record'>;\n\n/** Best-effort destination for locally persisted audit events (e.g. an integration's audit log). */\nexport interface AuditSink {\n id: string;\n audit?(args: { event: AuditEventRow }): Promise<void>;\n}\n\ninterface FactorySessionState {\n factoryProjectId?: string;\n projectRepositoryId?: string;\n}\n\nfunction readStoredActorProfile(metadata: Record<string, unknown> | undefined): AuditActorProfileInput | undefined {\n if (!metadata) return undefined;\n const raw = metadata[ACTOR_PROFILE_METADATA_KEY];\n if (!raw || typeof raw !== 'object') return undefined;\n const profile = raw as Record<string, unknown>;\n const name = typeof profile.name === 'string' && profile.name.trim() ? profile.name.trim() : undefined;\n const avatarUrl =\n typeof profile.avatarUrl === 'string' && profile.avatarUrl.trim() ? profile.avatarUrl.trim() : undefined;\n if (!name && !avatarUrl) return undefined;\n return { ...(name ? { name } : {}), ...(avatarUrl ? { avatarUrl } : {}) };\n}\n\nexport interface AuditDomainOptions {\n auth: RouteAuth;\n /** Audit storage domain handle. */\n audit: AuditStorage;\n /** Projects domain handle, used to scope the audit trail route. */\n projects: FactoryProjectsStorage;\n /** Resolve persisted human actor ids to display names and profile images. */\n users?: Pick<IUserProvider, 'getUser' | 'getUsers'>;\n /** Best-effort fan-out destinations notified after each recorded event. */\n sinks?: AuditSink[];\n /** Resolve the acting tenant for agent-emitted events from the request context. */\n agentTenant?: (requestContext: RequestContext) => { orgId?: string; userId?: string } | undefined;\n}\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst MAX_ACTOR_PROFILES = 100;\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\n/** The actions a `namespaces=` query names: none when it names only unknown ones, `undefined` when it names nothing. */\nfunction actionsInRequestedNamespaces(raw: string | undefined): string[] | undefined {\n if (!raw) return undefined;\n return auditActionsInNamespaces(\n raw\n .split(',')\n .map(namespace => namespace.trim())\n .filter(isAuditNamespace),\n );\n}\n\nfunction parseActorIdsParam(raw: string | undefined): string[] {\n if (!raw) return [];\n return [\n ...new Set(\n raw\n .split(',')\n .map(actorId => actorId.trim())\n .filter(isHumanActorId),\n ),\n ].slice(0, MAX_ACTOR_PROFILES);\n}\n\nfunction parseLimitParam(raw: string | undefined): number | undefined {\n if (!raw) return undefined;\n const limit = Number.parseInt(raw, 10);\n return Number.isFinite(limit) ? limit : undefined;\n}\n\nexport function auditRequestContext(c: Context): AuditContext {\n const location = c.req.header('x-forwarded-for')?.split(',')[0]?.trim();\n const userAgent = c.req.header('user-agent');\n return {\n ...(location ? { location } : {}),\n ...(userAgent ? { userAgent } : {}),\n };\n}\n\n/** Who a browser request is and where it came from: the pair every funnel row carries. */\nexport function auditRequestOrigin(c: Context): {\n actorProfile: AuditActorProfileInput | undefined;\n context: AuditContext;\n} {\n return { actorProfile: auditActorProfile(getFactoryAuthUser(c)), context: auditRequestContext(c) };\n}\n\n/** Factory-owned audit behavior backed by the audit storage domain. */\nexport class AuditDomain implements AuditEmitter, AuditAgentEmitter {\n readonly #auth: RouteAuth;\n readonly #audit: AuditStorage;\n readonly #projects: FactoryProjectsStorage;\n readonly #users: AuditDomainOptions['users'];\n readonly #sinks: AuditSink[];\n readonly #agentTenant: AuditDomainOptions['agentTenant'];\n\n constructor({ auth, audit, projects, users, sinks = [], agentTenant }: AuditDomainOptions) {\n this.#auth = auth;\n this.#audit = audit;\n this.#projects = projects;\n this.#users = users;\n this.#sinks = sinks;\n this.#agentTenant = agentTenant;\n\n const ids = new Set<string>();\n for (const sink of this.#sinks) {\n if (!sink.id) throw new Error('Audit integration id must not be empty');\n if (ids.has(sink.id)) throw new Error(`Duplicate audit integration id '${sink.id}'`);\n ids.add(sink.id);\n }\n }\n\n async record(input: RecordAuditEventInput<AuditAction>): Promise<AuditEventRow | null> {\n try {\n await this.#audit.ensureReady();\n const { event: row, created } = input.idempotencyKey\n ? await this.#audit.recordOnce(input)\n : { event: await this.#audit.record(input), created: true };\n if (!created) return row;\n for (const sink of this.#sinks) {\n if (!sink.audit) continue;\n void Promise.resolve()\n .then(() => sink.audit?.({ event: row }))\n .catch(err => {\n console.warn('[Audit] Audit integration failed', {\n integration: sink.id,\n action: row.action,\n error: err instanceof Error ? err.message : String(err),\n });\n });\n }\n return row;\n } catch (err) {\n console.warn('[Audit] Failed to record audit event', {\n action: input.action,\n error: err instanceof Error ? err.message : String(err),\n });\n return null;\n }\n }\n\n async list(input: ListAuditEventsInput): Promise<AuditEventPage> {\n await this.#audit.ensureReady();\n return this.#audit.list(input);\n }\n\n async emit({ context, input }: { context: Context; input: EmitAuditInput }): Promise<void> {\n try {\n const tenant = this.#auth.tenant(context);\n if (!tenant?.orgId) return;\n await this.record({\n orgId: tenant.orgId,\n actorId: tenant.userId,\n actorProfile: auditActorProfile(getFactoryAuthUser(context)),\n action: input.action,\n targets: input.targets,\n metadata: input.metadata,\n factoryProjectId: input.factoryProjectId,\n projectRepositoryId: input.projectRepositoryId,\n context: auditRequestContext(context),\n });\n } catch (err) {\n console.warn('[Audit] Failed to emit audit event', {\n action: input.action,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n async emitAgent({\n requestContext,\n input,\n }: {\n requestContext: RequestContext;\n input: EmitAgentAuditInput;\n }): Promise<void> {\n try {\n const context = requestContext.get('controller') as\n | AgentControllerRequestContext<FactorySessionState>\n | undefined;\n const tenant = this.#agentTenant?.(requestContext);\n const orgId = tenant?.orgId;\n const userId = tenant?.userId;\n const threadId = context?.threadId;\n const state = context?.getState();\n if (!orgId || !userId || !threadId || !state?.factoryProjectId) return;\n\n const modeId = context.session.modeId?.trim();\n const modelId = context.session.modelId?.trim();\n await this.record({\n orgId,\n actorId: `agent:${threadId}`,\n actorType: 'agent',\n action: input.action,\n targets: input.targets,\n metadata: {\n ...input.metadata,\n startedBy: userId,\n ...(modeId ? { agentName: auditAgentName(modeId) } : {}),\n ...(modelId ? { modelId } : {}),\n },\n factoryProjectId: state.factoryProjectId,\n projectRepositoryId: state.projectRepositoryId,\n context: {},\n });\n } catch (err) {\n console.warn('[Audit] Failed to emit agent audit event', {\n action: input.action,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n async #resolveActorProfiles(\n events: AuditEventRow[],\n requestedActorIds: string[] = [],\n ): Promise<Record<string, WireAuditActor>> {\n const humanActorIds = [\n ...new Set(\n [\n ...requestedActorIds,\n ...events.filter(event => event.actorType === 'human').map(event => event.actorId),\n ].filter(isHumanActorId),\n ),\n ].slice(0, MAX_ACTOR_PROFILES);\n if (humanActorIds.length === 0) return {};\n\n // Prefer the profile stamped into event metadata at record time: it works\n // regardless of the auth provider's ability to resolve a user by id\n // (MastraAuthStudio's getUser() always returns null, WorkOS looks up by\n // id but every provider stamps here uniformly).\n const profiles: Record<string, WireAuditActor> = {};\n for (const event of events) {\n if (event.actorType !== 'human') continue;\n if (!isHumanActorId(event.actorId)) continue;\n if (profiles[event.actorId]) continue;\n const stored = readStoredActorProfile(event.metadata);\n if (!stored?.name) continue;\n profiles[event.actorId] = {\n id: event.actorId,\n name: stored.name,\n ...(stored.avatarUrl ? { avatarUrl: stored.avatarUrl } : {}),\n };\n }\n\n const unresolved = humanActorIds.filter(actorId => !profiles[actorId]);\n if (unresolved.length === 0 || !this.#users) return profiles;\n\n try {\n const users = this.#users.getUsers\n ? await this.#users.getUsers(unresolved)\n : await Promise.all(unresolved.map(actorId => this.#users?.getUser(actorId) ?? null));\n for (const [index, user] of users.entries()) {\n if (!user) continue;\n const name = user.name?.trim() || user.email?.trim() || user.id;\n const actorId = unresolved[index] ?? user.id;\n profiles[actorId] = {\n id: user.id,\n name,\n ...(user.avatarUrl ? { avatarUrl: user.avatarUrl } : {}),\n };\n }\n } catch (err) {\n console.warn('[Audit] Failed to resolve audit actor profiles', {\n error: err instanceof Error ? err.message : String(err),\n });\n }\n return profiles;\n }\n\n routes(): ApiRoute[] {\n return [\n registerApiRoute('/web/factory/projects/:id/audit', {\n method: 'GET',\n handler: async cc => {\n const c = loose(cc);\n const tenant = await this.#resolveTenant(c);\n if ('response' in tenant) return tenant.response;\n\n const projectId = c.req.param('id');\n if (!projectId || !UUID_RE.test(projectId)) return c.json({ error: 'Project not found' }, 404);\n await this.#projects.ensureReady();\n const project = await this.#projects.get({ orgId: tenant.orgId, id: projectId });\n if (!project) return c.json({ error: 'Project not found' }, 404);\n\n const actions = actionsInRequestedNamespaces(c.req.query('namespaces'));\n if (actions?.length === 0) return c.json({ error: 'unknown_namespaces' }, 400);\n\n const page = await this.list({\n orgId: tenant.orgId,\n factoryProjectId: projectId,\n actions,\n actorId: c.req.query('actor') || undefined,\n before: c.req.query('before') || undefined,\n limit: parseLimitParam(c.req.query('limit')),\n });\n const actors = await this.#resolveActorProfiles(page.events, parseActorIdsParam(c.req.query('actorIds')));\n const body: WireAuditPage = {\n events: page.events.map(toWireAuditEvent),\n actors,\n ...(page.nextCursor ? { nextCursor: page.nextCursor } : {}),\n };\n return c.json(body);\n },\n }),\n ];\n }\n\n async #resolveTenant(c: Context): Promise<{ orgId: string; userId: string } | { response: Response }> {\n await this.#auth.ensureUser(c);\n const tenant = this.#auth.tenant(c);\n if (!tenant) return { response: c.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: c.json({ error: 'organization_required', message: 'The audit trail requires an organization.' }, 403),\n };\n }\n return { orgId: tenant.orgId, userId: tenant.userId };\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AuditActorType } from './actors.js';
|
|
2
|
+
import type { AuditEventRow, AuditTarget } from './base.js';
|
|
3
|
+
export interface WireAuditActor {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
avatarUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
/** An `AuditEventRow` as the list route sends it: the date as ISO text, tenancy and request context left behind. */
|
|
9
|
+
export interface WireAuditEvent {
|
|
10
|
+
id: string;
|
|
11
|
+
actorId: string;
|
|
12
|
+
actorType: AuditActorType;
|
|
13
|
+
action: string;
|
|
14
|
+
targets: AuditTarget[];
|
|
15
|
+
metadata: Record<string, unknown>;
|
|
16
|
+
occurredAt: string;
|
|
17
|
+
}
|
|
18
|
+
export interface WireAuditPage {
|
|
19
|
+
events: WireAuditEvent[];
|
|
20
|
+
actors: Record<string, WireAuditActor>;
|
|
21
|
+
nextCursor?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function toWireAuditEvent(row: AuditEventRow): WireAuditEvent;
|
|
24
|
+
//# sourceMappingURL=wire.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/audit/wire.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE5D,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,oHAAoH;AACpH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,cAAc,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAUnE"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/storage/domains/audit/wire.ts
|
|
2
|
+
function toWireAuditEvent(row) {
|
|
3
|
+
return {
|
|
4
|
+
id: row.id,
|
|
5
|
+
actorId: row.actorId,
|
|
6
|
+
actorType: row.actorType,
|
|
7
|
+
action: row.action,
|
|
8
|
+
targets: row.targets,
|
|
9
|
+
metadata: row.metadata,
|
|
10
|
+
occurredAt: row.occurredAt.toISOString()
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { toWireAuditEvent };
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=wire.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire.js","names":[],"sources":["../../../../src/storage/domains/audit/wire.ts"],"sourcesContent":["import type { AuditActorType } from './actors.js';\nimport type { AuditEventRow, AuditTarget } from './base.js';\n\nexport interface WireAuditActor {\n id: string;\n name: string;\n avatarUrl?: string;\n}\n\n/** An `AuditEventRow` as the list route sends it: the date as ISO text, tenancy and request context left behind. */\nexport interface WireAuditEvent {\n id: string;\n actorId: string;\n actorType: AuditActorType;\n action: string;\n targets: AuditTarget[];\n metadata: Record<string, unknown>;\n occurredAt: string;\n}\n\nexport interface WireAuditPage {\n events: WireAuditEvent[];\n actors: Record<string, WireAuditActor>;\n nextCursor?: string;\n}\n\nexport function toWireAuditEvent(row: AuditEventRow): WireAuditEvent {\n return {\n id: row.id,\n actorId: row.actorId,\n actorType: row.actorType,\n action: row.action,\n targets: row.targets,\n metadata: row.metadata,\n occurredAt: row.occurredAt.toISOString(),\n };\n}\n"],"mappings":";AA0BA,SAAgB,iBAAiB,KAAoC;CACnE,OAAO;EACL,IAAI,IAAI;EACR,SAAS,IAAI;EACb,WAAW,IAAI;EACf,QAAQ,IAAI;EACZ,SAAS,IAAI;EACb,UAAU,IAAI;EACd,YAAY,IAAI,WAAW,YAAY;CACzC;AACF"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { IntegrationTools } from '../integrations/base.js';
|
|
2
2
|
import type { FactoryTransitionService } from '../rules/transition-service.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { AuditRecorder } from '../storage/domains/audit/domain.js';
|
|
4
4
|
import type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';
|
|
5
5
|
import type { SupervisorScope } from './read-tools.js';
|
|
6
6
|
interface SupervisorWriteDependencies {
|
|
7
7
|
scope: SupervisorScope;
|
|
8
8
|
userId: string;
|
|
9
9
|
workItems: WorkItemsStorage;
|
|
10
|
-
audit:
|
|
10
|
+
audit: AuditRecorder;
|
|
11
11
|
transitionService: FactoryTransitionService;
|
|
12
12
|
reconcileAcceptanceLabels?: (input: {
|
|
13
13
|
orgId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"write-tools.d.ts","sourceRoot":"","sources":["../../src/supervisor/write-tools.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"write-tools.d.ts","sourceRoot":"","sources":["../../src/supervisor/write-tools.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAG/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC3F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,UAAU,2BAA2B;IACnC,KAAK,EAAE,eAAe,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,gBAAgB,CAAC;IAC5B,KAAK,EAAE,aAAa,CAAC;IACrB,iBAAiB,EAAE,wBAAwB,CAAC;IAC5C,yBAAyB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,WAAW,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACpG,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,wBAAgB,iCAAiC,CAAC,IAAI,EAAE,2BAA2B,GAAG,gBAAgB,CA4LrG"}
|
|
@@ -95,8 +95,7 @@ function createFactorySupervisorWriteTools(deps) {
|
|
|
95
95
|
id: workItemId
|
|
96
96
|
});
|
|
97
97
|
if (!item || item.factoryProjectId !== deps.scope.factoryProjectId) throw new Error("Work item not found.");
|
|
98
|
-
|
|
99
|
-
if (!from) throw new Error("The work item does not have one valid Factory stage.");
|
|
98
|
+
if (!(item.stages.length === 1 ? item.stages[0] : void 0)) throw new Error("The work item does not have one valid Factory stage.");
|
|
100
99
|
const result = await deps.transitionService.transition({
|
|
101
100
|
orgId: deps.scope.orgId,
|
|
102
101
|
factoryProjectId: deps.scope.factoryProjectId,
|
|
@@ -114,21 +113,6 @@ function createFactorySupervisorWriteTools(deps) {
|
|
|
114
113
|
},
|
|
115
114
|
cause: "supervisor"
|
|
116
115
|
});
|
|
117
|
-
await audit(result.status === "accepted" ? "factory.work_item.stage_moved" : "factory.work_item.transition_rejected", {
|
|
118
|
-
type: "work_item",
|
|
119
|
-
id: workItemId
|
|
120
|
-
}, result.status === "accepted" ? {
|
|
121
|
-
from,
|
|
122
|
-
to: result.stage,
|
|
123
|
-
revision: result.revision,
|
|
124
|
-
transitionId: result.transitionId
|
|
125
|
-
} : {
|
|
126
|
-
from,
|
|
127
|
-
to: stage,
|
|
128
|
-
code: result.code,
|
|
129
|
-
reason: result.reason,
|
|
130
|
-
transitionId: result.transitionId
|
|
131
|
-
});
|
|
132
116
|
if (result.status !== "accepted") throw new Error(`The transition was rejected (${result.code}): ${result.reason}`);
|
|
133
117
|
return result;
|
|
134
118
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"write-tools.js","names":[],"sources":["../../src/supervisor/write-tools.ts"],"sourcesContent":["import { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport { boardForWorkItem } from '../boards/index.js';\nimport type { IntegrationTools } from '../integrations/base.js';\nimport type { FactoryTransitionService } from '../rules/transition-service.js';\nimport { BOARD_IDENTIFIER_RE, MAX_BOARD_IDENTIFIER_LENGTH } from '../rules/validation.js';\nimport type { AuditStorage } from '../storage/domains/audit/base.js';\nimport type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { SupervisorScope } from './read-tools.js';\n\ninterface SupervisorWriteDependencies {\n scope: SupervisorScope;\n userId: string;\n workItems: WorkItemsStorage;\n audit: AuditStorage;\n transitionService: FactoryTransitionService;\n reconcileAcceptanceLabels?: (input: { orgId: string; factoryProjectId: string; item: WorkItemRow }) => Promise<void>;\n signalSession?: (input: { sessionId: string; message: string; userId: string }) => Promise<unknown>;\n now?: () => Date;\n}\n\nexport function createFactorySupervisorWriteTools(deps: SupervisorWriteDependencies): IntegrationTools {\n const now = deps.now ?? (() => new Date());\n const audit = async (action: string, target: { type: string; id: string }, metadata: Record<string, unknown> = {}) =>\n deps.audit.record({\n orgId: deps.scope.orgId,\n actorId: deps.userId,\n actorType: 'human',\n action,\n targets: [target],\n factoryProjectId: deps.scope.factoryProjectId,\n metadata: { ...metadata, cause: 'supervisor' },\n occurredAt: now(),\n });\n\n return {\n factory_retry_decision: createTool({\n id: 'factory_retry_decision',\n description: 'Retry one failed Factory decision after the person confirms the repair.',\n inputSchema: z.object({ decisionId: z.string().min(1) }),\n requireApproval: true,\n execute: async ({ decisionId }) => {\n const decision = await deps.workItems.retryDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n now(),\n );\n if (!decision) throw new Error('The decision is not failed, no longer exists, or belongs to another factory.');\n await audit(\n 'factory.run.retry',\n { type: 'rule_decision', id: decisionId },\n { workItemId: decision.workItemId },\n );\n return { decisionId, status: decision.status, workItemId: decision.workItemId };\n },\n }),\n factory_dismiss_decision: createTool({\n id: 'factory_dismiss_decision',\n description: 'Dismiss one parked Factory proposal after the person confirms it is no longer wanted.',\n inputSchema: z.object({ decisionId: z.string().min(1) }),\n requireApproval: true,\n execute: async ({ decisionId }) => {\n const decision = await deps.workItems.dismissDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n now(),\n );\n if (!decision)\n throw new Error('The decision is not proposed, no longer exists, or belongs to another factory.');\n await audit(\n 'factory.run.dismissed',\n { type: 'rule_decision', id: decisionId },\n { workItemId: decision.workItemId },\n );\n return { decisionId, status: decision.status, workItemId: decision.workItemId };\n },\n }),\n factory_resolve_proposal: createTool({\n id: 'factory_resolve_proposal',\n description: 'Approve or dismiss one proposed Factory decision after the person confirms the choice.',\n inputSchema: z.object({ decisionId: z.string().min(1), resolution: z.enum(['approve', 'dismiss']) }),\n requireApproval: true,\n execute: async ({ decisionId, resolution }) => {\n const decision =\n resolution === 'approve'\n ? await deps.workItems.approveDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n now(),\n deps.userId,\n )\n : await deps.workItems.dismissDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n now(),\n );\n if (!decision) throw new Error('The proposal is no longer open or belongs to another factory.');\n await audit(\n resolution === 'approve' ? 'factory.run.approved' : 'factory.run.dismissed',\n {\n type: 'rule_decision',\n id: decisionId,\n },\n { workItemId: decision.workItemId },\n );\n return { decisionId, resolution, status: decision.status, workItemId: decision.workItemId };\n },\n }),\n factory_transition_work_item: createTool({\n id: 'factory_transition_work_item',\n description: 'Move or accept one Factory work item after the person confirms the destination stage.',\n inputSchema: z.object({\n workItemId: z.string().min(1),\n stage: z.string().max(MAX_BOARD_IDENTIFIER_LENGTH).regex(BOARD_IDENTIFIER_RE),\n }),\n requireApproval: true,\n execute: async ({ workItemId, stage }) => {\n const item = await deps.workItems.get({ orgId: deps.scope.orgId, id: workItemId });\n if (!item || item.factoryProjectId !== deps.scope.factoryProjectId) throw new Error('Work item not found.');\n const from = item.stages.length === 1 ? item.stages[0] : undefined;\n if (!from) throw new Error('The work item does not have one valid Factory stage.');\n const result = await deps.transitionService.transition({\n orgId: deps.scope.orgId,\n factoryProjectId: deps.scope.factoryProjectId,\n workItemId,\n board: boardForWorkItem(item),\n stage,\n expectedRevision: item.revision,\n actor: { type: 'human', id: deps.userId },\n ingress: { type: 'human', identity: `supervisor:${deps.userId}:${workItemId}:${item.revision}:${stage}` },\n cause: 'supervisor',\n });\n await audit(\n result.status === 'accepted' ? 'factory.work_item.stage_moved' : 'factory.work_item.transition_rejected',\n { type: 'work_item', id: workItemId },\n result.status === 'accepted'\n ? { from, to: result.stage, revision: result.revision, transitionId: result.transitionId }\n : { from, to: stage, code: result.code, reason: result.reason, transitionId: result.transitionId },\n );\n if (result.status !== 'accepted') {\n throw new Error(`The transition was rejected (${result.code}): ${result.reason}`);\n }\n return result;\n },\n }),\n factory_reconcile_labels: createTool({\n id: 'factory_reconcile_labels',\n description: 'Reconcile stale acceptance labels for one accepted work item after the person confirms the repair.',\n inputSchema: z.object({ workItemId: z.string().min(1) }),\n requireApproval: true,\n execute: async ({ workItemId }) => {\n if (!deps.reconcileAcceptanceLabels) throw new Error('Acceptance-label reconciliation is unavailable.');\n const item = await deps.workItems.get({ orgId: deps.scope.orgId, id: workItemId });\n if (!item || item.factoryProjectId !== deps.scope.factoryProjectId) throw new Error('Work item not found.');\n if (!item.acceptedAt) throw new Error('The work item has not been accepted.');\n await deps.reconcileAcceptanceLabels({ ...deps.scope, item });\n await audit('factory.work_item.labels_reconciled', { type: 'work_item', id: workItemId });\n return { workItemId, reconciled: true };\n },\n }),\n factory_revoke_binding: createTool({\n id: 'factory_revoke_binding',\n description: 'Revoke an orphaned or stale run binding after a health finding and person confirmation.',\n inputSchema: z.object({ bindingId: z.string().min(1) }),\n requireApproval: true,\n execute: async ({ bindingId }) => {\n const binding = await deps.workItems.revokeRunBinding({\n orgId: deps.scope.orgId,\n factoryProjectId: deps.scope.factoryProjectId,\n bindingId,\n revokedAt: now(),\n });\n if (!binding) throw new Error('The binding is not active, no longer exists, or belongs to another factory.');\n await audit(\n 'factory.intake.binding_updated',\n { type: 'run_binding', id: bindingId },\n {\n workItemId: binding.workItemId,\n role: binding.role,\n status: 'revoked',\n },\n );\n return { bindingId, status: binding.status, workItemId: binding.workItemId, role: binding.role };\n },\n }),\n factory_signal_session: createTool({\n id: 'factory_signal_session',\n description: 'Send bounded guidance to a worker session after the person confirms the exact message.',\n inputSchema: z.object({ sessionId: z.string().min(1), message: z.string().trim().min(1).max(2000) }),\n requireApproval: true,\n execute: async ({ sessionId, message }) => {\n if (!deps.signalSession) throw new Error('Worker session signaling is unavailable.');\n const bindings = await deps.workItems.listRunBindings(deps.scope.orgId, deps.scope.factoryProjectId);\n const binding = bindings.find(row => row.sessionId === sessionId);\n if (!binding) throw new Error('The session does not belong to this factory.');\n await deps.signalSession({ sessionId, message, userId: deps.userId });\n await audit(\n 'factory.agent.signaled',\n { type: 'factory_session', id: sessionId },\n {\n workItemId: binding.workItemId,\n role: binding.role,\n },\n );\n return { sessionId, delivered: true, workItemId: binding.workItemId, role: binding.role };\n },\n }),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"write-tools.js","names":[],"sources":["../../src/supervisor/write-tools.ts"],"sourcesContent":["import { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport { boardForWorkItem } from '../boards/index.js';\nimport type { IntegrationTools } from '../integrations/base.js';\nimport type { FactoryTransitionService } from '../rules/transition-service.js';\nimport { BOARD_IDENTIFIER_RE, MAX_BOARD_IDENTIFIER_LENGTH } from '../rules/validation.js';\nimport type { AuditAction } from '../storage/domains/audit/actions.js';\nimport type { AuditRecorder } from '../storage/domains/audit/domain.js';\nimport type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { SupervisorScope } from './read-tools.js';\n\ninterface SupervisorWriteDependencies {\n scope: SupervisorScope;\n userId: string;\n workItems: WorkItemsStorage;\n audit: AuditRecorder;\n transitionService: FactoryTransitionService;\n reconcileAcceptanceLabels?: (input: { orgId: string; factoryProjectId: string; item: WorkItemRow }) => Promise<void>;\n signalSession?: (input: { sessionId: string; message: string; userId: string }) => Promise<unknown>;\n now?: () => Date;\n}\n\nexport function createFactorySupervisorWriteTools(deps: SupervisorWriteDependencies): IntegrationTools {\n const now = deps.now ?? (() => new Date());\n const audit = async (\n action: AuditAction,\n target: { type: string; id: string },\n metadata: Record<string, unknown> = {},\n ) =>\n deps.audit.record({\n orgId: deps.scope.orgId,\n actorId: deps.userId,\n actorType: 'human',\n action,\n targets: [target],\n factoryProjectId: deps.scope.factoryProjectId,\n metadata: { ...metadata, cause: 'supervisor' },\n occurredAt: now(),\n });\n\n return {\n factory_retry_decision: createTool({\n id: 'factory_retry_decision',\n description: 'Retry one failed Factory decision after the person confirms the repair.',\n inputSchema: z.object({ decisionId: z.string().min(1) }),\n requireApproval: true,\n execute: async ({ decisionId }) => {\n const decision = await deps.workItems.retryDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n now(),\n );\n if (!decision) throw new Error('The decision is not failed, no longer exists, or belongs to another factory.');\n await audit(\n 'factory.run.retry',\n { type: 'rule_decision', id: decisionId },\n { workItemId: decision.workItemId },\n );\n return { decisionId, status: decision.status, workItemId: decision.workItemId };\n },\n }),\n factory_dismiss_decision: createTool({\n id: 'factory_dismiss_decision',\n description: 'Dismiss one parked Factory proposal after the person confirms it is no longer wanted.',\n inputSchema: z.object({ decisionId: z.string().min(1) }),\n requireApproval: true,\n execute: async ({ decisionId }) => {\n const decision = await deps.workItems.dismissDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n now(),\n );\n if (!decision)\n throw new Error('The decision is not proposed, no longer exists, or belongs to another factory.');\n await audit(\n 'factory.run.dismissed',\n { type: 'rule_decision', id: decisionId },\n { workItemId: decision.workItemId },\n );\n return { decisionId, status: decision.status, workItemId: decision.workItemId };\n },\n }),\n factory_resolve_proposal: createTool({\n id: 'factory_resolve_proposal',\n description: 'Approve or dismiss one proposed Factory decision after the person confirms the choice.',\n inputSchema: z.object({ decisionId: z.string().min(1), resolution: z.enum(['approve', 'dismiss']) }),\n requireApproval: true,\n execute: async ({ decisionId, resolution }) => {\n const decision =\n resolution === 'approve'\n ? await deps.workItems.approveDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n now(),\n deps.userId,\n )\n : await deps.workItems.dismissDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n now(),\n );\n if (!decision) throw new Error('The proposal is no longer open or belongs to another factory.');\n await audit(\n resolution === 'approve' ? 'factory.run.approved' : 'factory.run.dismissed',\n {\n type: 'rule_decision',\n id: decisionId,\n },\n { workItemId: decision.workItemId },\n );\n return { decisionId, resolution, status: decision.status, workItemId: decision.workItemId };\n },\n }),\n factory_transition_work_item: createTool({\n id: 'factory_transition_work_item',\n description: 'Move or accept one Factory work item after the person confirms the destination stage.',\n inputSchema: z.object({\n workItemId: z.string().min(1),\n stage: z.string().max(MAX_BOARD_IDENTIFIER_LENGTH).regex(BOARD_IDENTIFIER_RE),\n }),\n requireApproval: true,\n execute: async ({ workItemId, stage }) => {\n const item = await deps.workItems.get({ orgId: deps.scope.orgId, id: workItemId });\n if (!item || item.factoryProjectId !== deps.scope.factoryProjectId) throw new Error('Work item not found.');\n const from = item.stages.length === 1 ? item.stages[0] : undefined;\n if (!from) throw new Error('The work item does not have one valid Factory stage.');\n const result = await deps.transitionService.transition({\n orgId: deps.scope.orgId,\n factoryProjectId: deps.scope.factoryProjectId,\n workItemId,\n board: boardForWorkItem(item),\n stage,\n expectedRevision: item.revision,\n actor: { type: 'human', id: deps.userId },\n ingress: { type: 'human', identity: `supervisor:${deps.userId}:${workItemId}:${item.revision}:${stage}` },\n cause: 'supervisor',\n });\n if (result.status !== 'accepted') {\n throw new Error(`The transition was rejected (${result.code}): ${result.reason}`);\n }\n return result;\n },\n }),\n factory_reconcile_labels: createTool({\n id: 'factory_reconcile_labels',\n description: 'Reconcile stale acceptance labels for one accepted work item after the person confirms the repair.',\n inputSchema: z.object({ workItemId: z.string().min(1) }),\n requireApproval: true,\n execute: async ({ workItemId }) => {\n if (!deps.reconcileAcceptanceLabels) throw new Error('Acceptance-label reconciliation is unavailable.');\n const item = await deps.workItems.get({ orgId: deps.scope.orgId, id: workItemId });\n if (!item || item.factoryProjectId !== deps.scope.factoryProjectId) throw new Error('Work item not found.');\n if (!item.acceptedAt) throw new Error('The work item has not been accepted.');\n await deps.reconcileAcceptanceLabels({ ...deps.scope, item });\n await audit('factory.work_item.labels_reconciled', { type: 'work_item', id: workItemId });\n return { workItemId, reconciled: true };\n },\n }),\n factory_revoke_binding: createTool({\n id: 'factory_revoke_binding',\n description: 'Revoke an orphaned or stale run binding after a health finding and person confirmation.',\n inputSchema: z.object({ bindingId: z.string().min(1) }),\n requireApproval: true,\n execute: async ({ bindingId }) => {\n const binding = await deps.workItems.revokeRunBinding({\n orgId: deps.scope.orgId,\n factoryProjectId: deps.scope.factoryProjectId,\n bindingId,\n revokedAt: now(),\n });\n if (!binding) throw new Error('The binding is not active, no longer exists, or belongs to another factory.');\n await audit(\n 'factory.intake.binding_updated',\n { type: 'run_binding', id: bindingId },\n {\n workItemId: binding.workItemId,\n role: binding.role,\n status: 'revoked',\n },\n );\n return { bindingId, status: binding.status, workItemId: binding.workItemId, role: binding.role };\n },\n }),\n factory_signal_session: createTool({\n id: 'factory_signal_session',\n description: 'Send bounded guidance to a worker session after the person confirms the exact message.',\n inputSchema: z.object({ sessionId: z.string().min(1), message: z.string().trim().min(1).max(2000) }),\n requireApproval: true,\n execute: async ({ sessionId, message }) => {\n if (!deps.signalSession) throw new Error('Worker session signaling is unavailable.');\n const bindings = await deps.workItems.listRunBindings(deps.scope.orgId, deps.scope.factoryProjectId);\n const binding = bindings.find(row => row.sessionId === sessionId);\n if (!binding) throw new Error('The session does not belong to this factory.');\n await deps.signalSession({ sessionId, message, userId: deps.userId });\n await audit(\n 'factory.agent.signaled',\n { type: 'factory_session', id: sessionId },\n {\n workItemId: binding.workItemId,\n role: binding.role,\n },\n );\n return { sessionId, delivered: true, workItemId: binding.workItemId, role: binding.role };\n },\n }),\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/factory",
|
|
3
|
-
"version": "0.14.0-alpha.
|
|
3
|
+
"version": "0.14.0-alpha.4",
|
|
4
4
|
"description": "Mastra Software Factory module: the server core behind the Mastra Software Factory — storage domains, integrations, and surfaces for agent-powered software delivery",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -57,11 +57,11 @@
|
|
|
57
57
|
"hono": "^4.12.8",
|
|
58
58
|
"posthog-node": "^5.46.1",
|
|
59
59
|
"zod": "^4.3.6",
|
|
60
|
-
"@mastra/auth-workos": "1.6.5",
|
|
61
60
|
"@mastra/auth-studio": "1.3.5",
|
|
61
|
+
"@mastra/auth-workos": "1.6.5",
|
|
62
|
+
"@mastra/code-sdk": "1.7.1-alpha.4",
|
|
62
63
|
"@mastra/slack": "1.6.3",
|
|
63
|
-
"@mastra/
|
|
64
|
-
"@mastra/core": "1.66.0-alpha.2"
|
|
64
|
+
"@mastra/core": "1.66.0-alpha.4"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@types/node": "22.20.1",
|
|
@@ -70,11 +70,11 @@
|
|
|
70
70
|
"typescript": "^6.0.3",
|
|
71
71
|
"typescript-eslint": "^8.57.0",
|
|
72
72
|
"vitest": "4.1.10",
|
|
73
|
-
"@internal/lint": "0.0.131",
|
|
74
73
|
"@mastra/libsql": "1.22.5-alpha.1",
|
|
75
|
-
"@mastra/pg": "1.24.0-alpha.
|
|
74
|
+
"@mastra/pg": "1.24.0-alpha.3",
|
|
76
75
|
"@internal/types-builder": "0.0.106",
|
|
77
|
-
"@internal/workspace": "0.0.3"
|
|
76
|
+
"@internal/workspace": "0.0.3",
|
|
77
|
+
"@internal/lint": "0.0.131"
|
|
78
78
|
},
|
|
79
79
|
"engines": {
|
|
80
80
|
"node": ">=22.19.0"
|