@mastra/factory 0.5.0 → 0.6.0-alpha.1
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/CHANGELOG.md +42 -0
- package/dist/auth.d.ts +2 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +2 -0
- package/dist/auth.js.map +1 -1
- package/dist/capabilities/intake.d.ts +1 -0
- package/dist/capabilities/intake.d.ts.map +1 -1
- package/dist/capabilities/version-control.d.ts +2 -0
- package/dist/capabilities/version-control.d.ts.map +1 -1
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +2 -1
- package/dist/factory.js.map +1 -1
- package/dist/integrations/base.d.ts +6 -0
- package/dist/integrations/base.d.ts.map +1 -1
- package/dist/integrations/github/integration.d.ts +1 -0
- package/dist/integrations/github/integration.d.ts.map +1 -1
- package/dist/integrations/github/integration.js +11 -1
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/routes.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +20 -46
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/rules.d.ts +4 -0
- package/dist/integrations/github/rules.d.ts.map +1 -1
- package/dist/integrations/github/rules.js +68 -10
- package/dist/integrations/github/rules.js.map +1 -1
- package/dist/integrations/github/sandbox-release.d.ts +16 -1
- package/dist/integrations/github/sandbox-release.d.ts.map +1 -1
- package/dist/integrations/github/sandbox-release.js +42 -1
- package/dist/integrations/github/sandbox-release.js.map +1 -1
- package/dist/integrations/linear/integration.d.ts +2 -0
- package/dist/integrations/linear/integration.d.ts.map +1 -1
- package/dist/integrations/linear/integration.js +5 -1
- package/dist/integrations/linear/integration.js.map +1 -1
- package/dist/integrations/linear/routes.d.ts.map +1 -1
- package/dist/integrations/linear/routes.js +1 -0
- package/dist/integrations/linear/routes.js.map +1 -1
- package/dist/integrations/linear/rules.d.ts +2 -0
- package/dist/integrations/linear/rules.d.ts.map +1 -1
- package/dist/integrations/linear/rules.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +7 -0
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/integrations/slack/integration.d.ts.map +1 -1
- package/dist/integrations/slack/integration.js +3 -2
- package/dist/integrations/slack/integration.js.map +1 -1
- package/dist/integrations/slack/slack.d.ts +37 -110
- package/dist/integrations/slack/slack.d.ts.map +1 -1
- package/dist/integrations/slack/slack.js +49 -44
- package/dist/integrations/slack/slack.js.map +1 -1
- package/dist/routes/surface.d.ts +11 -1
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +18 -8
- package/dist/routes/surface.js.map +1 -1
- package/dist/rules/defaults.d.ts.map +1 -1
- package/dist/rules/defaults.js +35 -4
- package/dist/rules/defaults.js.map +1 -1
- package/dist/rules/start-coordinator.d.ts.map +1 -1
- package/dist/rules/start-coordinator.js +7 -26
- package/dist/rules/start-coordinator.js.map +1 -1
- package/dist/rules/types.d.ts +10 -0
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/types.js.map +1 -1
- package/dist/session/factory-session.d.ts +116 -0
- package/dist/session/factory-session.d.ts.map +1 -0
- package/dist/session/factory-session.js +162 -0
- package/dist/session/factory-session.js.map +1 -0
- package/dist/storage/domains/audit/domain.d.ts +9 -2
- package/dist/storage/domains/audit/domain.d.ts.map +1 -1
- package/dist/storage/domains/audit/domain.js +106 -4
- package/dist/storage/domains/audit/domain.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +11 -0
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/package.json +7 -7
- package/dist/integrations/github/factory-session.d.ts +0 -12
- package/dist/integrations/github/factory-session.d.ts.map +0 -1
- package/dist/integrations/github/factory-session.js +0 -37
- package/dist/integrations/github/factory-session.js.map +0 -1
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { MastraCodeState } from '@mastra/code-sdk/schema';
|
|
2
|
+
import type { AgentController } from '@mastra/core/agent-controller';
|
|
3
|
+
import type { MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';
|
|
4
|
+
import type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';
|
|
5
|
+
import type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';
|
|
6
|
+
type FactorySession = Awaited<ReturnType<AgentController<MastraCodeState>['createSession']>>;
|
|
7
|
+
/**
|
|
8
|
+
* Read the factory project's default model. Best-effort: a missing project or an
|
|
9
|
+
* uninitialized storage domain means "no default", never a failed run.
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolveFactoryDefaultModelId(projects: FactoryProjectsStorage | undefined, factoryProjectId: string | undefined): Promise<string | undefined>;
|
|
12
|
+
export interface EnsureFactorySourceSessionArgs {
|
|
13
|
+
/**
|
|
14
|
+
* Storage handle of the integration that owns source control. Nothing here is
|
|
15
|
+
* provider-specific: the connection is matched by the handle's own
|
|
16
|
+
* `integrationId`, so GitHub, Slack-on-behalf-of-GitHub, or any future owner
|
|
17
|
+
* all resolve through the same traversal.
|
|
18
|
+
*/
|
|
19
|
+
sourceControl: SourceControlStorageHandle;
|
|
20
|
+
orgId: string;
|
|
21
|
+
factoryProjectId: string;
|
|
22
|
+
branch: string;
|
|
23
|
+
/** Pick a specific linked repository by slug. Defaults to the first linked repository. */
|
|
24
|
+
repositorySlug?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface EnsuredFactorySourceSession {
|
|
27
|
+
sessionId: string;
|
|
28
|
+
userId: string;
|
|
29
|
+
projectRepositoryId: string;
|
|
30
|
+
branch: string;
|
|
31
|
+
baseBranch: string;
|
|
32
|
+
}
|
|
33
|
+
export interface ResolvedFactorySourceRepository {
|
|
34
|
+
projectRepositoryId: string;
|
|
35
|
+
/** The repository's pinned branch, else its default branch. */
|
|
36
|
+
baseBranch: string;
|
|
37
|
+
/** Who connected the repository. The attribution for runs with no interactive user. */
|
|
38
|
+
connectedByUserId: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Outcome of {@link resolveFactorySourceRepository}. A miss carries which step
|
|
42
|
+
* failed: callers differ on whether that is an error (an autonomous run cannot
|
|
43
|
+
* proceed) or a routine fallback (a chat integration drops to a chat-only
|
|
44
|
+
* session), and the two steps fail for different reasons worth reporting apart.
|
|
45
|
+
*/
|
|
46
|
+
export type FactorySourceRepositoryResult = ({
|
|
47
|
+
found: true;
|
|
48
|
+
} & ResolvedFactorySourceRepository) | {
|
|
49
|
+
found: false;
|
|
50
|
+
reason: 'connection' | 'repository';
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Resolve which repository a factory project's source-control runs act on: the
|
|
54
|
+
* owner's connection on the project, then one of its linked repositories.
|
|
55
|
+
*
|
|
56
|
+
* The owner is whichever integration owns source control, matched by the
|
|
57
|
+
* handle's own `integrationId` — nothing here is provider-specific.
|
|
58
|
+
*/
|
|
59
|
+
export declare function resolveFactorySourceRepository(args: {
|
|
60
|
+
sourceControl: SourceControlStorageHandle;
|
|
61
|
+
orgId: string;
|
|
62
|
+
factoryProjectId: string;
|
|
63
|
+
/** Pick a specific linked repository by slug. Defaults to the first linked repository. */
|
|
64
|
+
repositorySlug?: string;
|
|
65
|
+
}): Promise<FactorySourceRepositoryResult>;
|
|
66
|
+
/**
|
|
67
|
+
* Walk a Factory user-session id back to the project it belongs to.
|
|
68
|
+
*
|
|
69
|
+
* Repo-backed channel threads are keyed by their Factory session id, which is
|
|
70
|
+
* the only handle a session-start hook gets. This turns that id back into the
|
|
71
|
+
* project whose configuration the session should adopt. Durable by
|
|
72
|
+
* construction — it reads the same rows the session was created from, so it
|
|
73
|
+
* survives restarts without any in-memory mapping.
|
|
74
|
+
*/
|
|
75
|
+
export declare function resolveFactoryProjectForSession(args: {
|
|
76
|
+
sourceControl: SourceControlStorageHandle;
|
|
77
|
+
sessionId: string;
|
|
78
|
+
}): Promise<{
|
|
79
|
+
factoryProjectId: string;
|
|
80
|
+
orgId: string;
|
|
81
|
+
userId: string;
|
|
82
|
+
} | null>;
|
|
83
|
+
/**
|
|
84
|
+
* Create the source-control session a repo-backed factory run needs.
|
|
85
|
+
*
|
|
86
|
+
* `FactoryStartCoordinator.prepare` requires this record to already exist —
|
|
87
|
+
* `resolveSourceSession` throws `Factory session not found` otherwise — so every
|
|
88
|
+
* autonomous entry point has to produce one before it can start a run. This is
|
|
89
|
+
* that step, in one place: the owner's connection on the factory project, one of
|
|
90
|
+
* its linked repositories, and a session on the requested branch with the
|
|
91
|
+
* repository's pinned or default branch as the base.
|
|
92
|
+
*
|
|
93
|
+
* The run is attributed to whoever connected the repository
|
|
94
|
+
* (`connection.createdByUserId`), because an autonomous run has no interactive
|
|
95
|
+
* user of its own.
|
|
96
|
+
*/
|
|
97
|
+
export declare function ensureFactorySourceSession(args: EnsureFactorySourceSessionArgs): Promise<EnsuredFactorySourceSession>;
|
|
98
|
+
export interface HydrateFactorySessionArgs {
|
|
99
|
+
orgId: string;
|
|
100
|
+
userId: string;
|
|
101
|
+
/** The factory project's default model. Without it the session keeps the SDK's built-in mode default. */
|
|
102
|
+
defaultModelId?: string;
|
|
103
|
+
/** Omitted when the storage domain is unavailable, in which case the session runs on memory defaults. */
|
|
104
|
+
memorySettings?: MemorySettingsStorage;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Apply a factory project's configuration to a freshly created session:
|
|
108
|
+
* observational-memory settings, then the project's default model.
|
|
109
|
+
*
|
|
110
|
+
* Both steps are best-effort. A retired model id or an unreachable settings row
|
|
111
|
+
* must not sink a run that is otherwise ready — the session simply keeps the
|
|
112
|
+
* default it was created with, and the reason is logged.
|
|
113
|
+
*/
|
|
114
|
+
export declare function hydrateFactorySession(session: FactorySession, args: HydrateFactorySessionArgs): Promise<void>;
|
|
115
|
+
export {};
|
|
116
|
+
//# sourceMappingURL=factory-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory-session.d.ts","sourceRoot":"","sources":["../../src/session/factory-session.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAErE,OAAO,KAAK,EAAwB,qBAAqB,EAAE,MAAM,4CAA4C,CAAC;AAC9G,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AAE5F,KAAK,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,eAAe,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;AAE7F;;;GAGG;AACH,wBAAsB,4BAA4B,CAChD,QAAQ,EAAE,sBAAsB,GAAG,SAAS,EAC5C,gBAAgB,EAAE,MAAM,GAAG,SAAS,GACnC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAQ7B;AAED,MAAM,WAAW,8BAA8B;IAC7C;;;;;OAKG;IACH,aAAa,EAAE,0BAA0B,CAAC;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,0FAA0F;IAC1F,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB,EAAE,MAAM,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,+BAA+B;IAC9C,mBAAmB,EAAE,MAAM,CAAC;IAC5B,+DAA+D;IAC/D,UAAU,EAAE,MAAM,CAAC;IACnB,uFAAuF;IACvF,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,MAAM,MAAM,6BAA6B,GACrC,CAAC;IAAE,KAAK,EAAE,IAAI,CAAA;CAAE,GAAG,+BAA+B,CAAC,GACnD;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAAA;CAAE,CAAC;AAE1D;;;;;;GAMG;AACH,wBAAsB,8BAA8B,CAAC,IAAI,EAAE;IACzD,aAAa,EAAE,0BAA0B,CAAC;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,0FAA0F;IAC1F,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,6BAA6B,CAAC,CAyBzC;AAED;;;;;;;;GAQG;AACH,wBAAsB,+BAA+B,CAAC,IAAI,EAAE;IAC1D,aAAa,EAAE,0BAA0B,CAAC;IAC1C,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC;IAAE,gBAAgB,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAc9E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,8BAA8B,GACnC,OAAO,CAAC,2BAA2B,CAAC,CA4BtC;AAcD,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,yGAAyG;IACzG,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yGAAyG;IACzG,cAAc,CAAC,EAAE,qBAAqB,CAAC;CACxC;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,yBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBnH"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
2
|
+
//#region src/session/factory-session.ts
|
|
3
|
+
/**
|
|
4
|
+
* Read the factory project's default model. Best-effort: a missing project or an
|
|
5
|
+
* uninitialized storage domain means "no default", never a failed run.
|
|
6
|
+
*/
|
|
7
|
+
async function resolveFactoryDefaultModelId(projects, factoryProjectId) {
|
|
8
|
+
if (!projects || !factoryProjectId) return void 0;
|
|
9
|
+
try {
|
|
10
|
+
return (await projects.getById({ id: factoryProjectId }))?.defaultModelId ?? void 0;
|
|
11
|
+
} catch {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Resolve which repository a factory project's source-control runs act on: the
|
|
17
|
+
* owner's connection on the project, then one of its linked repositories.
|
|
18
|
+
*
|
|
19
|
+
* The owner is whichever integration owns source control, matched by the
|
|
20
|
+
* handle's own `integrationId` — nothing here is provider-specific.
|
|
21
|
+
*/
|
|
22
|
+
async function resolveFactorySourceRepository(args) {
|
|
23
|
+
const { sourceControl, orgId, factoryProjectId, repositorySlug } = args;
|
|
24
|
+
const connection = (await sourceControl.connections.list({
|
|
25
|
+
orgId,
|
|
26
|
+
factoryProjectId
|
|
27
|
+
})).find((candidate) => candidate.integrationId === sourceControl.integrationId);
|
|
28
|
+
if (!connection) return {
|
|
29
|
+
found: false,
|
|
30
|
+
reason: "connection"
|
|
31
|
+
};
|
|
32
|
+
const projectRepositories = await sourceControl.projectRepositories.list({
|
|
33
|
+
orgId,
|
|
34
|
+
connectionId: connection.id
|
|
35
|
+
});
|
|
36
|
+
const resolved = (await Promise.all(projectRepositories.map(async (projectRepository) => ({
|
|
37
|
+
projectRepository,
|
|
38
|
+
repository: await sourceControl.repositories.get({
|
|
39
|
+
orgId,
|
|
40
|
+
id: projectRepository.repositoryId
|
|
41
|
+
})
|
|
42
|
+
})))).find((candidate) => candidate.repository && (!repositorySlug || candidate.repository.slug === repositorySlug));
|
|
43
|
+
if (!resolved?.repository) return {
|
|
44
|
+
found: false,
|
|
45
|
+
reason: "repository"
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
found: true,
|
|
49
|
+
projectRepositoryId: resolved.projectRepository.id,
|
|
50
|
+
baseBranch: resolved.projectRepository.branch ?? resolved.repository.defaultBranch,
|
|
51
|
+
connectedByUserId: connection.createdByUserId
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Walk a Factory user-session id back to the project it belongs to.
|
|
56
|
+
*
|
|
57
|
+
* Repo-backed channel threads are keyed by their Factory session id, which is
|
|
58
|
+
* the only handle a session-start hook gets. This turns that id back into the
|
|
59
|
+
* project whose configuration the session should adopt. Durable by
|
|
60
|
+
* construction — it reads the same rows the session was created from, so it
|
|
61
|
+
* survives restarts without any in-memory mapping.
|
|
62
|
+
*/
|
|
63
|
+
async function resolveFactoryProjectForSession(args) {
|
|
64
|
+
const { sourceControl, sessionId } = args;
|
|
65
|
+
const session = await sourceControl.sessions.getBySessionId(sessionId);
|
|
66
|
+
if (!session) return null;
|
|
67
|
+
const projectRepository = await sourceControl.projectRepositories.get({
|
|
68
|
+
orgId: session.orgId,
|
|
69
|
+
id: session.projectRepositoryId
|
|
70
|
+
});
|
|
71
|
+
if (!projectRepository) return null;
|
|
72
|
+
const connection = await sourceControl.connections.get({
|
|
73
|
+
orgId: session.orgId,
|
|
74
|
+
id: projectRepository.connectionId
|
|
75
|
+
});
|
|
76
|
+
if (!connection) return null;
|
|
77
|
+
return {
|
|
78
|
+
factoryProjectId: connection.factoryProjectId,
|
|
79
|
+
orgId: session.orgId,
|
|
80
|
+
userId: session.userId
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Create the source-control session a repo-backed factory run needs.
|
|
85
|
+
*
|
|
86
|
+
* `FactoryStartCoordinator.prepare` requires this record to already exist —
|
|
87
|
+
* `resolveSourceSession` throws `Factory session not found` otherwise — so every
|
|
88
|
+
* autonomous entry point has to produce one before it can start a run. This is
|
|
89
|
+
* that step, in one place: the owner's connection on the factory project, one of
|
|
90
|
+
* its linked repositories, and a session on the requested branch with the
|
|
91
|
+
* repository's pinned or default branch as the base.
|
|
92
|
+
*
|
|
93
|
+
* The run is attributed to whoever connected the repository
|
|
94
|
+
* (`connection.createdByUserId`), because an autonomous run has no interactive
|
|
95
|
+
* user of its own.
|
|
96
|
+
*/
|
|
97
|
+
async function ensureFactorySourceSession(args) {
|
|
98
|
+
const { sourceControl, orgId, factoryProjectId, branch, repositorySlug } = args;
|
|
99
|
+
const resolved = await resolveFactorySourceRepository({
|
|
100
|
+
sourceControl,
|
|
101
|
+
orgId,
|
|
102
|
+
factoryProjectId,
|
|
103
|
+
repositorySlug
|
|
104
|
+
});
|
|
105
|
+
if (!resolved.found) throw new Error(resolved.reason === "connection" ? "Factory source-control connection not found." : "Factory source-control repository not found.");
|
|
106
|
+
const userId = resolved.connectedByUserId;
|
|
107
|
+
const session = await sourceControl.sessions.create({
|
|
108
|
+
sessionId: randomUUID(),
|
|
109
|
+
projectRepositoryId: resolved.projectRepositoryId,
|
|
110
|
+
orgId,
|
|
111
|
+
userId,
|
|
112
|
+
branch,
|
|
113
|
+
baseBranch: resolved.baseBranch
|
|
114
|
+
});
|
|
115
|
+
return {
|
|
116
|
+
sessionId: session.sessionId,
|
|
117
|
+
userId,
|
|
118
|
+
projectRepositoryId: resolved.projectRepositoryId,
|
|
119
|
+
branch: session.branch,
|
|
120
|
+
baseBranch: resolved.baseBranch
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
async function applyMemorySettings(session, record) {
|
|
124
|
+
if (record?.observerModelId) await session.om.observer.switchModel({ modelId: record.observerModelId });
|
|
125
|
+
if (record?.reflectorModelId) await session.om.reflector.switchModel({ modelId: record.reflectorModelId });
|
|
126
|
+
const state = {
|
|
127
|
+
...record?.observationThreshold != null ? { observationThreshold: record.observationThreshold } : {},
|
|
128
|
+
...record?.reflectionThreshold != null ? { reflectionThreshold: record.reflectionThreshold } : {},
|
|
129
|
+
...record?.observeAttachments != null ? { observeAttachments: record.observeAttachments } : {}
|
|
130
|
+
};
|
|
131
|
+
if (Object.keys(state).length > 0) await session.state.set(state);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Apply a factory project's configuration to a freshly created session:
|
|
135
|
+
* observational-memory settings, then the project's default model.
|
|
136
|
+
*
|
|
137
|
+
* Both steps are best-effort. A retired model id or an unreachable settings row
|
|
138
|
+
* must not sink a run that is otherwise ready — the session simply keeps the
|
|
139
|
+
* default it was created with, and the reason is logged.
|
|
140
|
+
*/
|
|
141
|
+
async function hydrateFactorySession(session, args) {
|
|
142
|
+
if (args.memorySettings) try {
|
|
143
|
+
await applyMemorySettings(session, await args.memorySettings.get({
|
|
144
|
+
orgId: args.orgId,
|
|
145
|
+
userId: args.userId
|
|
146
|
+
}));
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.warn("[Factory Start] Failed to apply observational-memory settings", { error: error instanceof Error ? error.message : String(error) });
|
|
149
|
+
}
|
|
150
|
+
if (args.defaultModelId) try {
|
|
151
|
+
await session.model.switch({ modelId: args.defaultModelId });
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.warn("[Factory Start] Failed to apply factory default model", {
|
|
154
|
+
modelId: args.defaultModelId,
|
|
155
|
+
error: error instanceof Error ? error.message : String(error)
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
160
|
+
export { ensureFactorySourceSession, hydrateFactorySession, resolveFactoryDefaultModelId, resolveFactoryProjectForSession, resolveFactorySourceRepository };
|
|
161
|
+
|
|
162
|
+
//# sourceMappingURL=factory-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory-session.js","names":[],"sources":["../../src/session/factory-session.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\n\nimport type { MemorySettingsRecord, MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\n\ntype FactorySession = Awaited<ReturnType<AgentController<MastraCodeState>['createSession']>>;\n\n/**\n * Read the factory project's default model. Best-effort: a missing project or an\n * uninitialized storage domain means \"no default\", never a failed run.\n */\nexport async function resolveFactoryDefaultModelId(\n projects: FactoryProjectsStorage | undefined,\n factoryProjectId: string | undefined,\n): Promise<string | undefined> {\n if (!projects || !factoryProjectId) return undefined;\n try {\n const project = await projects.getById({ id: factoryProjectId });\n return project?.defaultModelId ?? undefined;\n } catch {\n return undefined;\n }\n}\n\nexport interface EnsureFactorySourceSessionArgs {\n /**\n * Storage handle of the integration that owns source control. Nothing here is\n * provider-specific: the connection is matched by the handle's own\n * `integrationId`, so GitHub, Slack-on-behalf-of-GitHub, or any future owner\n * all resolve through the same traversal.\n */\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n branch: string;\n /** Pick a specific linked repository by slug. Defaults to the first linked repository. */\n repositorySlug?: string;\n}\n\nexport interface EnsuredFactorySourceSession {\n sessionId: string;\n userId: string;\n projectRepositoryId: string;\n branch: string;\n baseBranch: string;\n}\n\nexport interface ResolvedFactorySourceRepository {\n projectRepositoryId: string;\n /** The repository's pinned branch, else its default branch. */\n baseBranch: string;\n /** Who connected the repository. The attribution for runs with no interactive user. */\n connectedByUserId: string;\n}\n\n/**\n * Outcome of {@link resolveFactorySourceRepository}. A miss carries which step\n * failed: callers differ on whether that is an error (an autonomous run cannot\n * proceed) or a routine fallback (a chat integration drops to a chat-only\n * session), and the two steps fail for different reasons worth reporting apart.\n */\nexport type FactorySourceRepositoryResult =\n | ({ found: true } & ResolvedFactorySourceRepository)\n | { found: false; reason: 'connection' | 'repository' };\n\n/**\n * Resolve which repository a factory project's source-control runs act on: the\n * owner's connection on the project, then one of its linked repositories.\n *\n * The owner is whichever integration owns source control, matched by the\n * handle's own `integrationId` — nothing here is provider-specific.\n */\nexport async function resolveFactorySourceRepository(args: {\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n /** Pick a specific linked repository by slug. Defaults to the first linked repository. */\n repositorySlug?: string;\n}): Promise<FactorySourceRepositoryResult> {\n const { sourceControl, orgId, factoryProjectId, repositorySlug } = args;\n\n const connections = await sourceControl.connections.list({ orgId, factoryProjectId });\n const connection = connections.find(candidate => candidate.integrationId === sourceControl.integrationId);\n if (!connection) return { found: false, reason: 'connection' };\n\n const projectRepositories = await sourceControl.projectRepositories.list({ orgId, connectionId: connection.id });\n const resolvedRepositories = await Promise.all(\n projectRepositories.map(async projectRepository => ({\n projectRepository,\n repository: await sourceControl.repositories.get({ orgId, id: projectRepository.repositoryId }),\n })),\n );\n const resolved = resolvedRepositories.find(\n candidate => candidate.repository && (!repositorySlug || candidate.repository.slug === repositorySlug),\n );\n if (!resolved?.repository) return { found: false, reason: 'repository' };\n\n return {\n found: true,\n projectRepositoryId: resolved.projectRepository.id,\n baseBranch: resolved.projectRepository.branch ?? resolved.repository.defaultBranch,\n connectedByUserId: connection.createdByUserId,\n };\n}\n\n/**\n * Walk a Factory user-session id back to the project it belongs to.\n *\n * Repo-backed channel threads are keyed by their Factory session id, which is\n * the only handle a session-start hook gets. This turns that id back into the\n * project whose configuration the session should adopt. Durable by\n * construction — it reads the same rows the session was created from, so it\n * survives restarts without any in-memory mapping.\n */\nexport async function resolveFactoryProjectForSession(args: {\n sourceControl: SourceControlStorageHandle;\n sessionId: string;\n}): Promise<{ factoryProjectId: string; orgId: string; userId: string } | null> {\n const { sourceControl, sessionId } = args;\n\n const session = await sourceControl.sessions.getBySessionId(sessionId);\n if (!session) return null;\n const projectRepository = await sourceControl.projectRepositories.get({\n orgId: session.orgId,\n id: session.projectRepositoryId,\n });\n if (!projectRepository) return null;\n const connection = await sourceControl.connections.get({ orgId: session.orgId, id: projectRepository.connectionId });\n if (!connection) return null;\n\n return { factoryProjectId: connection.factoryProjectId, orgId: session.orgId, userId: session.userId };\n}\n\n/**\n * Create the source-control session a repo-backed factory run needs.\n *\n * `FactoryStartCoordinator.prepare` requires this record to already exist —\n * `resolveSourceSession` throws `Factory session not found` otherwise — so every\n * autonomous entry point has to produce one before it can start a run. This is\n * that step, in one place: the owner's connection on the factory project, one of\n * its linked repositories, and a session on the requested branch with the\n * repository's pinned or default branch as the base.\n *\n * The run is attributed to whoever connected the repository\n * (`connection.createdByUserId`), because an autonomous run has no interactive\n * user of its own.\n */\nexport async function ensureFactorySourceSession(\n args: EnsureFactorySourceSessionArgs,\n): Promise<EnsuredFactorySourceSession> {\n const { sourceControl, orgId, factoryProjectId, branch, repositorySlug } = args;\n\n const resolved = await resolveFactorySourceRepository({ sourceControl, orgId, factoryProjectId, repositorySlug });\n if (!resolved.found) {\n throw new Error(\n resolved.reason === 'connection'\n ? 'Factory source-control connection not found.'\n : 'Factory source-control repository not found.',\n );\n }\n\n const userId = resolved.connectedByUserId;\n const session = await sourceControl.sessions.create({\n sessionId: randomUUID(),\n projectRepositoryId: resolved.projectRepositoryId,\n orgId,\n userId,\n branch,\n baseBranch: resolved.baseBranch,\n });\n return {\n sessionId: session.sessionId,\n userId,\n projectRepositoryId: resolved.projectRepositoryId,\n branch: session.branch,\n baseBranch: resolved.baseBranch,\n };\n}\n\nasync function applyMemorySettings(session: FactorySession, record: MemorySettingsRecord | null): Promise<void> {\n if (record?.observerModelId) await session.om.observer.switchModel({ modelId: record.observerModelId });\n if (record?.reflectorModelId) await session.om.reflector.switchModel({ modelId: record.reflectorModelId });\n\n const state = {\n ...(record?.observationThreshold != null ? { observationThreshold: record.observationThreshold } : {}),\n ...(record?.reflectionThreshold != null ? { reflectionThreshold: record.reflectionThreshold } : {}),\n ...(record?.observeAttachments != null ? { observeAttachments: record.observeAttachments } : {}),\n };\n if (Object.keys(state).length > 0) await session.state.set(state);\n}\n\nexport interface HydrateFactorySessionArgs {\n orgId: string;\n userId: string;\n /** The factory project's default model. Without it the session keeps the SDK's built-in mode default. */\n defaultModelId?: string;\n /** Omitted when the storage domain is unavailable, in which case the session runs on memory defaults. */\n memorySettings?: MemorySettingsStorage;\n}\n\n/**\n * Apply a factory project's configuration to a freshly created session:\n * observational-memory settings, then the project's default model.\n *\n * Both steps are best-effort. A retired model id or an unreachable settings row\n * must not sink a run that is otherwise ready — the session simply keeps the\n * default it was created with, and the reason is logged.\n */\nexport async function hydrateFactorySession(session: FactorySession, args: HydrateFactorySessionArgs): Promise<void> {\n if (args.memorySettings) {\n try {\n const record = await args.memorySettings.get({ orgId: args.orgId, userId: args.userId });\n await applyMemorySettings(session, record);\n } catch (error) {\n console.warn('[Factory Start] Failed to apply observational-memory settings', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n if (args.defaultModelId) {\n try {\n await session.model.switch({ modelId: args.defaultModelId });\n } catch (error) {\n console.warn('[Factory Start] Failed to apply factory default model', {\n modelId: args.defaultModelId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RequestContext } from '@mastra/core/request-context';
|
|
2
|
-
import type { ApiRoute } from '@mastra/core/server';
|
|
2
|
+
import type { ApiRoute, IUserProvider } from '@mastra/core/server';
|
|
3
3
|
import type { Context } from 'hono';
|
|
4
4
|
import type { RouteAuth } from '../../../routes/route.js';
|
|
5
5
|
import type { FactoryProjectsStorage } from '../projects/base.js';
|
|
@@ -35,12 +35,19 @@ export interface AuditSink {
|
|
|
35
35
|
event: AuditEventRow;
|
|
36
36
|
}): Promise<void>;
|
|
37
37
|
}
|
|
38
|
+
export interface AuditActorProfile {
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
avatarUrl?: string;
|
|
42
|
+
}
|
|
38
43
|
export interface AuditDomainOptions {
|
|
39
44
|
auth: RouteAuth;
|
|
40
45
|
/** Audit storage domain handle. */
|
|
41
46
|
audit: AuditStorage;
|
|
42
47
|
/** Projects domain handle, used to scope the audit trail route. */
|
|
43
48
|
projects: FactoryProjectsStorage;
|
|
49
|
+
/** Resolve persisted human actor ids to display names and profile images. */
|
|
50
|
+
users?: Pick<IUserProvider, 'getUser' | 'getUsers'>;
|
|
44
51
|
/** Best-effort fan-out destinations notified after each recorded event. */
|
|
45
52
|
sinks?: AuditSink[];
|
|
46
53
|
/** Resolve the acting tenant for agent-emitted events from the request context. */
|
|
@@ -53,7 +60,7 @@ export declare function auditRequestContext(c: Context): AuditContext;
|
|
|
53
60
|
/** Factory-owned audit behavior backed by the audit storage domain. */
|
|
54
61
|
export declare class AuditDomain implements AuditEmitter, AuditAgentEmitter {
|
|
55
62
|
#private;
|
|
56
|
-
constructor({ auth, audit, projects, sinks, agentTenant }: AuditDomainOptions);
|
|
63
|
+
constructor({ auth, audit, projects, users, sinks, agentTenant }: AuditDomainOptions);
|
|
57
64
|
record(input: RecordAuditEventInput): Promise<AuditEventRow | null>;
|
|
58
65
|
list(input: ListAuditEventsInput): Promise<AuditEventPage>;
|
|
59
66
|
emit({ context, input }: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domain.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/audit/domain.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"domain.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/audit/domain.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,50 @@
|
|
|
1
1
|
import { registerApiRoute } from "@mastra/core/server";
|
|
2
2
|
//#region src/storage/domains/audit/domain.ts
|
|
3
|
+
/**
|
|
4
|
+
* Reserved metadata key on `AuditEventRow.metadata` that carries the acting
|
|
5
|
+
* human user's display name and avatar captured from the auth context at
|
|
6
|
+
* record time. Read back by `#resolveActorProfiles` so names/avatars work
|
|
7
|
+
* for every auth provider without requiring an `IUserProvider.getUser(id)`
|
|
8
|
+
* implementation — the Studio provider proxies through the shared API and
|
|
9
|
+
* cannot resolve arbitrary users by id.
|
|
10
|
+
*/
|
|
11
|
+
const ACTOR_PROFILE_METADATA_KEY = "__actorProfile";
|
|
12
|
+
function readStoredActorProfile(metadata) {
|
|
13
|
+
if (!metadata) return void 0;
|
|
14
|
+
const raw = metadata[ACTOR_PROFILE_METADATA_KEY];
|
|
15
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
16
|
+
const profile = raw;
|
|
17
|
+
const name = typeof profile.name === "string" && profile.name.trim() ? profile.name.trim() : void 0;
|
|
18
|
+
const avatarUrl = typeof profile.avatarUrl === "string" && profile.avatarUrl.trim() ? profile.avatarUrl.trim() : void 0;
|
|
19
|
+
if (!name && !avatarUrl) return void 0;
|
|
20
|
+
return {
|
|
21
|
+
...name ? { name } : {},
|
|
22
|
+
...avatarUrl ? { avatarUrl } : {}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Read the auth gate's stashed user directly from the Hono context. */
|
|
26
|
+
function readFactoryAuthUserFromContext(context) {
|
|
27
|
+
return context.get("factoryAuthUser") ?? void 0;
|
|
28
|
+
}
|
|
29
|
+
function buildActorProfileMetadata(user) {
|
|
30
|
+
const name = user.name?.trim() || user.email?.trim();
|
|
31
|
+
const avatarUrl = user.avatarUrl?.trim();
|
|
32
|
+
if (!name && !avatarUrl) return void 0;
|
|
33
|
+
return {
|
|
34
|
+
...name ? { name } : {},
|
|
35
|
+
...avatarUrl ? { avatarUrl } : {}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
3
38
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4
39
|
const MAX_ACTION_FILTERS = 16;
|
|
40
|
+
const MAX_ACTOR_PROFILES = 100;
|
|
41
|
+
const AUTOMATION_ACTORS = /* @__PURE__ */ new Set([
|
|
42
|
+
"factory",
|
|
43
|
+
"system",
|
|
44
|
+
"automation",
|
|
45
|
+
"factory-rule-dispatcher",
|
|
46
|
+
"factory-tool-result-rule"
|
|
47
|
+
]);
|
|
5
48
|
function loose(c) {
|
|
6
49
|
return c;
|
|
7
50
|
}
|
|
@@ -10,6 +53,14 @@ function parseActionsParam(raw) {
|
|
|
10
53
|
const actions = raw.split(",").map((action) => action.trim()).filter(Boolean).slice(0, MAX_ACTION_FILTERS);
|
|
11
54
|
return actions.length > 0 ? actions : void 0;
|
|
12
55
|
}
|
|
56
|
+
function isHumanActorId(actorId) {
|
|
57
|
+
if (!actorId) return false;
|
|
58
|
+
return !AUTOMATION_ACTORS.has(actorId) && !actorId.startsWith("agent:") && !actorId.startsWith("github:");
|
|
59
|
+
}
|
|
60
|
+
function parseActorIdsParam(raw) {
|
|
61
|
+
if (!raw) return [];
|
|
62
|
+
return [...new Set(raw.split(",").map((actorId) => actorId.trim()).filter(isHumanActorId))].slice(0, MAX_ACTOR_PROFILES);
|
|
63
|
+
}
|
|
13
64
|
function parseLimitParam(raw) {
|
|
14
65
|
if (!raw) return void 0;
|
|
15
66
|
const limit = Number.parseInt(raw, 10);
|
|
@@ -28,12 +79,14 @@ var AuditDomain = class {
|
|
|
28
79
|
#auth;
|
|
29
80
|
#audit;
|
|
30
81
|
#projects;
|
|
82
|
+
#users;
|
|
31
83
|
#sinks;
|
|
32
84
|
#agentTenant;
|
|
33
|
-
constructor({ auth, audit, projects, sinks = [], agentTenant }) {
|
|
85
|
+
constructor({ auth, audit, projects, users, sinks = [], agentTenant }) {
|
|
34
86
|
this.#auth = auth;
|
|
35
87
|
this.#audit = audit;
|
|
36
88
|
this.#projects = projects;
|
|
89
|
+
this.#users = users;
|
|
37
90
|
this.#sinks = sinks;
|
|
38
91
|
this.#agentTenant = agentTenant;
|
|
39
92
|
const ids = /* @__PURE__ */ new Set();
|
|
@@ -74,12 +127,18 @@ var AuditDomain = class {
|
|
|
74
127
|
try {
|
|
75
128
|
const tenant = this.#auth.tenant(context);
|
|
76
129
|
if (!tenant?.orgId) return;
|
|
130
|
+
const user = readFactoryAuthUserFromContext(context);
|
|
131
|
+
const actorProfile = user ? buildActorProfileMetadata(user) : void 0;
|
|
132
|
+
const metadata = actorProfile ? {
|
|
133
|
+
...input.metadata,
|
|
134
|
+
[ACTOR_PROFILE_METADATA_KEY]: actorProfile
|
|
135
|
+
} : input.metadata;
|
|
77
136
|
await this.record({
|
|
78
137
|
orgId: tenant.orgId,
|
|
79
138
|
actorId: tenant.userId,
|
|
80
139
|
action: input.action,
|
|
81
140
|
targets: input.targets,
|
|
82
|
-
metadata
|
|
141
|
+
metadata,
|
|
83
142
|
factoryProjectId: input.factoryProjectId,
|
|
84
143
|
projectRepositoryId: input.projectRepositoryId,
|
|
85
144
|
context: auditRequestContext(context)
|
|
@@ -100,6 +159,8 @@ var AuditDomain = class {
|
|
|
100
159
|
const threadId = context?.threadId;
|
|
101
160
|
const state = context?.getState();
|
|
102
161
|
if (!orgId || !userId || !threadId || !state?.factoryProjectId) return;
|
|
162
|
+
const modeId = context.session.modeId?.trim();
|
|
163
|
+
const modelId = context.session.modelId?.trim();
|
|
103
164
|
await this.record({
|
|
104
165
|
orgId,
|
|
105
166
|
actorId: `agent:${threadId}`,
|
|
@@ -108,7 +169,9 @@ var AuditDomain = class {
|
|
|
108
169
|
targets: input.targets,
|
|
109
170
|
metadata: {
|
|
110
171
|
...input.metadata,
|
|
111
|
-
startedBy: userId
|
|
172
|
+
startedBy: userId,
|
|
173
|
+
...modeId ? { agentName: `${modeId} agent` } : {},
|
|
174
|
+
...modelId ? { modelId } : {}
|
|
112
175
|
},
|
|
113
176
|
factoryProjectId: state.factoryProjectId,
|
|
114
177
|
projectRepositoryId: state.projectRepositoryId,
|
|
@@ -121,6 +184,41 @@ var AuditDomain = class {
|
|
|
121
184
|
});
|
|
122
185
|
}
|
|
123
186
|
}
|
|
187
|
+
async #resolveActorProfiles(events, requestedActorIds = []) {
|
|
188
|
+
const humanActorIds = [...new Set([...requestedActorIds, ...events.filter((event) => event.actorType === "human").map((event) => event.actorId)].filter(isHumanActorId))].slice(0, MAX_ACTOR_PROFILES);
|
|
189
|
+
if (humanActorIds.length === 0) return {};
|
|
190
|
+
const profiles = {};
|
|
191
|
+
for (const event of events) {
|
|
192
|
+
if (event.actorType !== "human") continue;
|
|
193
|
+
if (!isHumanActorId(event.actorId)) continue;
|
|
194
|
+
if (profiles[event.actorId]) continue;
|
|
195
|
+
const stored = readStoredActorProfile(event.metadata);
|
|
196
|
+
if (!stored?.name) continue;
|
|
197
|
+
profiles[event.actorId] = {
|
|
198
|
+
id: event.actorId,
|
|
199
|
+
name: stored.name,
|
|
200
|
+
...stored.avatarUrl ? { avatarUrl: stored.avatarUrl } : {}
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const unresolved = humanActorIds.filter((actorId) => !profiles[actorId]);
|
|
204
|
+
if (unresolved.length === 0 || !this.#users) return profiles;
|
|
205
|
+
try {
|
|
206
|
+
const users = this.#users.getUsers ? await this.#users.getUsers(unresolved) : await Promise.all(unresolved.map((actorId) => this.#users?.getUser(actorId) ?? null));
|
|
207
|
+
for (const [index, user] of users.entries()) {
|
|
208
|
+
if (!user) continue;
|
|
209
|
+
const name = user.name?.trim() || user.email?.trim() || user.id;
|
|
210
|
+
const actorId = unresolved[index] ?? user.id;
|
|
211
|
+
profiles[actorId] = {
|
|
212
|
+
id: user.id,
|
|
213
|
+
name,
|
|
214
|
+
...user.avatarUrl ? { avatarUrl: user.avatarUrl } : {}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
} catch (err) {
|
|
218
|
+
console.warn("[Audit] Failed to resolve audit actor profiles", { error: err instanceof Error ? err.message : String(err) });
|
|
219
|
+
}
|
|
220
|
+
return profiles;
|
|
221
|
+
}
|
|
124
222
|
routes() {
|
|
125
223
|
return [registerApiRoute("/web/factory/projects/:id/audit", {
|
|
126
224
|
method: "GET",
|
|
@@ -143,7 +241,11 @@ var AuditDomain = class {
|
|
|
143
241
|
before: c.req.query("before") || void 0,
|
|
144
242
|
limit: parseLimitParam(c.req.query("limit"))
|
|
145
243
|
});
|
|
146
|
-
|
|
244
|
+
const actors = await this.#resolveActorProfiles(page.events, parseActorIdsParam(c.req.query("actorIds")));
|
|
245
|
+
return c.json({
|
|
246
|
+
...page,
|
|
247
|
+
actors
|
|
248
|
+
});
|
|
147
249
|
}
|
|
148
250
|
})];
|
|
149
251
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domain.js","names":["#auth","#audit","#projects","#sinks","#agentTenant","#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 } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\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 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 /** 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;\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 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 #sinks: AuditSink[];\n readonly #agentTenant: AuditDomainOptions['agentTenant'];\n\n constructor({ auth, audit, projects, sinks = [], agentTenant }: AuditDomainOptions) {\n this.#auth = auth;\n this.#audit = audit;\n this.#projects = projects;\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 await this.record({\n orgId: tenant.orgId,\n actorId: tenant.userId,\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 await this.record({\n orgId,\n actorId: `agent:${threadId}`,\n actorType: 'agent',\n action: input.action,\n targets: input.targets,\n metadata: { ...input.metadata, startedBy: userId },\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 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 return c.json(page);\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 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\n/** Read the auth gate's stashed user directly from the Hono context. */\nfunction readFactoryAuthUserFromContext(\n context: Context,\n): { name?: string; email?: string; avatarUrl?: string } | undefined {\n const user = context.get('factoryAuthUser') as { name?: string; email?: string; avatarUrl?: string } | undefined;\n return user ?? undefined;\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 = readFactoryAuthUserFromContext(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":";;;;;;;;;;AAiEA,MAAM,6BAA6B;AAOnC,SAAS,uBAAuB,UAA+E;CAC7G,IAAI,CAAC,UAAU,OAAO,KAAA;CACtB,MAAM,MAAM,SAAS;CACrB,IAAI,CAAC,OAAO,OAAO,QAAQ,UAAU,OAAO,KAAA;CAC5C,MAAM,UAAU;CAChB,MAAM,OAAO,OAAO,QAAQ,SAAS,YAAY,QAAQ,KAAK,KAAK,IAAI,QAAQ,KAAK,KAAK,IAAI,KAAA;CAC7F,MAAM,YACJ,OAAO,QAAQ,cAAc,YAAY,QAAQ,UAAU,KAAK,IAAI,QAAQ,UAAU,KAAK,IAAI,KAAA;CACjG,IAAI,CAAC,QAAQ,CAAC,WAAW,OAAO,KAAA;CAChC,OAAO;EAAE,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;EAAI,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;CAAG;AAC1E;;AAGA,SAAS,+BACP,SACmE;CAEnE,OADa,QAAQ,IAAI,iBACf,KAAK,KAAA;AACjB;AAEA,SAAS,0BAA0B,MAIA;CACjC,MAAM,OAAO,KAAK,MAAM,KAAK,KAAK,KAAK,OAAO,KAAK;CACnD,MAAM,YAAY,KAAK,WAAW,KAAK;CACvC,IAAI,CAAC,QAAQ,CAAC,WAAW,OAAO,KAAA;CAChC,OAAO;EAAE,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;EAAI,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;CAAG;AAC1E;AAgBA,MAAM,UAAU;AAChB,MAAM,qBAAqB;AAC3B,MAAM,qBAAqB;AAC3B,MAAM,oCAAoB,IAAI,IAAI;CAChC;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,SAAS,MAAM,GAAqB;CAClC,OAAO;AACT;AAEA,SAAS,kBAAkB,KAA+C;CACxE,IAAI,CAAC,KAAK,OAAO,KAAA;CACjB,MAAM,UAAU,IACb,MAAM,GAAG,CAAC,CACV,KAAI,WAAU,OAAO,KAAK,CAAC,CAAC,CAC5B,OAAO,OAAO,CAAC,CACf,MAAM,GAAG,kBAAkB;CAC9B,OAAO,QAAQ,SAAS,IAAI,UAAU,KAAA;AACxC;AAEA,SAAS,eAAe,SAAgD;CACtE,IAAI,CAAC,SAAS,OAAO;CACrB,OAAO,CAAC,kBAAkB,IAAI,OAAO,KAAK,CAAC,QAAQ,WAAW,QAAQ,KAAK,CAAC,QAAQ,WAAW,SAAS;AAC1G;AAEA,SAAS,mBAAmB,KAAmC;CAC7D,IAAI,CAAC,KAAK,OAAO,CAAC;CAClB,OAAO,CACL,GAAG,IAAI,IACL,IACG,MAAM,GAAG,CAAC,CACV,KAAI,YAAW,QAAQ,KAAK,CAAC,CAAC,CAC9B,OAAO,cAAc,CAC1B,CACF,CAAC,CAAC,MAAM,GAAG,kBAAkB;AAC/B;AAEA,SAAS,gBAAgB,KAA6C;CACpE,IAAI,CAAC,KAAK,OAAO,KAAA;CACjB,MAAM,QAAQ,OAAO,SAAS,KAAK,EAAE;CACrC,OAAO,OAAO,SAAS,KAAK,IAAI,QAAQ,KAAA;AAC1C;AAEA,SAAgB,oBAAoB,GAA0B;CAC5D,MAAM,WAAW,EAAE,IAAI,OAAO,iBAAiB,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK;CACtE,MAAM,YAAY,EAAE,IAAI,OAAO,YAAY;CAC3C,OAAO;EACL,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;EAC/B,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;CACnC;AACF;;AAGA,IAAa,cAAb,MAAoE;CAClE;CACA;CACA;CACA;CACA;CACA;CAEA,YAAY,EAAE,MAAM,OAAO,UAAU,OAAO,QAAQ,CAAC,GAAG,eAAmC;EACzF,KAAKA,QAAQ;EACb,KAAKC,SAAS;EACd,KAAKC,YAAY;EACjB,KAAKC,SAAS;EACd,KAAKC,SAAS;EACd,KAAKC,eAAe;EAEpB,MAAM,sBAAM,IAAI,IAAY;EAC5B,KAAK,MAAM,QAAQ,KAAKD,QAAQ;GAC9B,IAAI,CAAC,KAAK,IAAI,MAAM,IAAI,MAAM,wCAAwC;GACtE,IAAI,IAAI,IAAI,KAAK,EAAE,GAAG,MAAM,IAAI,MAAM,mCAAmC,KAAK,GAAG,EAAE;GACnF,IAAI,IAAI,KAAK,EAAE;EACjB;CACF;CAEA,MAAM,OAAO,OAA6D;EACxE,IAAI;GACF,MAAM,KAAKH,OAAO,YAAY;GAC9B,MAAM,MAAM,MAAM,KAAKA,OAAO,OAAO,KAAK;GAC1C,KAAK,MAAM,QAAQ,KAAKG,QAAQ;IAC9B,IAAI,CAAC,KAAK,OAAO;IACjB,QAAa,QAAQ,CAAC,CACnB,WAAW,KAAK,QAAQ,EAAE,OAAO,IAAI,CAAC,CAAC,CAAC,CACxC,OAAM,QAAO;KACZ,QAAQ,KAAK,oCAAoC;MAC/C,aAAa,KAAK;MAClB,QAAQ,IAAI;MACZ,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;KACxD,CAAC;IACH,CAAC;GACL;GACA,OAAO;EACT,SAAS,KAAK;GACZ,QAAQ,KAAK,wCAAwC;IACnD,QAAQ,MAAM;IACd,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;GACxD,CAAC;GACD,OAAO;EACT;CACF;CAEA,MAAM,KAAK,OAAsD;EAC/D,MAAM,KAAKH,OAAO,YAAY;EAC9B,OAAO,KAAKA,OAAO,KAAK,KAAK;CAC/B;CAEA,MAAM,KAAK,EAAE,SAAS,SAAqE;EACzF,IAAI;GACF,MAAM,SAAS,KAAKD,MAAM,OAAO,OAAO;GACxC,IAAI,CAAC,QAAQ,OAAO;GACpB,MAAM,OAAO,+BAA+B,OAAO;GACnD,MAAM,eAAe,OAAO,0BAA0B,IAAI,IAAI,KAAA;GAC9D,MAAM,WAAW,eACb;IAAE,GAAG,MAAM;KAAW,6BAA6B;GAAa,IAChE,MAAM;GACV,MAAM,KAAK,OAAO;IAChB,OAAO,OAAO;IACd,SAAS,OAAO;IAChB,QAAQ,MAAM;IACd,SAAS,MAAM;IACf;IACA,kBAAkB,MAAM;IACxB,qBAAqB,MAAM;IAC3B,SAAS,oBAAoB,OAAO;GACtC,CAAC;EACH,SAAS,KAAK;GACZ,QAAQ,KAAK,sCAAsC;IACjD,QAAQ,MAAM;IACd,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;GACxD,CAAC;EACH;CACF;CAEA,MAAM,UAAU,EACd,gBACA,SAIgB;EAChB,IAAI;GACF,MAAM,UAAU,eAAe,IAAI,YAAY;GAG/C,MAAM,SAAS,KAAKK,eAAe,cAAc;GACjD,MAAM,QAAQ,QAAQ;GACtB,MAAM,SAAS,QAAQ;GACvB,MAAM,WAAW,SAAS;GAC1B,MAAM,QAAQ,SAAS,SAAS;GAChC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,YAAY,CAAC,OAAO,kBAAkB;GAEhE,MAAM,SAAS,QAAQ,QAAQ,QAAQ,KAAK;GAC5C,MAAM,UAAU,QAAQ,QAAQ,SAAS,KAAK;GAC9C,MAAM,KAAK,OAAO;IAChB;IACA,SAAS,SAAS;IAClB,WAAW;IACX,QAAQ,MAAM;IACd,SAAS,MAAM;IACf,UAAU;KACR,GAAG,MAAM;KACT,WAAW;KACX,GAAI,SAAS,EAAE,WAAW,GAAG,OAAO,QAAQ,IAAI,CAAC;KACjD,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;IAC/B;IACA,kBAAkB,MAAM;IACxB,qBAAqB,MAAM;IAC3B,SAAS,CAAC;GACZ,CAAC;EACH,SAAS,KAAK;GACZ,QAAQ,KAAK,4CAA4C;IACvD,QAAQ,MAAM;IACd,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;GACxD,CAAC;EACH;CACF;CAEA,MAAMC,sBACJ,QACA,oBAA8B,CAAC,GACa;EAC5C,MAAM,gBAAgB,CACpB,GAAG,IAAI,IACL,CACE,GAAG,mBACH,GAAG,OAAO,QAAO,UAAS,MAAM,cAAc,OAAO,CAAC,CAAC,KAAI,UAAS,MAAM,OAAO,CACnF,CAAC,CAAC,OAAO,cAAc,CACzB,CACF,CAAC,CAAC,MAAM,GAAG,kBAAkB;EAC7B,IAAI,cAAc,WAAW,GAAG,OAAO,CAAC;EAMxC,MAAM,WAA8C,CAAC;EACrD,KAAK,MAAM,SAAS,QAAQ;GAC1B,IAAI,MAAM,cAAc,SAAS;GACjC,IAAI,CAAC,eAAe,MAAM,OAAO,GAAG;GACpC,IAAI,SAAS,MAAM,UAAU;GAC7B,MAAM,SAAS,uBAAuB,MAAM,QAAQ;GACpD,IAAI,CAAC,QAAQ,MAAM;GACnB,SAAS,MAAM,WAAW;IACxB,IAAI,MAAM;IACV,MAAM,OAAO;IACb,GAAI,OAAO,YAAY,EAAE,WAAW,OAAO,UAAU,IAAI,CAAC;GAC5D;EACF;EAEA,MAAM,aAAa,cAAc,QAAO,YAAW,CAAC,SAAS,QAAQ;EACrE,IAAI,WAAW,WAAW,KAAK,CAAC,KAAKH,QAAQ,OAAO;EAEpD,IAAI;GACF,MAAM,QAAQ,KAAKA,OAAO,WACtB,MAAM,KAAKA,OAAO,SAAS,UAAU,IACrC,MAAM,QAAQ,IAAI,WAAW,KAAI,YAAW,KAAKA,QAAQ,QAAQ,OAAO,KAAK,IAAI,CAAC;GACtF,KAAK,MAAM,CAAC,OAAO,SAAS,MAAM,QAAQ,GAAG;IAC3C,IAAI,CAAC,MAAM;IACX,MAAM,OAAO,KAAK,MAAM,KAAK,KAAK,KAAK,OAAO,KAAK,KAAK,KAAK;IAC7D,MAAM,UAAU,WAAW,UAAU,KAAK;IAC1C,SAAS,WAAW;KAClB,IAAI,KAAK;KACT;KACA,GAAI,KAAK,YAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;IACxD;GACF;EACF,SAAS,KAAK;GACZ,QAAQ,KAAK,kDAAkD,EAC7D,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EACxD,CAAC;EACH;EACA,OAAO;CACT;CAEA,SAAqB;EACnB,OAAO,CACL,iBAAiB,mCAAmC;GAClD,QAAQ;GACR,SAAS,OAAM,OAAM;IACnB,MAAM,IAAI,MAAM,EAAE;IAClB,MAAM,SAAS,MAAM,KAAKI,eAAe,CAAC;IAC1C,IAAI,cAAc,QAAQ,OAAO,OAAO;IAExC,MAAM,YAAY,EAAE,IAAI,MAAM,IAAI;IAClC,IAAI,CAAC,aAAa,CAAC,QAAQ,KAAK,SAAS,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO,oBAAoB,GAAG,GAAG;IAC7F,MAAM,KAAKL,UAAU,YAAY;IAEjC,IAAI,CAAC,MADiB,KAAKA,UAAU,IAAI;KAAE,OAAO,OAAO;KAAO,IAAI;IAAU,CAAC,GACjE,OAAO,EAAE,KAAK,EAAE,OAAO,oBAAoB,GAAG,GAAG;IAE/D,MAAM,OAAO,MAAM,KAAK,KAAK;KAC3B,OAAO,OAAO;KACd,kBAAkB;KAClB,SAAS,kBAAkB,EAAE,IAAI,MAAM,SAAS,CAAC;KACjD,SAAS,EAAE,IAAI,MAAM,OAAO,KAAK,KAAA;KACjC,QAAQ,EAAE,IAAI,MAAM,QAAQ,KAAK,KAAA;KACjC,OAAO,gBAAgB,EAAE,IAAI,MAAM,OAAO,CAAC;IAC7C,CAAC;IACD,MAAM,SAAS,MAAM,KAAKI,sBAAsB,KAAK,QAAQ,mBAAmB,EAAE,IAAI,MAAM,UAAU,CAAC,CAAC;IACxG,OAAO,EAAE,KAAK;KAAE,GAAG;KAAM;IAAO,CAAC;GACnC;EACF,CAAC,CACH;CACF;CAEA,MAAMC,eAAe,GAAiF;EACpG,MAAM,KAAKP,MAAM,WAAW,CAAC;EAC7B,MAAM,SAAS,KAAKA,MAAM,OAAO,CAAC;EAClC,IAAI,CAAC,QAAQ,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,eAAe,GAAG,GAAG,EAAE;EACvE,IAAI,CAAC,OAAO,OACV,OAAO,EACL,UAAU,EAAE,KAAK;GAAE,OAAO;GAAyB,SAAS;EAA4C,GAAG,GAAG,EAChH;EAEF,OAAO;GAAE,OAAO,OAAO;GAAO,QAAQ,OAAO;EAAO;CACtD;AACF"}
|