@mastra/factory 0.24.0-alpha.7 → 0.24.0-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/boards/review.d.ts.map +1 -1
- package/dist/boards/review.js +21 -1
- package/dist/boards/review.js.map +1 -1
- package/dist/boards/work-transition-policy.d.ts.map +1 -1
- package/dist/boards/work-transition-policy.js +2 -1
- package/dist/boards/work-transition-policy.js.map +1 -1
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +91 -13
- package/dist/factory.js.map +1 -1
- package/dist/integrations/github/rules.d.ts.map +1 -1
- package/dist/integrations/github/rules.js +26 -22
- package/dist/integrations/github/rules.js.map +1 -1
- package/dist/integrations/github/sandbox.d.ts +2 -3
- package/dist/integrations/github/sandbox.d.ts.map +1 -1
- package/dist/integrations/github/sandbox.js +78 -7
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/integrations/gitlab/rules.d.ts.map +1 -1
- package/dist/integrations/gitlab/rules.js +28 -21
- package/dist/integrations/gitlab/rules.js.map +1 -1
- package/dist/integrations/slack/integration.d.ts.map +1 -1
- package/dist/integrations/slack/integration.js +33 -6
- package/dist/integrations/slack/integration.js.map +1 -1
- package/dist/integrations/slack/slack.d.ts.map +1 -1
- package/dist/integrations/slack/slack.js +25 -24
- package/dist/integrations/slack/slack.js.map +1 -1
- package/dist/routes/attention-activity.d.ts.map +1 -1
- package/dist/routes/attention-activity.js +5 -2
- package/dist/routes/attention-activity.js.map +1 -1
- package/dist/routes/attention-providers.d.ts +0 -1
- package/dist/routes/attention-providers.d.ts.map +1 -1
- package/dist/routes/attention-providers.js +5 -6
- package/dist/routes/attention-providers.js.map +1 -1
- package/dist/routes/config.d.ts +4 -0
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +20 -6
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/provider-credentials.d.ts +2 -1
- package/dist/routes/provider-credentials.d.ts.map +1 -1
- package/dist/routes/provider-credentials.js +6 -2
- package/dist/routes/provider-credentials.js.map +1 -1
- package/dist/rules/dispatch-errors.d.ts.map +1 -1
- package/dist/rules/dispatch-errors.js +12 -0
- package/dist/rules/dispatch-errors.js.map +1 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +42 -3
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/session/channel-reply-processor.d.ts +35 -0
- package/dist/session/channel-reply-processor.d.ts.map +1 -0
- package/dist/session/channel-reply-processor.js +46 -0
- package/dist/session/channel-reply-processor.js.map +1 -0
- package/dist/session/channel-session-link-processor.d.ts +39 -0
- package/dist/session/channel-session-link-processor.d.ts.map +1 -0
- package/dist/session/channel-session-link-processor.js +99 -0
- package/dist/session/channel-session-link-processor.js.map +1 -0
- package/dist/session/factory-session.d.ts +4 -0
- package/dist/session/factory-session.d.ts.map +1 -1
- package/dist/session/factory-session.js +1 -0
- package/dist/session/factory-session.js.map +1 -1
- package/dist/session/reply-credential-processor.d.ts.map +1 -1
- package/dist/session/reply-credential-processor.js +4 -6
- package/dist/session/reply-credential-processor.js.map +1 -1
- package/dist/session/run-audit.d.ts +24 -0
- package/dist/session/run-audit.d.ts.map +1 -1
- package/dist/session/run-audit.js +93 -7
- package/dist/session/run-audit.js.map +1 -1
- package/dist/session/source-control-tools.d.ts.map +1 -1
- package/dist/session/source-control-tools.js.map +1 -1
- package/dist/storage/domains/source-control/base.d.ts +7 -0
- package/dist/storage/domains/source-control/base.d.ts.map +1 -1
- package/dist/storage/domains/source-control/base.js +12 -0
- package/dist/storage/domains/source-control/base.js.map +1 -1
- package/dist/storage/domains/source-control/inmemory.d.ts +4 -0
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/source-control/inmemory.js +8 -0
- package/dist/storage/domains/source-control/inmemory.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +2 -2
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +5 -0
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/tools/approval-tools.d.ts +1 -1
- package/dist/tools/approval-tools.d.ts.map +1 -1
- package/dist/tools/approval-tools.js +3 -1
- package/dist/tools/approval-tools.js.map +1 -1
- package/dist/tools/deliver.d.ts +16 -2
- package/dist/tools/deliver.d.ts.map +1 -1
- package/dist/tools/deliver.js +12 -2
- package/dist/tools/deliver.js.map +1 -1
- package/dist/tools/index.d.ts +6 -5
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +9 -12
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/messaging-tools.d.ts +1 -1
- package/dist/tools/messaging-tools.d.ts.map +1 -1
- package/dist/tools/messaging-tools.js +84 -3
- package/dist/tools/messaging-tools.js.map +1 -1
- package/dist/tools/permissions.d.ts.map +1 -1
- package/dist/tools/permissions.js +3 -0
- package/dist/tools/permissions.js.map +1 -1
- package/dist/tools/read-tools.d.ts +3 -0
- package/dist/tools/read-tools.d.ts.map +1 -1
- package/dist/tools/read-tools.js +16 -8
- package/dist/tools/read-tools.js.map +1 -1
- package/dist/tools/reports.d.ts.map +1 -1
- package/dist/tools/reports.js +3 -1
- package/dist/tools/reports.js.map +1 -1
- package/dist/tools/scope.d.ts +6 -0
- package/dist/tools/scope.d.ts.map +1 -1
- package/dist/tools/scope.js +10 -1
- package/dist/tools/scope.js.map +1 -1
- package/dist/tools/session-tools.d.ts +1 -1
- package/dist/tools/session-tools.d.ts.map +1 -1
- package/dist/tools/session-tools.js +11 -7
- package/dist/tools/session-tools.js.map +1 -1
- package/dist/tools/sessions-state-processor.d.ts +5 -1
- package/dist/tools/sessions-state-processor.d.ts.map +1 -1
- package/dist/tools/sessions-state-processor.js +32 -48
- package/dist/tools/sessions-state-processor.js.map +1 -1
- package/dist/tools/side-sessions.d.ts +53 -0
- package/dist/tools/side-sessions.d.ts.map +1 -0
- package/dist/tools/side-sessions.js +83 -0
- package/dist/tools/side-sessions.js.map +1 -0
- package/dist/tools/work-item-tools.d.ts.map +1 -1
- package/dist/tools/work-item-tools.js +45 -25
- package/dist/tools/work-item-tools.js.map +1 -1
- package/package.json +6 -6
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
//#region src/session/channel-reply-processor.ts
|
|
2
|
+
const ID = "factory-channel-reply";
|
|
3
|
+
const SESSION_LINK_LABEL = "Open session ↗";
|
|
4
|
+
/**
|
|
5
|
+
* Tells the agent how to open a reply when the run came in over a chat
|
|
6
|
+
* channel (Slack, …): acknowledge a job in one line that names it — "On it —
|
|
7
|
+
* sleeping 30s, then running the echo" — before starting, so the sender sees
|
|
8
|
+
* a task-specific first message rather than a silent typing status until the
|
|
9
|
+
* first tool finishes. The channel driver posts that opener on its own before
|
|
10
|
+
* the tool widget, so the instruction and the rendering agree. The session
|
|
11
|
+
* link is not the model's job: `FactoryChannelSessionLinkProcessor` appends it
|
|
12
|
+
* to the first reply of a new session, so it is there whether or not the
|
|
13
|
+
* model judged the request worth an opener. No-op for runs without channel
|
|
14
|
+
* context (Studio, API, boards).
|
|
15
|
+
*/
|
|
16
|
+
var FactoryChannelReplyProcessor = class {
|
|
17
|
+
id = ID;
|
|
18
|
+
processInputStep({ messageList, requestContext }) {
|
|
19
|
+
if (!requestContext?.get("channel")) return void 0;
|
|
20
|
+
messageList.addSystem({
|
|
21
|
+
role: "system",
|
|
22
|
+
content: ["When a request will take tools or more than a moment to answer, reply first with one short line that says what you are about to do — \"On it — <the task in a few words>.\" — then do the work; the opener posts as its own message before any progress is shown. Give direct answers and trivial replies without an opener.", "Finish with the outcome, not a replay of the steps."].join("\n")
|
|
23
|
+
}, ID);
|
|
24
|
+
return { messageList };
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The web URL for a session, or undefined without a public origin. Same
|
|
29
|
+
* routes the Factory UI navigates to: a repo-backed thread's resourceId is the
|
|
30
|
+
* Factory user-session id and doubles as the workspace segment; chat-only
|
|
31
|
+
* threads keep the literal `channel` segment and name their resource in the
|
|
32
|
+
* query; a session with no factory falls back to the factory-agnostic
|
|
33
|
+
* /threads/ redirect, which forwards the query through.
|
|
34
|
+
*/
|
|
35
|
+
function sessionLink({ threadId, resourceId, factoryProjectId }) {
|
|
36
|
+
const origin = process.env.MASTRACODE_PUBLIC_URL;
|
|
37
|
+
if (!origin) return void 0;
|
|
38
|
+
const isChatOnly = resourceId.startsWith("channel:");
|
|
39
|
+
const resourceParam = `?resourceId=${encodeURIComponent(resourceId)}`;
|
|
40
|
+
if (!factoryProjectId) return `${origin}/threads/${encodeURIComponent(threadId)}${resourceParam}`;
|
|
41
|
+
return `${origin}/factories/${encodeURIComponent(factoryProjectId)}/workspaces/${isChatOnly ? "channel" : encodeURIComponent(resourceId)}/threads/${encodeURIComponent(threadId)}${isChatOnly ? resourceParam : ""}`;
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { FactoryChannelReplyProcessor, SESSION_LINK_LABEL, sessionLink };
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=channel-reply-processor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-reply-processor.js","names":[],"sources":["../../src/session/channel-reply-processor.ts"],"sourcesContent":["import type { ProcessInputStepArgs, Processor } from '@mastra/core/processors';\n\nconst ID = 'factory-channel-reply';\n\nexport const SESSION_LINK_LABEL = 'Open session ↗';\n\n/**\n * Tells the agent how to open a reply when the run came in over a chat\n * channel (Slack, …): acknowledge a job in one line that names it — \"On it —\n * sleeping 30s, then running the echo\" — before starting, so the sender sees\n * a task-specific first message rather than a silent typing status until the\n * first tool finishes. The channel driver posts that opener on its own before\n * the tool widget, so the instruction and the rendering agree. The session\n * link is not the model's job: `FactoryChannelSessionLinkProcessor` appends it\n * to the first reply of a new session, so it is there whether or not the\n * model judged the request worth an opener. No-op for runs without channel\n * context (Studio, API, boards).\n */\nexport class FactoryChannelReplyProcessor implements Processor {\n readonly id = ID;\n\n processInputStep({ messageList, requestContext }: ProcessInputStepArgs) {\n if (!requestContext?.get('channel')) return undefined;\n messageList.addSystem(\n {\n role: 'system',\n content: [\n 'When a request will take tools or more than a moment to answer, reply first with one short line that says what you are about to do — \"On it — <the task in a few words>.\" — then do the work; the opener posts as its own message before any progress is shown. Give direct answers and trivial replies without an opener.',\n 'Finish with the outcome, not a replay of the steps.',\n ].join('\\n'),\n },\n ID,\n );\n return { messageList };\n }\n}\n\nexport interface SessionLinkTarget {\n threadId: string;\n resourceId: string;\n factoryProjectId?: string;\n}\n\n/**\n * The web URL for a session, or undefined without a public origin. Same\n * routes the Factory UI navigates to: a repo-backed thread's resourceId is the\n * Factory user-session id and doubles as the workspace segment; chat-only\n * threads keep the literal `channel` segment and name their resource in the\n * query; a session with no factory falls back to the factory-agnostic\n * /threads/ redirect, which forwards the query through.\n */\nexport function sessionLink({ threadId, resourceId, factoryProjectId }: SessionLinkTarget): string | undefined {\n const origin = process.env.MASTRACODE_PUBLIC_URL;\n if (!origin) return undefined;\n const isChatOnly = resourceId.startsWith('channel:');\n const resourceParam = `?resourceId=${encodeURIComponent(resourceId)}`;\n if (!factoryProjectId) return `${origin}/threads/${encodeURIComponent(threadId)}${resourceParam}`;\n const workspaceSegment = isChatOnly ? 'channel' : encodeURIComponent(resourceId);\n return `${origin}/factories/${encodeURIComponent(factoryProjectId)}/workspaces/${workspaceSegment}/threads/${encodeURIComponent(threadId)}${isChatOnly ? resourceParam : ''}`;\n}\n"],"mappings":";AAEA,MAAM,KAAK;AAEX,MAAa,qBAAqB;;;;;;;;;;;;;AAclC,IAAa,+BAAb,MAA+D;CAC7D,KAAc;CAEd,iBAAiB,EAAE,aAAa,kBAAwC;EACtE,IAAI,CAAC,gBAAgB,IAAI,SAAS,GAAG,OAAO,KAAA;EAC5C,YAAY,UACV;GACE,MAAM;GACN,SAAS,CACP,gUACA,qDACF,CAAC,CAAC,KAAK,IAAI;EACb,GACA,EACF;EACA,OAAO,EAAE,YAAY;CACvB;AACF;;;;;;;;;AAgBA,SAAgB,YAAY,EAAE,UAAU,YAAY,oBAA2D;CAC7G,MAAM,SAAS,QAAQ,IAAI;CAC3B,IAAI,CAAC,QAAQ,OAAO,KAAA;CACpB,MAAM,aAAa,WAAW,WAAW,UAAU;CACnD,MAAM,gBAAgB,eAAe,mBAAmB,UAAU;CAClE,IAAI,CAAC,kBAAkB,OAAO,GAAG,OAAO,WAAW,mBAAmB,QAAQ,IAAI;CAElF,OAAO,GAAG,OAAO,aAAa,mBAAmB,gBAAgB,EAAE,cAD1C,aAAa,YAAY,mBAAmB,UAAU,EACmB,WAAW,mBAAmB,QAAQ,IAAI,aAAa,gBAAgB;AAC3K"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Processor, ProcessOutputStepArgs, ProcessOutputStreamArgs } from '@mastra/core/processors';
|
|
2
|
+
import type { RequestContext } from '@mastra/core/request-context';
|
|
3
|
+
import type { ChunkType } from '@mastra/core/stream';
|
|
4
|
+
/**
|
|
5
|
+
* Request-context marker a channel handler sets on the run that opens a new
|
|
6
|
+
* thread. Only that run gets the link; follow-ups in the same thread don't.
|
|
7
|
+
*/
|
|
8
|
+
export declare const NEW_CHANNEL_SESSION_KEY = "factory__newChannelSession";
|
|
9
|
+
export declare function markNewChannelSession(requestContext: RequestContext): void;
|
|
10
|
+
/**
|
|
11
|
+
* Emits an `Open session ↗` link right after the first reply of a new channel
|
|
12
|
+
* session, so the link closes the reply rather than leading it. The channel
|
|
13
|
+
* driver ends a post at `text-end`, so Slack shows the link as its own short
|
|
14
|
+
* message after the reply.
|
|
15
|
+
*
|
|
16
|
+
* "End of the reply" is not `text-end`: a reply with citations is several
|
|
17
|
+
* text blocks (`text … source … text`), each with its own `text-end`. The
|
|
18
|
+
* reply ends at the first `tool-call` (the opener before tools run) or at
|
|
19
|
+
* `step-finish` (a reply with no tools), whichever comes first after some
|
|
20
|
+
* text. A processor can only return one part, so on that chunk this returns
|
|
21
|
+
* a `text-delta` carrying the link and stashes the chunk itself under
|
|
22
|
+
* `REPROCESS_PART_KEY`, which the runner re-drives through the chain right
|
|
23
|
+
* after — the link lands before it and downstream processors see both.
|
|
24
|
+
* Factory-emitted rather than left to the model so a skipped opener can't
|
|
25
|
+
* drop it. No-op without the new-session marker or a public origin.
|
|
26
|
+
*
|
|
27
|
+
* The streamed text is also what gets persisted, and the link is a Slack
|
|
28
|
+
* display detail: in memory it would show the model its own `[↗](…)` on
|
|
29
|
+
* later turns (inviting it to fabricate links) and render a self-link in
|
|
30
|
+
* the web transcript. The link delta opens its own text part, so
|
|
31
|
+
* `processOutputStep` drops that part from the assistant message before
|
|
32
|
+
* `MessageHistory` saves it.
|
|
33
|
+
*/
|
|
34
|
+
export declare class FactoryChannelSessionLinkProcessor implements Processor {
|
|
35
|
+
readonly id = "factory-channel-session-link";
|
|
36
|
+
processOutputStream({ part, state, requestContext }: ProcessOutputStreamArgs): Promise<ChunkType>;
|
|
37
|
+
processOutputStep({ messageList, requestContext }: ProcessOutputStepArgs): import("@mastra/core/agent").MessageList;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=channel-session-link-processor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-session-link-processor.d.ts","sourceRoot":"","sources":["../../src/session/channel-session-link-processor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AACzG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAKrD;;;GAGG;AACH,eAAO,MAAM,uBAAuB,+BAA+B,CAAC;AAEpE,wBAAgB,qBAAqB,CAAC,cAAc,EAAE,cAAc,GAAG,IAAI,CAE1E;AAaD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,kCAAmC,YAAW,SAAS;IAClE,QAAQ,CAAC,EAAE,kCAAkC;IAEvC,mBAAmB,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,uBAAuB,GAAG,OAAO,CAAC,SAAS,CAAC;IAmBvG,iBAAiB,CAAC,EAAE,WAAW,EAAE,cAAc,EAAE,EAAE,qBAAqB;CAiBzE"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { readFactorySessionScope } from "../rules/binding-context.js";
|
|
2
|
+
import { SESSION_LINK_LABEL, sessionLink } from "./channel-reply-processor.js";
|
|
3
|
+
//#region src/session/channel-session-link-processor.ts
|
|
4
|
+
/**
|
|
5
|
+
* Request-context marker a channel handler sets on the run that opens a new
|
|
6
|
+
* thread. Only that run gets the link; follow-ups in the same thread don't.
|
|
7
|
+
*/
|
|
8
|
+
const NEW_CHANNEL_SESSION_KEY = "factory__newChannelSession";
|
|
9
|
+
function markNewChannelSession(requestContext) {
|
|
10
|
+
requestContext.set(NEW_CHANNEL_SESSION_KEY, true);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* State key the core ProcessorRunner reads to re-drive one extra part through
|
|
14
|
+
* the output chain after the part a processor returned. Internal to core
|
|
15
|
+
* (`processors/stream-reprocess.ts`, used by `BatchPartsProcessor`); mirrored
|
|
16
|
+
* here rather than exported so Factory's one use doesn't widen core's API.
|
|
17
|
+
*/
|
|
18
|
+
const REPROCESS_PART_KEY = "__mastraReprocessPart";
|
|
19
|
+
/** Text-part id of the link delta; never collides with a model block id. */
|
|
20
|
+
const LINK_PART_ID = "factory-session-link";
|
|
21
|
+
/**
|
|
22
|
+
* Emits an `Open session ↗` link right after the first reply of a new channel
|
|
23
|
+
* session, so the link closes the reply rather than leading it. The channel
|
|
24
|
+
* driver ends a post at `text-end`, so Slack shows the link as its own short
|
|
25
|
+
* message after the reply.
|
|
26
|
+
*
|
|
27
|
+
* "End of the reply" is not `text-end`: a reply with citations is several
|
|
28
|
+
* text blocks (`text … source … text`), each with its own `text-end`. The
|
|
29
|
+
* reply ends at the first `tool-call` (the opener before tools run) or at
|
|
30
|
+
* `step-finish` (a reply with no tools), whichever comes first after some
|
|
31
|
+
* text. A processor can only return one part, so on that chunk this returns
|
|
32
|
+
* a `text-delta` carrying the link and stashes the chunk itself under
|
|
33
|
+
* `REPROCESS_PART_KEY`, which the runner re-drives through the chain right
|
|
34
|
+
* after — the link lands before it and downstream processors see both.
|
|
35
|
+
* Factory-emitted rather than left to the model so a skipped opener can't
|
|
36
|
+
* drop it. No-op without the new-session marker or a public origin.
|
|
37
|
+
*
|
|
38
|
+
* The streamed text is also what gets persisted, and the link is a Slack
|
|
39
|
+
* display detail: in memory it would show the model its own `[↗](…)` on
|
|
40
|
+
* later turns (inviting it to fabricate links) and render a self-link in
|
|
41
|
+
* the web transcript. The link delta opens its own text part, so
|
|
42
|
+
* `processOutputStep` drops that part from the assistant message before
|
|
43
|
+
* `MessageHistory` saves it.
|
|
44
|
+
*/
|
|
45
|
+
var FactoryChannelSessionLinkProcessor = class {
|
|
46
|
+
id = "factory-channel-session-link";
|
|
47
|
+
async processOutputStream({ part, state, requestContext }) {
|
|
48
|
+
if (state.linked || !requestContext?.get("factory__newChannelSession")) return part;
|
|
49
|
+
if (part.type === "text-delta") {
|
|
50
|
+
state.textStarted = true;
|
|
51
|
+
return part;
|
|
52
|
+
}
|
|
53
|
+
if (part.type !== "tool-call" && part.type !== "step-finish" || !state.textStarted) return part;
|
|
54
|
+
state.linked = true;
|
|
55
|
+
const link = sessionLinkFromRequest(requestContext);
|
|
56
|
+
if (!link) return part;
|
|
57
|
+
state[REPROCESS_PART_KEY] = part;
|
|
58
|
+
return {
|
|
59
|
+
type: "text-delta",
|
|
60
|
+
runId: part.runId,
|
|
61
|
+
from: part.from,
|
|
62
|
+
payload: {
|
|
63
|
+
id: LINK_PART_ID,
|
|
64
|
+
text: linkSuffix(link)
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
processOutputStep({ messageList, requestContext }) {
|
|
69
|
+
if (!requestContext?.get("factory__newChannelSession")) return messageList;
|
|
70
|
+
const link = sessionLinkFromRequest(requestContext);
|
|
71
|
+
if (!link) return messageList;
|
|
72
|
+
const suffix = linkSuffix(link);
|
|
73
|
+
for (const message of messageList.get.response.db()) {
|
|
74
|
+
if (message.role !== "assistant") continue;
|
|
75
|
+
const index = message.content.parts.findIndex((part) => part.type === "text" && part.text === suffix);
|
|
76
|
+
if (index === -1) continue;
|
|
77
|
+
message.content.parts.splice(index, 1);
|
|
78
|
+
if (typeof message.content.content === "string" && message.content.content.endsWith(suffix)) message.content.content = message.content.content.slice(0, -suffix.length);
|
|
79
|
+
return messageList;
|
|
80
|
+
}
|
|
81
|
+
return messageList;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
function linkSuffix(link) {
|
|
85
|
+
return `\n\n[${SESSION_LINK_LABEL}](${link})`;
|
|
86
|
+
}
|
|
87
|
+
function sessionLinkFromRequest(requestContext) {
|
|
88
|
+
const controller = requestContext.get("controller");
|
|
89
|
+
if (!controller?.threadId) return void 0;
|
|
90
|
+
return sessionLink({
|
|
91
|
+
threadId: controller.threadId,
|
|
92
|
+
resourceId: controller.resourceId,
|
|
93
|
+
factoryProjectId: readFactorySessionScope(requestContext)?.factoryProjectId
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
export { FactoryChannelSessionLinkProcessor, NEW_CHANNEL_SESSION_KEY, markNewChannelSession };
|
|
98
|
+
|
|
99
|
+
//# sourceMappingURL=channel-session-link-processor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-session-link-processor.js","names":[],"sources":["../../src/session/channel-session-link-processor.ts"],"sourcesContent":["import type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { Processor, ProcessOutputStepArgs, ProcessOutputStreamArgs } from '@mastra/core/processors';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport type { ChunkType } from '@mastra/core/stream';\n\nimport { readFactorySessionScope } from '../rules/binding-context.js';\nimport { SESSION_LINK_LABEL, sessionLink } from './channel-reply-processor.js';\n\n/**\n * Request-context marker a channel handler sets on the run that opens a new\n * thread. Only that run gets the link; follow-ups in the same thread don't.\n */\nexport const NEW_CHANNEL_SESSION_KEY = 'factory__newChannelSession';\n\nexport function markNewChannelSession(requestContext: RequestContext): void {\n requestContext.set(NEW_CHANNEL_SESSION_KEY, true);\n}\n\n/**\n * State key the core ProcessorRunner reads to re-drive one extra part through\n * the output chain after the part a processor returned. Internal to core\n * (`processors/stream-reprocess.ts`, used by `BatchPartsProcessor`); mirrored\n * here rather than exported so Factory's one use doesn't widen core's API.\n */\nconst REPROCESS_PART_KEY = '__mastraReprocessPart';\n\n/** Text-part id of the link delta; never collides with a model block id. */\nconst LINK_PART_ID = 'factory-session-link';\n\n/**\n * Emits an `Open session ↗` link right after the first reply of a new channel\n * session, so the link closes the reply rather than leading it. The channel\n * driver ends a post at `text-end`, so Slack shows the link as its own short\n * message after the reply.\n *\n * \"End of the reply\" is not `text-end`: a reply with citations is several\n * text blocks (`text … source … text`), each with its own `text-end`. The\n * reply ends at the first `tool-call` (the opener before tools run) or at\n * `step-finish` (a reply with no tools), whichever comes first after some\n * text. A processor can only return one part, so on that chunk this returns\n * a `text-delta` carrying the link and stashes the chunk itself under\n * `REPROCESS_PART_KEY`, which the runner re-drives through the chain right\n * after — the link lands before it and downstream processors see both.\n * Factory-emitted rather than left to the model so a skipped opener can't\n * drop it. No-op without the new-session marker or a public origin.\n *\n * The streamed text is also what gets persisted, and the link is a Slack\n * display detail: in memory it would show the model its own `[↗](…)` on\n * later turns (inviting it to fabricate links) and render a self-link in\n * the web transcript. The link delta opens its own text part, so\n * `processOutputStep` drops that part from the assistant message before\n * `MessageHistory` saves it.\n */\nexport class FactoryChannelSessionLinkProcessor implements Processor {\n readonly id = 'factory-channel-session-link';\n\n async processOutputStream({ part, state, requestContext }: ProcessOutputStreamArgs): Promise<ChunkType> {\n if (state.linked || !requestContext?.get(NEW_CHANNEL_SESSION_KEY)) return part;\n if (part.type === 'text-delta') {\n state.textStarted = true;\n return part;\n }\n if ((part.type !== 'tool-call' && part.type !== 'step-finish') || !state.textStarted) return part;\n state.linked = true;\n const link = sessionLinkFromRequest(requestContext);\n if (!link) return part;\n state[REPROCESS_PART_KEY] = part;\n return {\n type: 'text-delta',\n runId: part.runId,\n from: part.from,\n payload: { id: LINK_PART_ID, text: linkSuffix(link) },\n };\n }\n\n processOutputStep({ messageList, requestContext }: ProcessOutputStepArgs) {\n if (!requestContext?.get(NEW_CHANNEL_SESSION_KEY)) return messageList;\n const link = sessionLinkFromRequest(requestContext);\n if (!link) return messageList;\n const suffix = linkSuffix(link);\n for (const message of messageList.get.response.db()) {\n if (message.role !== 'assistant') continue;\n const index = message.content.parts.findIndex(part => part.type === 'text' && part.text === suffix);\n if (index === -1) continue;\n message.content.parts.splice(index, 1);\n if (typeof message.content.content === 'string' && message.content.content.endsWith(suffix)) {\n message.content.content = message.content.content.slice(0, -suffix.length);\n }\n return messageList;\n }\n return messageList;\n }\n}\n\nfunction linkSuffix(link: string): string {\n // Own paragraph: the link closes the message, it is not part of the last sentence.\n return `\\n\\n[${SESSION_LINK_LABEL}](${link})`;\n}\n\nfunction sessionLinkFromRequest(requestContext: RequestContext): string | undefined {\n const controller = requestContext.get('controller') as AgentControllerRequestContext | undefined;\n if (!controller?.threadId) return undefined;\n return sessionLink({\n threadId: controller.threadId,\n resourceId: controller.resourceId,\n factoryProjectId: readFactorySessionScope(requestContext)?.factoryProjectId,\n });\n}\n"],"mappings":";;;;;;;AAYA,MAAa,0BAA0B;AAEvC,SAAgB,sBAAsB,gBAAsC;CAC1E,eAAe,IAAI,yBAAyB,IAAI;AAClD;;;;;;;AAQA,MAAM,qBAAqB;;AAG3B,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;AA0BrB,IAAa,qCAAb,MAAqE;CACnE,KAAc;CAEd,MAAM,oBAAoB,EAAE,MAAM,OAAO,kBAA+D;EACtG,IAAI,MAAM,UAAU,CAAC,gBAAgB,IAAA,4BAA2B,GAAG,OAAO;EAC1E,IAAI,KAAK,SAAS,cAAc;GAC9B,MAAM,cAAc;GACpB,OAAO;EACT;EACA,IAAK,KAAK,SAAS,eAAe,KAAK,SAAS,iBAAkB,CAAC,MAAM,aAAa,OAAO;EAC7F,MAAM,SAAS;EACf,MAAM,OAAO,uBAAuB,cAAc;EAClD,IAAI,CAAC,MAAM,OAAO;EAClB,MAAM,sBAAsB;EAC5B,OAAO;GACL,MAAM;GACN,OAAO,KAAK;GACZ,MAAM,KAAK;GACX,SAAS;IAAE,IAAI;IAAc,MAAM,WAAW,IAAI;GAAE;EACtD;CACF;CAEA,kBAAkB,EAAE,aAAa,kBAAyC;EACxE,IAAI,CAAC,gBAAgB,IAAA,4BAA2B,GAAG,OAAO;EAC1D,MAAM,OAAO,uBAAuB,cAAc;EAClD,IAAI,CAAC,MAAM,OAAO;EAClB,MAAM,SAAS,WAAW,IAAI;EAC9B,KAAK,MAAM,WAAW,YAAY,IAAI,SAAS,GAAG,GAAG;GACnD,IAAI,QAAQ,SAAS,aAAa;GAClC,MAAM,QAAQ,QAAQ,QAAQ,MAAM,WAAU,SAAQ,KAAK,SAAS,UAAU,KAAK,SAAS,MAAM;GAClG,IAAI,UAAU,IAAI;GAClB,QAAQ,QAAQ,MAAM,OAAO,OAAO,CAAC;GACrC,IAAI,OAAO,QAAQ,QAAQ,YAAY,YAAY,QAAQ,QAAQ,QAAQ,SAAS,MAAM,GACxF,QAAQ,QAAQ,UAAU,QAAQ,QAAQ,QAAQ,MAAM,GAAG,CAAC,OAAO,MAAM;GAE3E,OAAO;EACT;EACA,OAAO;CACT;AACF;AAEA,SAAS,WAAW,MAAsB;CAExC,OAAO,QAAQ,mBAAmB,IAAI,KAAK;AAC7C;AAEA,SAAS,uBAAuB,gBAAoD;CAClF,MAAM,aAAa,eAAe,IAAI,YAAY;CAClD,IAAI,CAAC,YAAY,UAAU,OAAO,KAAA;CAClC,OAAO,YAAY;EACjB,UAAU,WAAW;EACrB,YAAY,WAAW;EACvB,kBAAkB,wBAAwB,cAAc,CAAC,EAAE;CAC7D,CAAC;AACH"}
|
|
@@ -31,6 +31,10 @@ export interface SourceControlSessionLookup {
|
|
|
31
31
|
sessionId: string;
|
|
32
32
|
title: string;
|
|
33
33
|
}): Promise<void>;
|
|
34
|
+
setOriginMuted(args: {
|
|
35
|
+
sessionId: string;
|
|
36
|
+
muted: boolean;
|
|
37
|
+
}): Promise<void>;
|
|
34
38
|
markFirstMessage(args: {
|
|
35
39
|
sessionId: string;
|
|
36
40
|
}): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory-session.d.ts","sourceRoot":"","sources":["../../src/session/factory-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAGrE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEtF,OAAO,KAAK,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAEvG,OAAO,KAAK,EACV,oBAAoB,EACpB,8BAA8B,EAC9B,0BAA0B,EAC3B,MAAM,2CAA2C,CAAC;AAInD,KAAK,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,eAAe,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;AAE7F,qBAAa,iCAAkC,SAAQ,KAAK;CAAG;AAE/D,8EAA8E;AAC9E,wBAAsB,0BAA0B,CAAC,IAAI,EAAE;IACrD,QAAQ,EAAE,sBAAsB,GAAG,SAAS,CAAC;IAC7C,aAAa,EAAE,oBAAoB,GAAG,SAAS,CAAC;IAChD,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,uGAAuG;IACvG,OAAO,EAAE,KAAK,GAAG,SAAS,CAAC;CAC5B,GAAG,OAAO,CAAC;IAAE,UAAU,EAAE,mBAAmB,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAoBjE;AAED,MAAM,WAAW,0BAA0B;IACzC,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IACxE,2BAA2B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,0BAA0B,GAAG,IAAI,CAAC,CAAC;IAC3F,yEAAyE;IACzE,YAAY,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAAC;IAChG,MAAM,CAAC,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,gBAAgB,CAAC,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,uBAAuB,CAAC,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrE;AAED;;;;GAIG;AACH,wBAAgB,gCAAgC,CAC9C,cAAc,EAAE,SAAS,0BAA0B,EAAE,GACpD,0BAA0B,
|
|
1
|
+
{"version":3,"file":"factory-session.d.ts","sourceRoot":"","sources":["../../src/session/factory-session.ts"],"names":[],"mappings":"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"}
|
|
@@ -51,6 +51,7 @@ function createSourceControlSessionLookup(sourceControls) {
|
|
|
51
51
|
getSourceControlBySessionId: async (sessionId) => (await resolve(sessionId))?.sourceControl ?? null,
|
|
52
52
|
listByOrigin: async (args) => (await Promise.all(sourceControls.map((sourceControl) => sourceControl.sessions.listByOrigin(args)))).flat(),
|
|
53
53
|
rename: async (args) => (await resolve(args.sessionId))?.sourceControl.sessions.rename(args),
|
|
54
|
+
setOriginMuted: async (args) => (await resolve(args.sessionId))?.sourceControl.sessions.setOriginMuted(args),
|
|
54
55
|
markFirstMessage: async (args) => (await resolve(args.sessionId))?.sourceControl.sessions.markFirstMessage(args),
|
|
55
56
|
markFirstMeaningfulExec: async (args) => (await resolve(args.sessionId))?.sourceControl.sessions.markFirstMeaningfulExec(args)
|
|
56
57
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory-session.js","names":[],"sources":["../../src/session/factory-session.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\n\nimport { resolveTenantProviderDefaultModel } from '../routes/tenant-credentials.js';\nimport type { ModelDefaultsStorage } from '../storage/domains/model-defaults/base.js';\nimport { effectiveModelScope } from '../storage/domains/projects/base.js';\nimport type { EffectiveModelScope, FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport { SourceControlConnectionNotFoundError } from '../storage/domains/source-control/base.js';\nimport type {\n SourceControlSession,\n SourceControlSessionVisibility,\n SourceControlStorageHandle,\n} from '../storage/domains/source-control/base.js';\nimport { resolveEnvironmentRepositories } from './environment-repositories.js';\nimport { seedSessionOrg } from './org-seed.js';\n\ntype FactorySession = Awaited<ReturnType<AgentController<MastraCodeState>['createSession']>>;\n\nexport class FactorySourceControlConflictError extends Error {}\n\n/** Resolve the model and exclusive credential scope for a Factory session. */\nexport async function resolveFactorySessionModel(args: {\n projects: FactoryProjectsStorage | undefined;\n modelDefaults: ModelDefaultsStorage | undefined;\n factoryProjectId: string | undefined;\n orgId: string;\n ownerUserId: string;\n /** A work-item run or an interactive session; a hybrid Factory resolves a different scope for each. */\n context: 'run' | 'session';\n}): Promise<{ modelScope: EffectiveModelScope; modelId?: string }> {\n if (!args.projects || !args.factoryProjectId) throw new Error('Factory model policy is unavailable.');\n\n const project = await args.projects.getById({ id: args.factoryProjectId });\n if (!project || project.orgId !== args.orgId) throw new Error('Factory model policy is unavailable.');\n if (effectiveModelScope(project.modelScope, args.context) === 'factory') {\n return { modelScope: 'factory', ...(project.defaultModelId ? { modelId: project.defaultModelId } : {}) };\n }\n\n try {\n const userDefault = await args.modelDefaults?.get({ orgId: args.orgId, userId: args.ownerUserId });\n return { modelScope: 'user', ...(userDefault?.modelId ? { modelId: userDefault.modelId } : {}) };\n } catch (error) {\n console.warn('[Factory] Owner default model lookup failed; using the mode default model', {\n orgId: args.orgId,\n ownerUserId: args.ownerUserId,\n error: error instanceof Error ? error.message : String(error),\n });\n return { modelScope: 'user' };\n }\n}\n\nexport interface SourceControlSessionLookup {\n getBySessionId(sessionId: string): Promise<SourceControlSession | null>;\n getSourceControlBySessionId(sessionId: string): Promise<SourceControlStorageHandle | null>;\n /** Side sessions started by `originSessionId`, across every provider. */\n listByOrigin(args: { orgId: string; originSessionId: string }): Promise<SourceControlSession[]>;\n rename(args: { sessionId: string; title: string }): Promise<void>;\n markFirstMessage(args: { sessionId: string }): Promise<void>;\n markFirstMeaningfulExec(args: { sessionId: string }): Promise<void>;\n}\n\n/**\n * Read or update a session across every registered source-control partition.\n * Session ids are globally generated, so more than one match is invalid and\n * fails closed instead of mutating an arbitrary provider.\n */\nexport function createSourceControlSessionLookup(\n sourceControls: readonly SourceControlStorageHandle[],\n): SourceControlSessionLookup {\n const resolve = async (\n sessionId: string,\n ): Promise<{ sourceControl: SourceControlStorageHandle; session: SourceControlSession } | null> => {\n const matches = (\n await Promise.all(\n sourceControls.map(async sourceControl => ({\n sourceControl,\n session: await sourceControl.sessions.getBySessionId(sessionId),\n })),\n )\n ).filter(\n (match): match is { sourceControl: SourceControlStorageHandle; session: SourceControlSession } =>\n match.session !== null,\n );\n if (matches.length > 1) throw new Error('Factory session exists in multiple source-control providers.');\n return matches[0] ?? null;\n };\n\n return {\n getBySessionId: async sessionId => (await resolve(sessionId))?.session ?? null,\n getSourceControlBySessionId: async sessionId => (await resolve(sessionId))?.sourceControl ?? null,\n listByOrigin: async args =>\n (await Promise.all(sourceControls.map(sourceControl => sourceControl.sessions.listByOrigin(args)))).flat(),\n rename: async args => (await resolve(args.sessionId))?.sourceControl.sessions.rename(args),\n markFirstMessage: async args => (await resolve(args.sessionId))?.sourceControl.sessions.markFirstMessage(args),\n markFirstMeaningfulExec: async args =>\n (await resolve(args.sessionId))?.sourceControl.sessions.markFirstMeaningfulExec(args),\n };\n}\n\n/**\n * Resolve the source-control provider from durable Factory repository links,\n * never from the issue tracker that happened to create the work item.\n */\nexport async function resolveFactorySourceControl(args: {\n sourceControls: readonly SourceControlStorageHandle[];\n orgId: string;\n factoryProjectId: string;\n sessionId?: string;\n}): Promise<SourceControlStorageHandle | undefined> {\n if (args.sessionId) {\n const sessionMatches = (\n await Promise.all(\n args.sourceControls.map(async sourceControl => ({\n sourceControl,\n session: await sourceControl.sessions.getBySessionId(args.sessionId!),\n })),\n )\n ).filter(match => match.session !== null);\n if (sessionMatches.length > 1) throw new Error('Factory session exists in multiple source-control providers.');\n if (sessionMatches[0]) return sessionMatches[0].sourceControl;\n }\n\n const linked = [];\n for (const sourceControl of args.sourceControls) {\n const connections = await sourceControl.connections.list({\n orgId: args.orgId,\n factoryProjectId: args.factoryProjectId,\n });\n let hasLinkedRepository = false;\n for (const connection of connections) {\n try {\n if (\n (await sourceControl.projectRepositories.list({ orgId: args.orgId, connectionId: connection.id })).length > 0\n ) {\n hasLinkedRepository = true;\n break;\n }\n } catch (error) {\n if (!(error instanceof SourceControlConnectionNotFoundError)) throw error;\n }\n }\n if (hasLinkedRepository) linked.push(sourceControl);\n }\n if (linked.length > 1)\n throw new FactorySourceControlConflictError(\n 'Factory project has repositories linked through multiple source-control providers.',\n );\n return linked[0];\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 /**\n * The repository link the session is filed under: the one a work item\n * targets. Defaults to the factory's position-1 environment link (D1), for\n * callers where nothing names a repository (chat threads, user sessions).\n */\n projectRepositoryId?: string;\n /**\n * Attribute the run to this user instead of the repo connector. Set when the\n * run has an interactive user — e.g. the person who approved a proposed run.\n */\n attributeToUserId?: string;\n /** A pre-generated session id, so a caller can name the branch after it. Defaults to a fresh UUID. */\n sessionId?: string;\n /** Defaults to 'org'. A side session copies its parent's visibility. */\n visibility?: SourceControlSessionVisibility;\n /** The session and thread that started this one as a side session. */\n origin?: { sessionId: string; threadId: string };\n}\n\nexport interface EnsuredFactorySourceSession {\n sessionId: string;\n userId: string;\n projectRepositoryId: string;\n branch: string;\n baseBranch: string;\n}\n\nexport class FactorySourceSessionResolutionError extends Error {\n constructor(readonly reason: 'connection' | 'repository') {\n super(\n reason === 'connection'\n ? 'Factory source-control connection not found.'\n : 'Factory source-control repository not found.',\n );\n this.name = 'FactorySourceSessionResolutionError';\n }\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 resolvePrimaryEnvironmentRepository}. A miss carries which\n * step failed: callers differ on whether that is an error (an autonomous run\n * cannot proceed) or a routine fallback (a chat integration drops to a\n * chat-only session), and the two steps fail for different reasons worth\n * reporting apart.\n */\nexport type FactorySourceRepositoryResult =\n | ({ found: true } & ResolvedFactorySourceRepository)\n | { found: false; reason: 'connection' | 'repository' };\n\n/**\n * The repository link a new session is filed under. By default the position-1\n * `inEnvironment` link of the factory (decision D1); a work item that targets\n * a repository names its link through `projectRepositoryId`, which must be an\n * environment link of the same factory. Session start no longer picks a\n * repository to work in, the sandbox holds every environment repository. The\n * PR tools default to this link, and subscriptions, audit and the work item's\n * repository check still read it; moving them to the per-repository\n * `sessionRepositories` rows is a follow-up.\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 resolvePrimaryEnvironmentRepository(args: {\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n projectRepositoryId?: string;\n}): Promise<FactorySourceRepositoryResult> {\n const { sourceControl, orgId, factoryProjectId, projectRepositoryId } = args;\n const connections = await sourceControl.connections.list({ orgId, factoryProjectId });\n if (!connections.some(candidate => candidate.integrationId === sourceControl.integrationId)) {\n return { found: false, reason: 'connection' };\n }\n const repositories = await resolveEnvironmentRepositories({ sourceControl, orgId, factoryProjectId });\n const primary = repositories.find(\n candidate => projectRepositoryId === undefined || candidate.link.id === projectRepositoryId,\n );\n if (!primary) return { found: false, reason: 'repository' };\n return {\n found: true,\n projectRepositoryId: primary.link.id,\n baseBranch: primary.link.branch ?? primary.repository.defaultBranch,\n connectedByUserId: primary.connection.createdByUserId,\n };\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, the\n * work item's repository link (else the position-1 environment link, D1) as\n * the session's link, and a session on the requested branch with that\n * repository's pinned or default branch as the base. The sandbox itself boots\n * every environment repository.\n *\n * The run is attributed to `attributeToUserId` when the caller has an\n * interactive user (e.g. the approver of a proposed run), and otherwise falls\n * back to whoever connected the repository (`connection.createdByUserId`),\n * because a genuinely autonomous run has no interactive user of its own.\n */\nexport async function ensureFactorySourceSession(\n args: EnsureFactorySourceSessionArgs,\n): Promise<EnsuredFactorySourceSession> {\n const { sourceControl, orgId, factoryProjectId, branch, projectRepositoryId } = args;\n\n const resolved = await resolvePrimaryEnvironmentRepository({\n sourceControl,\n orgId,\n factoryProjectId,\n projectRepositoryId,\n });\n if (!resolved.found) throw new FactorySourceSessionResolutionError(resolved.reason);\n\n const userId = args.attributeToUserId ?? resolved.connectedByUserId;\n const session = await sourceControl.sessions.create({\n sessionId: args.sessionId ?? globalThis.crypto.randomUUID(),\n projectRepositoryId: resolved.projectRepositoryId,\n factoryProjectId,\n orgId,\n userId,\n branch,\n baseBranch: resolved.baseBranch,\n visibility: args.visibility ?? 'org',\n ...(args.origin ? { origin: args.origin } : {}),\n });\n return {\n sessionId: session.sessionId,\n userId,\n projectRepositoryId: resolved.projectRepositoryId,\n branch: session.branch,\n baseBranch: resolved.baseBranch,\n };\n}\n\nexport interface HydrateFactorySessionArgs {\n orgId: string;\n modelScope: EffectiveModelScope;\n ownerUserId: string;\n /**\n * The model selected by the Factory scope. Without it the provider default of a connected\n * provider is applied when the SDK mode default has no credential, else the mode default.\n */\n defaultModelId?: string;\n}\n\n/**\n * Apply a factory project's main model to a session on every run.\n *\n * The model switch is best-effort. A retired model id must not sink a run that\n * is otherwise ready, and the reason is logged. Observational-memory settings are resolved from the\n * authoritative row per invocation before memory processors run.\n */\nexport async function hydrateFactorySession(\n session: FactorySession,\n args: HydrateFactorySessionArgs,\n): Promise<string | undefined> {\n // The org rung knowledge curation scopes on. Seeded first so it lands even if\n // a later best-effort step fails; an empty org marks the session unresolved.\n await seedSessionOrg(session, args.orgId);\n await session.state.set({\n factoryModelScope: args.modelScope,\n factoryOwnerUserId: args.ownerUserId,\n });\n\n let selectedModelId = args.defaultModelId;\n if (!selectedModelId) {\n const modeModelId = session.mode.resolve().defaultModelId;\n const providerModelId = await resolveTenantProviderDefaultModel(\n { orgId: args.orgId, userId: args.ownerUserId, credentialScope: args.modelScope },\n modeModelId,\n ).catch(error => {\n console.warn('[Factory Start] Failed to resolve a provider default model', {\n error: error instanceof Error ? error.message : String(error),\n });\n return undefined;\n });\n selectedModelId = providerModelId ?? modeModelId;\n }\n if (!selectedModelId) return undefined;\n\n let switched = true;\n try {\n await session.model.switch(selectedModelId);\n } catch (error) {\n switched = false;\n console.warn('[Factory Start] Failed to apply scoped default model', {\n modelId: selectedModelId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n // Subagents otherwise keep the server-wide settings (or the SDK's built-in\n // default), which may name a provider this factory has no credentials for.\n // Each role is independent, so one failure doesn't strand the others.\n for (const agentType of ['explore', 'plan', 'execute']) {\n try {\n await session.subagents.model.set({ modelId: selectedModelId, agentType });\n } catch (error) {\n console.warn('[Factory Start] Failed to apply scoped default subagent model', {\n agentType,\n modelId: selectedModelId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n // Callers pin this id to the thread, so only report a model the session actually runs on.\n return switched ? selectedModelId : undefined;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"factory-session.js","names":[],"sources":["../../src/session/factory-session.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\n\nimport { resolveTenantProviderDefaultModel } from '../routes/tenant-credentials.js';\nimport type { ModelDefaultsStorage } from '../storage/domains/model-defaults/base.js';\nimport { effectiveModelScope } from '../storage/domains/projects/base.js';\nimport type { EffectiveModelScope, FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport { SourceControlConnectionNotFoundError } from '../storage/domains/source-control/base.js';\nimport type {\n SourceControlSession,\n SourceControlSessionVisibility,\n SourceControlStorageHandle,\n} from '../storage/domains/source-control/base.js';\nimport { resolveEnvironmentRepositories } from './environment-repositories.js';\nimport { seedSessionOrg } from './org-seed.js';\n\ntype FactorySession = Awaited<ReturnType<AgentController<MastraCodeState>['createSession']>>;\n\nexport class FactorySourceControlConflictError extends Error {}\n\n/** Resolve the model and exclusive credential scope for a Factory session. */\nexport async function resolveFactorySessionModel(args: {\n projects: FactoryProjectsStorage | undefined;\n modelDefaults: ModelDefaultsStorage | undefined;\n factoryProjectId: string | undefined;\n orgId: string;\n ownerUserId: string;\n /** A work-item run or an interactive session; a hybrid Factory resolves a different scope for each. */\n context: 'run' | 'session';\n}): Promise<{ modelScope: EffectiveModelScope; modelId?: string }> {\n if (!args.projects || !args.factoryProjectId) throw new Error('Factory model policy is unavailable.');\n\n const project = await args.projects.getById({ id: args.factoryProjectId });\n if (!project || project.orgId !== args.orgId) throw new Error('Factory model policy is unavailable.');\n if (effectiveModelScope(project.modelScope, args.context) === 'factory') {\n return { modelScope: 'factory', ...(project.defaultModelId ? { modelId: project.defaultModelId } : {}) };\n }\n\n try {\n const userDefault = await args.modelDefaults?.get({ orgId: args.orgId, userId: args.ownerUserId });\n return { modelScope: 'user', ...(userDefault?.modelId ? { modelId: userDefault.modelId } : {}) };\n } catch (error) {\n console.warn('[Factory] Owner default model lookup failed; using the mode default model', {\n orgId: args.orgId,\n ownerUserId: args.ownerUserId,\n error: error instanceof Error ? error.message : String(error),\n });\n return { modelScope: 'user' };\n }\n}\n\nexport interface SourceControlSessionLookup {\n getBySessionId(sessionId: string): Promise<SourceControlSession | null>;\n getSourceControlBySessionId(sessionId: string): Promise<SourceControlStorageHandle | null>;\n /** Side sessions started by `originSessionId`, across every provider. */\n listByOrigin(args: { orgId: string; originSessionId: string }): Promise<SourceControlSession[]>;\n rename(args: { sessionId: string; title: string }): Promise<void>;\n setOriginMuted(args: { sessionId: string; muted: boolean }): Promise<void>;\n markFirstMessage(args: { sessionId: string }): Promise<void>;\n markFirstMeaningfulExec(args: { sessionId: string }): Promise<void>;\n}\n\n/**\n * Read or update a session across every registered source-control partition.\n * Session ids are globally generated, so more than one match is invalid and\n * fails closed instead of mutating an arbitrary provider.\n */\nexport function createSourceControlSessionLookup(\n sourceControls: readonly SourceControlStorageHandle[],\n): SourceControlSessionLookup {\n const resolve = async (\n sessionId: string,\n ): Promise<{ sourceControl: SourceControlStorageHandle; session: SourceControlSession } | null> => {\n const matches = (\n await Promise.all(\n sourceControls.map(async sourceControl => ({\n sourceControl,\n session: await sourceControl.sessions.getBySessionId(sessionId),\n })),\n )\n ).filter(\n (match): match is { sourceControl: SourceControlStorageHandle; session: SourceControlSession } =>\n match.session !== null,\n );\n if (matches.length > 1) throw new Error('Factory session exists in multiple source-control providers.');\n return matches[0] ?? null;\n };\n\n return {\n getBySessionId: async sessionId => (await resolve(sessionId))?.session ?? null,\n getSourceControlBySessionId: async sessionId => (await resolve(sessionId))?.sourceControl ?? null,\n listByOrigin: async args =>\n (await Promise.all(sourceControls.map(sourceControl => sourceControl.sessions.listByOrigin(args)))).flat(),\n rename: async args => (await resolve(args.sessionId))?.sourceControl.sessions.rename(args),\n setOriginMuted: async args => (await resolve(args.sessionId))?.sourceControl.sessions.setOriginMuted(args),\n markFirstMessage: async args => (await resolve(args.sessionId))?.sourceControl.sessions.markFirstMessage(args),\n markFirstMeaningfulExec: async args =>\n (await resolve(args.sessionId))?.sourceControl.sessions.markFirstMeaningfulExec(args),\n };\n}\n\n/**\n * Resolve the source-control provider from durable Factory repository links,\n * never from the issue tracker that happened to create the work item.\n */\nexport async function resolveFactorySourceControl(args: {\n sourceControls: readonly SourceControlStorageHandle[];\n orgId: string;\n factoryProjectId: string;\n sessionId?: string;\n}): Promise<SourceControlStorageHandle | undefined> {\n if (args.sessionId) {\n const sessionMatches = (\n await Promise.all(\n args.sourceControls.map(async sourceControl => ({\n sourceControl,\n session: await sourceControl.sessions.getBySessionId(args.sessionId!),\n })),\n )\n ).filter(match => match.session !== null);\n if (sessionMatches.length > 1) throw new Error('Factory session exists in multiple source-control providers.');\n if (sessionMatches[0]) return sessionMatches[0].sourceControl;\n }\n\n const linked = [];\n for (const sourceControl of args.sourceControls) {\n const connections = await sourceControl.connections.list({\n orgId: args.orgId,\n factoryProjectId: args.factoryProjectId,\n });\n let hasLinkedRepository = false;\n for (const connection of connections) {\n try {\n if (\n (await sourceControl.projectRepositories.list({ orgId: args.orgId, connectionId: connection.id })).length > 0\n ) {\n hasLinkedRepository = true;\n break;\n }\n } catch (error) {\n if (!(error instanceof SourceControlConnectionNotFoundError)) throw error;\n }\n }\n if (hasLinkedRepository) linked.push(sourceControl);\n }\n if (linked.length > 1)\n throw new FactorySourceControlConflictError(\n 'Factory project has repositories linked through multiple source-control providers.',\n );\n return linked[0];\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 /**\n * The repository link the session is filed under: the one a work item\n * targets. Defaults to the factory's position-1 environment link (D1), for\n * callers where nothing names a repository (chat threads, user sessions).\n */\n projectRepositoryId?: string;\n /**\n * Attribute the run to this user instead of the repo connector. Set when the\n * run has an interactive user — e.g. the person who approved a proposed run.\n */\n attributeToUserId?: string;\n /** A pre-generated session id, so a caller can name the branch after it. Defaults to a fresh UUID. */\n sessionId?: string;\n /** Defaults to 'org'. A side session copies its parent's visibility. */\n visibility?: SourceControlSessionVisibility;\n /** The session and thread that started this one as a side session. */\n origin?: { sessionId: string; threadId: string };\n}\n\nexport interface EnsuredFactorySourceSession {\n sessionId: string;\n userId: string;\n projectRepositoryId: string;\n branch: string;\n baseBranch: string;\n}\n\nexport class FactorySourceSessionResolutionError extends Error {\n constructor(readonly reason: 'connection' | 'repository') {\n super(\n reason === 'connection'\n ? 'Factory source-control connection not found.'\n : 'Factory source-control repository not found.',\n );\n this.name = 'FactorySourceSessionResolutionError';\n }\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 resolvePrimaryEnvironmentRepository}. A miss carries which\n * step failed: callers differ on whether that is an error (an autonomous run\n * cannot proceed) or a routine fallback (a chat integration drops to a\n * chat-only session), and the two steps fail for different reasons worth\n * reporting apart.\n */\nexport type FactorySourceRepositoryResult =\n | ({ found: true } & ResolvedFactorySourceRepository)\n | { found: false; reason: 'connection' | 'repository' };\n\n/**\n * The repository link a new session is filed under. By default the position-1\n * `inEnvironment` link of the factory (decision D1); a work item that targets\n * a repository names its link through `projectRepositoryId`, which must be an\n * environment link of the same factory. Session start no longer picks a\n * repository to work in, the sandbox holds every environment repository. The\n * PR tools default to this link, and subscriptions, audit and the work item's\n * repository check still read it; moving them to the per-repository\n * `sessionRepositories` rows is a follow-up.\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 resolvePrimaryEnvironmentRepository(args: {\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n projectRepositoryId?: string;\n}): Promise<FactorySourceRepositoryResult> {\n const { sourceControl, orgId, factoryProjectId, projectRepositoryId } = args;\n const connections = await sourceControl.connections.list({ orgId, factoryProjectId });\n if (!connections.some(candidate => candidate.integrationId === sourceControl.integrationId)) {\n return { found: false, reason: 'connection' };\n }\n const repositories = await resolveEnvironmentRepositories({ sourceControl, orgId, factoryProjectId });\n const primary = repositories.find(\n candidate => projectRepositoryId === undefined || candidate.link.id === projectRepositoryId,\n );\n if (!primary) return { found: false, reason: 'repository' };\n return {\n found: true,\n projectRepositoryId: primary.link.id,\n baseBranch: primary.link.branch ?? primary.repository.defaultBranch,\n connectedByUserId: primary.connection.createdByUserId,\n };\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, the\n * work item's repository link (else the position-1 environment link, D1) as\n * the session's link, and a session on the requested branch with that\n * repository's pinned or default branch as the base. The sandbox itself boots\n * every environment repository.\n *\n * The run is attributed to `attributeToUserId` when the caller has an\n * interactive user (e.g. the approver of a proposed run), and otherwise falls\n * back to whoever connected the repository (`connection.createdByUserId`),\n * because a genuinely autonomous run has no interactive user of its own.\n */\nexport async function ensureFactorySourceSession(\n args: EnsureFactorySourceSessionArgs,\n): Promise<EnsuredFactorySourceSession> {\n const { sourceControl, orgId, factoryProjectId, branch, projectRepositoryId } = args;\n\n const resolved = await resolvePrimaryEnvironmentRepository({\n sourceControl,\n orgId,\n factoryProjectId,\n projectRepositoryId,\n });\n if (!resolved.found) throw new FactorySourceSessionResolutionError(resolved.reason);\n\n const userId = args.attributeToUserId ?? resolved.connectedByUserId;\n const session = await sourceControl.sessions.create({\n sessionId: args.sessionId ?? globalThis.crypto.randomUUID(),\n projectRepositoryId: resolved.projectRepositoryId,\n factoryProjectId,\n orgId,\n userId,\n branch,\n baseBranch: resolved.baseBranch,\n visibility: args.visibility ?? 'org',\n ...(args.origin ? { origin: args.origin } : {}),\n });\n return {\n sessionId: session.sessionId,\n userId,\n projectRepositoryId: resolved.projectRepositoryId,\n branch: session.branch,\n baseBranch: resolved.baseBranch,\n };\n}\n\nexport interface HydrateFactorySessionArgs {\n orgId: string;\n modelScope: EffectiveModelScope;\n ownerUserId: string;\n /**\n * The model selected by the Factory scope. Without it the provider default of a connected\n * provider is applied when the SDK mode default has no credential, else the mode default.\n */\n defaultModelId?: string;\n}\n\n/**\n * Apply a factory project's main model to a session on every run.\n *\n * The model switch is best-effort. A retired model id must not sink a run that\n * is otherwise ready, and the reason is logged. Observational-memory settings are resolved from the\n * authoritative row per invocation before memory processors run.\n */\nexport async function hydrateFactorySession(\n session: FactorySession,\n args: HydrateFactorySessionArgs,\n): Promise<string | undefined> {\n // The org rung knowledge curation scopes on. Seeded first so it lands even if\n // a later best-effort step fails; an empty org marks the session unresolved.\n await seedSessionOrg(session, args.orgId);\n await session.state.set({\n factoryModelScope: args.modelScope,\n factoryOwnerUserId: args.ownerUserId,\n });\n\n let selectedModelId = args.defaultModelId;\n if (!selectedModelId) {\n const modeModelId = session.mode.resolve().defaultModelId;\n const providerModelId = await resolveTenantProviderDefaultModel(\n { orgId: args.orgId, userId: args.ownerUserId, credentialScope: args.modelScope },\n modeModelId,\n ).catch(error => {\n console.warn('[Factory Start] Failed to resolve a provider default model', {\n error: error instanceof Error ? error.message : String(error),\n });\n return undefined;\n });\n selectedModelId = providerModelId ?? modeModelId;\n }\n if (!selectedModelId) return undefined;\n\n let switched = true;\n try {\n await session.model.switch(selectedModelId);\n } catch (error) {\n switched = false;\n console.warn('[Factory Start] Failed to apply scoped default model', {\n modelId: selectedModelId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n // Subagents otherwise keep the server-wide settings (or the SDK's built-in\n // default), which may name a provider this factory has no credentials for.\n // Each role is independent, so one failure doesn't strand the others.\n for (const agentType of ['explore', 'plan', 'execute']) {\n try {\n await session.subagents.model.set({ modelId: selectedModelId, agentType });\n } catch (error) {\n console.warn('[Factory Start] Failed to apply scoped default subagent model', {\n agentType,\n modelId: selectedModelId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n // Callers pin this id to the thread, so only report a model the session actually runs on.\n return switched ? selectedModelId : undefined;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reply-credential-processor.d.ts","sourceRoot":"","sources":["../../src/session/reply-credential-processor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"reply-credential-processor.d.ts","sourceRoot":"","sources":["../../src/session/reply-credential-processor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAIhF;;;;;;;GAOG;AACH,qBAAa,+BAAgC,YAAW,SAAS;IAC/D,QAAQ,CAAC,EAAE,8BAA8B;IACzC,QAAQ,CAAC,IAAI,8BAA8B;IAE3C,iBAAiB,CAAC,EAAE,WAAW,EAAE,cAAc,EAAE,EAAE,qBAAqB;CAkBzE"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { providerDisplayName } from "../provider-display-name.js";
|
|
1
|
+
import { credentialLabel, getAuthProviderId } from "../routes/provider-credentials.js";
|
|
3
2
|
import { resolveCredentialStore, resolveTenantFromRequestContext } from "@mastra/code-sdk/agents/credential-resolver";
|
|
4
3
|
import { normalizeLegacyProviderId } from "@mastra/code-sdk/providers/model-ids";
|
|
5
4
|
//#region src/session/reply-credential-processor.ts
|
|
@@ -25,10 +24,9 @@ var FactoryReplyCredentialProcessor = class {
|
|
|
25
24
|
const [sdkProviderRoot = metadata.provider] = metadata.provider.split(".");
|
|
26
25
|
const provider = normalizeLegacyProviderId(sdkProviderRoot);
|
|
27
26
|
const credential = store.get(getAuthProviderId(provider));
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
metadata.credentialLabel = credentialLabel;
|
|
27
|
+
const label = credential && credentialLabel(provider, credential.type);
|
|
28
|
+
if (!label) continue;
|
|
29
|
+
metadata.credentialLabel = label;
|
|
32
30
|
if (tenant.credentialScope === "user") metadata.credentialOwnerId = tenant.userId;
|
|
33
31
|
}
|
|
34
32
|
return messageList;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reply-credential-processor.js","names":[],"sources":["../../src/session/reply-credential-processor.ts"],"sourcesContent":["import { resolveCredentialStore, resolveTenantFromRequestContext } from '@mastra/code-sdk/agents/credential-resolver';\nimport { normalizeLegacyProviderId } from '@mastra/code-sdk/providers/model-ids';\nimport type { Processor, ProcessOutputStepArgs } from '@mastra/core/processors';\n\nimport {
|
|
1
|
+
{"version":3,"file":"reply-credential-processor.js","names":[],"sources":["../../src/session/reply-credential-processor.ts"],"sourcesContent":["import { resolveCredentialStore, resolveTenantFromRequestContext } from '@mastra/code-sdk/agents/credential-resolver';\nimport { normalizeLegacyProviderId } from '@mastra/code-sdk/providers/model-ids';\nimport type { Processor, ProcessOutputStepArgs } from '@mastra/core/processors';\n\nimport { credentialLabel, getAuthProviderId } from '../routes/provider-credentials.js';\n\n/**\n * Stamps each assistant reply with the credential that answered it, as\n * `content.metadata.credentialLabel` (\"Claude Pro/Max\", \"Anthropic API key\")\n * and, for a personal credential, `content.metadata.credentialOwnerId`. A\n * reply without an owner id was answered by the organization's credential.\n * Stamped at send time so a reply keeps its label after the thread's\n * credential scope changes.\n */\nexport class FactoryReplyCredentialProcessor implements Processor {\n readonly id = 'factory-reply-credential';\n readonly name = 'Factory reply credential';\n\n processOutputStep({ messageList, requestContext }: ProcessOutputStepArgs) {\n const tenant = resolveTenantFromRequestContext(requestContext);\n const store = resolveCredentialStore(requestContext);\n if (!tenant || !store) return messageList;\n for (const message of messageList.get.response.db()) {\n const metadata = message.content.metadata;\n if (message.role !== 'assistant' || !metadata || typeof metadata.provider !== 'string') continue;\n if (metadata.credentialLabel !== undefined) continue;\n const [sdkProviderRoot = metadata.provider] = metadata.provider.split('.');\n const provider = normalizeLegacyProviderId(sdkProviderRoot);\n const credential = store.get(getAuthProviderId(provider));\n const label = credential && credentialLabel(provider, credential.type);\n if (!label) continue;\n metadata.credentialLabel = label;\n if (tenant.credentialScope === 'user') metadata.credentialOwnerId = tenant.userId;\n }\n return messageList;\n }\n}\n"],"mappings":";;;;;;;;;;;;AAcA,IAAa,kCAAb,MAAkE;CAChE,KAAc;CACd,OAAgB;CAEhB,kBAAkB,EAAE,aAAa,kBAAyC;EACxE,MAAM,SAAS,gCAAgC,cAAc;EAC7D,MAAM,QAAQ,uBAAuB,cAAc;EACnD,IAAI,CAAC,UAAU,CAAC,OAAO,OAAO;EAC9B,KAAK,MAAM,WAAW,YAAY,IAAI,SAAS,GAAG,GAAG;GACnD,MAAM,WAAW,QAAQ,QAAQ;GACjC,IAAI,QAAQ,SAAS,eAAe,CAAC,YAAY,OAAO,SAAS,aAAa,UAAU;GACxF,IAAI,SAAS,oBAAoB,KAAA,GAAW;GAC5C,MAAM,CAAC,kBAAkB,SAAS,YAAY,SAAS,SAAS,MAAM,GAAG;GACzE,MAAM,WAAW,0BAA0B,eAAe;GAC1D,MAAM,aAAa,MAAM,IAAI,kBAAkB,QAAQ,CAAC;GACxD,MAAM,QAAQ,cAAc,gBAAgB,UAAU,WAAW,IAAI;GACrE,IAAI,CAAC,OAAO;GACZ,SAAS,kBAAkB;GAC3B,IAAI,OAAO,oBAAoB,QAAQ,SAAS,oBAAoB,OAAO;EAC7E;EACA,OAAO;CACT;AACF"}
|
|
@@ -37,6 +37,30 @@ export interface RunEndCaptureSession {
|
|
|
37
37
|
type RunEndReason = Extract<AgentControllerEvent, {
|
|
38
38
|
type: 'agent_end';
|
|
39
39
|
}>['reason'];
|
|
40
|
+
/**
|
|
41
|
+
* JSON budget for the per-step entries on a run's audit record. Audit metadata
|
|
42
|
+
* is capped at 4 KB as a whole and an oversized record loses all of it, so
|
|
43
|
+
* later steps are dropped (and counted) instead; totals always cover every step.
|
|
44
|
+
*/
|
|
45
|
+
export declare const FACTORY_RUN_USAGE_STEPS_JSON_BUDGET = 2400;
|
|
46
|
+
declare const USAGE_FIELDS: readonly ["promptTokens", "completionTokens", "totalTokens", "reasoningTokens", "cachedInputTokens", "cacheCreationInputTokens", "cacheCreationInputTokens5m", "cacheCreationInputTokens1h"];
|
|
47
|
+
type UsageCounts = Partial<Record<(typeof USAGE_FIELDS)[number], number>>;
|
|
48
|
+
/**
|
|
49
|
+
* Token usage attached to a run's `factory.run.ended` audit record.
|
|
50
|
+
* `unreported` means this process saw no usage for the run (it ran elsewhere,
|
|
51
|
+
* the process restarted, or the model reported none). It is never zero spend.
|
|
52
|
+
*/
|
|
53
|
+
export type FactoryRunUsage = {
|
|
54
|
+
status: 'recorded';
|
|
55
|
+
stepCount: number;
|
|
56
|
+
totals: UsageCounts;
|
|
57
|
+
steps: UsageCounts[];
|
|
58
|
+
stepsTruncated?: number;
|
|
59
|
+
/** Other runs that ended in the same agent turn; the turn's usage can't be split between them. */
|
|
60
|
+
sharedWithKickoffIds?: string[];
|
|
61
|
+
} | {
|
|
62
|
+
status: 'unreported';
|
|
63
|
+
};
|
|
40
64
|
export declare function listSessionOpenRuns(session: RunEndCaptureSession): Promise<FactoryOpenRun[]>;
|
|
41
65
|
export declare function waitForSessionRunAudit(session: RunEndCaptureSession): Promise<void>;
|
|
42
66
|
export declare function recordSessionRunStart(session: RunEndCaptureSession, { audit, run, actorType, observedEnd, }: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-audit.d.ts","sourceRoot":"","sources":["../../src/session/run-audit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAC1E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAExE,eAAO,MAAM,yBAAyB,oBAAoB,CAAC;AAE3D,QAAA,MAAM,oBAAoB;;;;;;;;;;;;;;iBAqBxB,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,6GAA6G;AAC7G,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAEhD,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,OAAO,CAOtG;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE;QACf,UAAU,CAAC,IAAI,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QACpD,UAAU,CAAC,IAAI,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,OAAO,CAAA;SAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAClE,CAAC;IACF,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACxE;AAED,KAAK,YAAY,GAAG,OAAO,CAAC,oBAAoB,EAAE;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"run-audit.d.ts","sourceRoot":"","sources":["../../src/session/run-audit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAC1E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAExE,eAAO,MAAM,yBAAyB,oBAAoB,CAAC;AAE3D,QAAA,MAAM,oBAAoB;;;;;;;;;;;;;;iBAqBxB,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,6GAA6G;AAC7G,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAEhD,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,OAAO,CAOtG;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE;QACf,UAAU,CAAC,IAAI,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QACpD,UAAU,CAAC,IAAI,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,OAAO,CAAA;SAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAClE,CAAC;IACF,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACxE;AAED,KAAK,YAAY,GAAG,OAAO,CAAC,oBAAoB,EAAE;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;AAInF;;;;GAIG;AACH,eAAO,MAAM,mCAAmC,OAAO,CAAC;AAExD,QAAA,MAAM,YAAY,8LASR,CAAC;AAEX,KAAK,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AAE1E;;;;GAIG;AACH,MAAM,MAAM,eAAe,GACvB;IACE,MAAM,EAAE,UAAU,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kGAAkG;IAClG,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC,GACD;IAAE,MAAM,EAAE,YAAY,CAAA;CAAE,CAAC;AAoE7B,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAGlG;AAED,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAEzF;AAsED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,oBAAoB,EAC7B,EACE,KAAK,EACL,GAAG,EACH,SAAS,EACT,WAAW,GACZ,EAAE;IACD,KAAK,EAAE,aAAa,CAAC;IACrB,GAAG,EAAE,cAAc,CAAC;IACpB,SAAS,EAAE,cAAc,CAAC;IAC1B,WAAW,EAAE,MAAM,YAAY,CAAC;CACjC,GACA,OAAO,CAAC,IAAI,CAAC,CAcf;AAED,4FAA4F;AAC5F,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,oBAAoB,EAC7B,SAAS,EAAE,MAAM,EACjB,EAAE,SAAa,GACd,OAAO,CAAC,IAAI,CAAC,CASf;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,oBAAoB,EAAE,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,aAAa,CAAA;CAAE,GAAG,MAAM,IAAI,CAcnH"}
|
|
@@ -29,6 +29,61 @@ function isOpenRunLiveElsewhere(run, ownerId, now = Date.now()) {
|
|
|
29
29
|
return run.ownerId !== void 0 && run.ownerId !== ownerId && run.heartbeatAt !== void 0 && now - run.heartbeatAt < 3e4;
|
|
30
30
|
}
|
|
31
31
|
const pendingWrites = /* @__PURE__ */ new WeakMap();
|
|
32
|
+
/**
|
|
33
|
+
* JSON budget for the per-step entries on a run's audit record. Audit metadata
|
|
34
|
+
* is capped at 4 KB as a whole and an oversized record loses all of it, so
|
|
35
|
+
* later steps are dropped (and counted) instead; totals always cover every step.
|
|
36
|
+
*/
|
|
37
|
+
const FACTORY_RUN_USAGE_STEPS_JSON_BUDGET = 2400;
|
|
38
|
+
const USAGE_FIELDS = [
|
|
39
|
+
"promptTokens",
|
|
40
|
+
"completionTokens",
|
|
41
|
+
"totalTokens",
|
|
42
|
+
"reasoningTokens",
|
|
43
|
+
"cachedInputTokens",
|
|
44
|
+
"cacheCreationInputTokens",
|
|
45
|
+
"cacheCreationInputTokens5m",
|
|
46
|
+
"cacheCreationInputTokens1h"
|
|
47
|
+
];
|
|
48
|
+
function usageCounts(usage) {
|
|
49
|
+
const counts = {};
|
|
50
|
+
for (const field of USAGE_FIELDS) {
|
|
51
|
+
const value = usage[field];
|
|
52
|
+
if (typeof value === "number") counts[field] = value;
|
|
53
|
+
}
|
|
54
|
+
return counts;
|
|
55
|
+
}
|
|
56
|
+
function runUsage(steps) {
|
|
57
|
+
if (steps.length === 0) return { status: "unreported" };
|
|
58
|
+
const totals = {};
|
|
59
|
+
for (const step of steps) for (const field of USAGE_FIELDS) if (step[field] !== void 0) totals[field] = (totals[field] ?? 0) + step[field];
|
|
60
|
+
const kept = [];
|
|
61
|
+
let length = 2;
|
|
62
|
+
for (const step of steps) {
|
|
63
|
+
length += JSON.stringify(step).length + 1;
|
|
64
|
+
if (length > 2400) break;
|
|
65
|
+
kept.push(step);
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
status: "recorded",
|
|
69
|
+
stepCount: steps.length,
|
|
70
|
+
totals,
|
|
71
|
+
steps: kept,
|
|
72
|
+
...steps.length > kept.length ? { stepsTruncated: steps.length - kept.length } : {}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const sessionUsage = /* @__PURE__ */ new WeakMap();
|
|
76
|
+
function usageFor(session) {
|
|
77
|
+
let usage = sessionUsage.get(session);
|
|
78
|
+
if (!usage) {
|
|
79
|
+
usage = {
|
|
80
|
+
current: [],
|
|
81
|
+
held: /* @__PURE__ */ new Map()
|
|
82
|
+
};
|
|
83
|
+
sessionUsage.set(session, usage);
|
|
84
|
+
}
|
|
85
|
+
return usage;
|
|
86
|
+
}
|
|
32
87
|
function serializeRunAudit(session, write) {
|
|
33
88
|
const pending = (pendingWrites.get(session) ?? Promise.resolve()).then(write).catch((error) => console.warn("[Factory run audit] Unable to record run lifecycle.", error));
|
|
34
89
|
pendingWrites.set(session, pending);
|
|
@@ -55,27 +110,47 @@ function runAuditInput(run) {
|
|
|
55
110
|
metadata
|
|
56
111
|
};
|
|
57
112
|
}
|
|
58
|
-
async function recordRunEnds(session, audit, reason, kickoffId) {
|
|
113
|
+
async function recordRunEnds(session, audit, reason, kickoffId, endedTurn) {
|
|
59
114
|
if (!reason || reason === "suspended") return;
|
|
115
|
+
const usage = usageFor(session);
|
|
60
116
|
const runs = await listSessionOpenRuns(session);
|
|
61
117
|
if (runs.length === 0) return;
|
|
118
|
+
const ending = runs.filter((run) => !kickoffId || run.kickoffId === kickoffId);
|
|
119
|
+
const turn = endedTurn ?? usage.lastTurn;
|
|
120
|
+
const steps = turn && !turn.claimed ? turn.steps : [];
|
|
121
|
+
if (turn && ending.length > 0) turn.claimed = true;
|
|
122
|
+
const turnUsage = runUsage(steps);
|
|
62
123
|
const remaining = [];
|
|
63
124
|
for (const run of runs) {
|
|
64
|
-
if (
|
|
125
|
+
if (!ending.includes(run)) {
|
|
65
126
|
remaining.push(run);
|
|
66
127
|
continue;
|
|
67
128
|
}
|
|
129
|
+
let runUsageRecord = usage.held.get(run.kickoffId);
|
|
130
|
+
if (!runUsageRecord) {
|
|
131
|
+
const shared = ending.filter((other) => other !== run).map((other) => other.kickoffId);
|
|
132
|
+
runUsageRecord = turnUsage.status === "recorded" && shared.length > 0 ? {
|
|
133
|
+
...turnUsage,
|
|
134
|
+
sharedWithKickoffIds: shared
|
|
135
|
+
} : turnUsage;
|
|
136
|
+
usage.held.set(run.kickoffId, runUsageRecord);
|
|
137
|
+
}
|
|
68
138
|
const input = runAuditInput(run);
|
|
69
|
-
if (
|
|
139
|
+
if (await audit.record({
|
|
70
140
|
...input,
|
|
71
141
|
actorId: `agent:${run.threadId}`,
|
|
72
142
|
actorType: "agent",
|
|
73
143
|
action: "factory.run.ended",
|
|
74
144
|
metadata: {
|
|
75
145
|
...input.metadata,
|
|
76
|
-
reason
|
|
146
|
+
reason,
|
|
147
|
+
usage: runUsageRecord
|
|
77
148
|
}
|
|
78
|
-
}))
|
|
149
|
+
})) usage.held.delete(run.kickoffId);
|
|
150
|
+
else {
|
|
151
|
+
console.warn(`[Factory run audit] Token usage for run ${run.kickoffId} (${run.role}) was not persisted; keeping it for the next attempt.`, runUsageRecord);
|
|
152
|
+
remaining.push(run);
|
|
153
|
+
}
|
|
79
154
|
}
|
|
80
155
|
await session.thread.setSetting({
|
|
81
156
|
key: FACTORY_OPEN_RUNS_SETTING,
|
|
@@ -113,12 +188,23 @@ function heartbeatSessionOpenRun(session, kickoffId, at = Date.now()) {
|
|
|
113
188
|
});
|
|
114
189
|
}
|
|
115
190
|
function observeSessionRunEnd(session, { audit }) {
|
|
191
|
+
const usage = usageFor(session);
|
|
116
192
|
return session.subscribe((event) => {
|
|
193
|
+
if (event.type === "usage_update") {
|
|
194
|
+
usage.current.push(usageCounts(event.usage));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
117
197
|
if (event.type !== "agent_end" || !event.reason || event.reason === "suspended") return;
|
|
118
|
-
|
|
198
|
+
const turn = {
|
|
199
|
+
steps: usage.current,
|
|
200
|
+
claimed: false
|
|
201
|
+
};
|
|
202
|
+
usage.current = [];
|
|
203
|
+
usage.lastTurn = turn;
|
|
204
|
+
serializeRunAudit(session, () => recordRunEnds(session, audit, event.reason, void 0, turn));
|
|
119
205
|
});
|
|
120
206
|
}
|
|
121
207
|
//#endregion
|
|
122
|
-
export { FACTORY_OPEN_RUNS_SETTING, FACTORY_OPEN_RUN_STALE_MS, heartbeatSessionOpenRun, isOpenRunLiveElsewhere, listSessionOpenRuns, observeSessionRunEnd, recordSessionRunStart, waitForSessionRunAudit };
|
|
208
|
+
export { FACTORY_OPEN_RUNS_SETTING, FACTORY_OPEN_RUN_STALE_MS, FACTORY_RUN_USAGE_STEPS_JSON_BUDGET, heartbeatSessionOpenRun, isOpenRunLiveElsewhere, listSessionOpenRuns, observeSessionRunEnd, recordSessionRunStart, waitForSessionRunAudit };
|
|
123
209
|
|
|
124
210
|
//# sourceMappingURL=run-audit.js.map
|