@mastra/factory 0.5.1-alpha.0 → 0.6.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +151 -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 +3 -0
- package/dist/capabilities/version-control.d.ts.map +1 -1
- package/dist/factory.d.ts +10 -0
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +15 -2
- 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 +14 -1
- package/dist/integrations/github/integration.d.ts.map +1 -1
- package/dist/integrations/github/integration.js +61 -3
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/issue-reconciler.d.ts +24 -0
- package/dist/integrations/github/issue-reconciler.d.ts.map +1 -0
- package/dist/integrations/github/issue-reconciler.js +104 -0
- package/dist/integrations/github/issue-reconciler.js.map +1 -0
- package/dist/integrations/github/reconcile-worker.d.ts +8 -0
- package/dist/integrations/github/reconcile-worker.d.ts.map +1 -1
- package/dist/integrations/github/reconcile-worker.js +38 -1
- package/dist/integrations/github/reconcile-worker.js.map +1 -1
- package/dist/integrations/github/routes.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +65 -52
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/rules.d.ts +37 -2
- package/dist/integrations/github/rules.d.ts.map +1 -1
- package/dist/integrations/github/rules.js +150 -14
- 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/github/sandbox.d.ts.map +1 -1
- package/dist/integrations/github/sandbox.js +74 -8
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/integrations/issue-reconcile-worker.d.ts +19 -0
- package/dist/integrations/issue-reconcile-worker.d.ts.map +1 -0
- package/dist/integrations/issue-reconcile-worker.js +98 -0
- package/dist/integrations/issue-reconcile-worker.js.map +1 -0
- package/dist/integrations/issue-reconciler.d.ts +47 -0
- package/dist/integrations/issue-reconciler.d.ts.map +1 -0
- package/dist/integrations/issue-reconciler.js +104 -0
- package/dist/integrations/issue-reconciler.js.map +1 -0
- package/dist/integrations/linear/integration.d.ts +4 -0
- package/dist/integrations/linear/integration.d.ts.map +1 -1
- package/dist/integrations/linear/integration.js +19 -2
- package/dist/integrations/linear/integration.js.map +1 -1
- package/dist/integrations/linear/issue-reconciler.d.ts +6 -0
- package/dist/integrations/linear/issue-reconciler.d.ts.map +1 -0
- package/dist/integrations/linear/issue-reconciler.js +64 -0
- package/dist/integrations/linear/issue-reconciler.js.map +1 -0
- 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 +5 -2
- package/dist/integrations/linear/rules.js.map +1 -1
- package/dist/integrations/platform/github/event-worker.d.ts +7 -0
- package/dist/integrations/platform/github/event-worker.d.ts.map +1 -1
- package/dist/integrations/platform/github/event-worker.js +24 -0
- package/dist/integrations/platform/github/event-worker.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts +13 -3
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +42 -1
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/integrations/platform/linear/event-worker.d.ts +105 -0
- package/dist/integrations/platform/linear/event-worker.d.ts.map +1 -0
- package/dist/integrations/platform/linear/event-worker.js +380 -0
- package/dist/integrations/platform/linear/event-worker.js.map +1 -0
- package/dist/integrations/platform/linear/integration.d.ts +9 -0
- package/dist/integrations/platform/linear/integration.d.ts.map +1 -1
- package/dist/integrations/platform/linear/integration.js +37 -1
- package/dist/integrations/platform/linear/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/config.d.ts +25 -0
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +63 -1
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/fs.d.ts +16 -2
- package/dist/routes/fs.d.ts.map +1 -1
- package/dist/routes/fs.js +47 -5
- package/dist/routes/fs.js.map +1 -1
- package/dist/routes/surface.d.ts +13 -1
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +20 -9
- package/dist/routes/surface.js.map +1 -1
- package/dist/rules/binding-context.d.ts +28 -1
- package/dist/rules/binding-context.d.ts.map +1 -1
- package/dist/rules/binding-context.js +66 -1
- package/dist/rules/binding-context.js.map +1 -1
- package/dist/rules/defaults.d.ts.map +1 -1
- package/dist/rules/defaults.js +71 -5
- 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/tools.d.ts +2 -0
- package/dist/rules/tools.d.ts.map +1 -1
- package/dist/rules/tools.js +15 -6
- package/dist/rules/tools.js.map +1 -1
- package/dist/rules/types.d.ts +17 -2
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/types.js +2 -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/session/filesystem-capture.d.ts +26 -0
- package/dist/session/filesystem-capture.d.ts.map +1 -0
- package/dist/session/filesystem-capture.js +75 -0
- package/dist/session/filesystem-capture.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/filesystem/base.d.ts +26 -0
- package/dist/storage/domains/filesystem/base.d.ts.map +1 -0
- package/dist/storage/domains/filesystem/base.js +74 -0
- package/dist/storage/domains/filesystem/base.js.map +1 -0
- package/dist/storage/domains/projects/base.d.ts +1 -0
- package/dist/storage/domains/projects/base.d.ts.map +1 -1
- package/dist/storage/domains/projects/base.js +3 -0
- package/dist/storage/domains/projects/base.js.map +1 -1
- package/dist/storage/domains/source-control/base.d.ts +2 -0
- package/dist/storage/domains/source-control/base.d.ts.map +1 -1
- package/dist/storage/domains/source-control/base.js +6 -0
- package/dist/storage/domains/source-control/base.js.map +1 -1
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/source-control/inmemory.js +3 -0
- package/dist/storage/domains/source-control/inmemory.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +11 -0
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +28 -0
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/factory-skills/factory-triage/SKILL.md +45 -12
- package/package.json +5 -5
- 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
|
@@ -22,7 +22,72 @@ function getFactorySessionAddress(requestContext) {
|
|
|
22
22
|
...coordinates
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolve the bound session address, falling back to the active binding row
|
|
27
|
+
* when the in-memory session state is missing `factoryProjectId` — a server
|
|
28
|
+
* crash drops controller sessions, and a resumed session ("Continue") is
|
|
29
|
+
* recreated with empty state, which silently unregistered the transition
|
|
30
|
+
* tool. On recovery the lost state is healed back onto the session (including
|
|
31
|
+
* the `untrustedCheckout`/`baseRef` security posture the start coordinator
|
|
32
|
+
* originally seeded) so later reads resolve directly.
|
|
33
|
+
*/
|
|
34
|
+
async function resolveFactorySessionAddress(options) {
|
|
35
|
+
const direct = getFactorySessionAddress(options.requestContext);
|
|
36
|
+
if (direct) return { address: direct };
|
|
37
|
+
const requestContext = options.requestContext;
|
|
38
|
+
if (!requestContext || typeof requestContext.get !== "function") return null;
|
|
39
|
+
const context = requestContext.get("controller");
|
|
40
|
+
if (!context?.threadId || !context.resourceId) return null;
|
|
41
|
+
const orgId = getFactoryAuthOrgId(requestContext.get("user"));
|
|
42
|
+
if (!orgId) return null;
|
|
43
|
+
const binding = await options.storage.findActiveRunBindingByThread({
|
|
44
|
+
orgId,
|
|
45
|
+
threadId: context.threadId,
|
|
46
|
+
resourceId: context.resourceId,
|
|
47
|
+
sessionId: context.resourceId
|
|
48
|
+
});
|
|
49
|
+
if (!binding) return null;
|
|
50
|
+
await healRecoveredSessionState({
|
|
51
|
+
context,
|
|
52
|
+
binding,
|
|
53
|
+
storage: options.storage,
|
|
54
|
+
sessions: options.sessions
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
address: {
|
|
58
|
+
orgId,
|
|
59
|
+
factoryProjectId: binding.factoryProjectId,
|
|
60
|
+
threadId: context.threadId,
|
|
61
|
+
resourceId: context.resourceId,
|
|
62
|
+
sessionId: context.resourceId
|
|
63
|
+
},
|
|
64
|
+
binding
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async function healRecoveredSessionState(options) {
|
|
68
|
+
const { binding } = options;
|
|
69
|
+
const updates = { factoryProjectId: binding.factoryProjectId };
|
|
70
|
+
if (binding.role === "review") updates.untrustedCheckout = true;
|
|
71
|
+
try {
|
|
72
|
+
const item = await options.storage.get({
|
|
73
|
+
orgId: binding.orgId,
|
|
74
|
+
id: binding.workItemId
|
|
75
|
+
});
|
|
76
|
+
const foundSession = await options.sessions?.getBySessionId(binding.sessionId) ?? null;
|
|
77
|
+
const sourceSession = foundSession?.orgId === binding.orgId ? foundSession : null;
|
|
78
|
+
if (sourceSession) updates.projectRepositoryId = sourceSession.projectRepositoryId;
|
|
79
|
+
if (updates.untrustedCheckout === true || item?.externalSource?.type === "pull-request") {
|
|
80
|
+
updates.untrustedCheckout = true;
|
|
81
|
+
const metadataBaseBranch = item?.metadata?.baseBranch;
|
|
82
|
+
const baseRef = sourceSession?.baseBranch || (typeof metadataBaseBranch === "string" && metadataBaseBranch ? metadataBaseBranch : void 0);
|
|
83
|
+
if (baseRef) updates.baseRef = baseRef;
|
|
84
|
+
}
|
|
85
|
+
} catch {}
|
|
86
|
+
try {
|
|
87
|
+
await options.context.setState(updates);
|
|
88
|
+
} catch {}
|
|
89
|
+
}
|
|
25
90
|
//#endregion
|
|
26
|
-
export { getFactorySessionAddress, getFactorySessionCoordinates };
|
|
91
|
+
export { getFactorySessionAddress, getFactorySessionCoordinates, resolveFactorySessionAddress };
|
|
27
92
|
|
|
28
93
|
//# sourceMappingURL=binding-context.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"binding-context.js","names":[],"sources":["../../src/rules/binding-context.ts"],"sourcesContent":["import type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\n\nimport type { FactoryAuthUser } from '../auth.js';\nimport { getFactoryAuthOrgId } from '../auth.js';\nimport type {
|
|
1
|
+
{"version":3,"file":"binding-context.js","names":[],"sources":["../../src/rules/binding-context.ts"],"sourcesContent":["import type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\n\nimport type { FactoryAuthUser } from '../auth.js';\nimport { getFactoryAuthOrgId } from '../auth.js';\nimport type {\n FactoryRunBindingAddress,\n FactoryRunBindingRecord,\n FactoryRunBindingSessionAddress,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\n\ninterface FactorySessionState {\n factoryProjectId?: string;\n projectRepositoryId?: string;\n untrustedCheckout?: boolean;\n baseRef?: string;\n}\n\ntype FactorySessionControllerContext = AgentControllerRequestContext<FactorySessionState>;\n\n/** Minimal source-control session lookup used to heal recovered session state. */\nexport interface FactorySessionSourceLookup {\n getBySessionId(sessionId: string): Promise<{ orgId: string; projectRepositoryId: string; baseBranch: string } | null>;\n}\n\nexport function getFactorySessionCoordinates(\n requestContext: RequestContext | undefined,\n): FactoryRunBindingSessionAddress | null {\n if (!requestContext || typeof requestContext.get !== 'function') return null;\n const context = requestContext.get('controller') as FactorySessionControllerContext | undefined;\n const factoryProjectId = context?.getState().factoryProjectId;\n if (!context?.threadId || !context.resourceId || !factoryProjectId) return null;\n return {\n factoryProjectId,\n threadId: context.threadId,\n resourceId: context.resourceId,\n sessionId: context.resourceId,\n };\n}\n\nexport function getFactorySessionAddress(requestContext: RequestContext | undefined): FactoryRunBindingAddress | null {\n const coordinates = getFactorySessionCoordinates(requestContext);\n if (!coordinates || !requestContext || typeof requestContext.get !== 'function') return null;\n const user = requestContext.get('user') as FactoryAuthUser | undefined;\n const orgId = getFactoryAuthOrgId(user);\n if (!orgId) return null;\n return { orgId, ...coordinates };\n}\n\nexport interface FactorySessionAddressResolution {\n address: FactoryRunBindingAddress;\n /** Present only when the address was recovered from the binding table. */\n binding?: FactoryRunBindingRecord;\n}\n\n/**\n * Resolve the bound session address, falling back to the active binding row\n * when the in-memory session state is missing `factoryProjectId` — a server\n * crash drops controller sessions, and a resumed session (\"Continue\") is\n * recreated with empty state, which silently unregistered the transition\n * tool. On recovery the lost state is healed back onto the session (including\n * the `untrustedCheckout`/`baseRef` security posture the start coordinator\n * originally seeded) so later reads resolve directly.\n */\nexport async function resolveFactorySessionAddress(options: {\n requestContext: RequestContext | undefined;\n storage: Pick<WorkItemsStorage, 'findActiveRunBindingByThread' | 'get'>;\n sessions?: FactorySessionSourceLookup;\n}): Promise<FactorySessionAddressResolution | null> {\n const direct = getFactorySessionAddress(options.requestContext);\n if (direct) return { address: direct };\n\n const requestContext = options.requestContext;\n if (!requestContext || typeof requestContext.get !== 'function') return null;\n const context = requestContext.get('controller') as FactorySessionControllerContext | undefined;\n if (!context?.threadId || !context.resourceId) return null;\n const user = requestContext.get('user') as FactoryAuthUser | undefined;\n const orgId = getFactoryAuthOrgId(user);\n if (!orgId) return null;\n\n const binding = await options.storage.findActiveRunBindingByThread({\n orgId,\n threadId: context.threadId,\n resourceId: context.resourceId,\n sessionId: context.resourceId,\n });\n if (!binding) return null;\n\n await healRecoveredSessionState({ context, binding, storage: options.storage, sessions: options.sessions });\n\n return {\n address: {\n orgId,\n factoryProjectId: binding.factoryProjectId,\n threadId: context.threadId,\n resourceId: context.resourceId,\n sessionId: context.resourceId,\n },\n binding,\n };\n}\n\nasync function healRecoveredSessionState(options: {\n context: FactorySessionControllerContext;\n binding: FactoryRunBindingRecord;\n storage: Pick<WorkItemsStorage, 'get'>;\n sessions?: FactorySessionSourceLookup;\n}): Promise<void> {\n const { binding } = options;\n const updates: FactorySessionState = { factoryProjectId: binding.factoryProjectId };\n // Security posture first, from the binding row alone (no I/O): review-bound\n // sessions run against attacker-writable checkouts, so `untrustedCheckout`\n // must survive even if the enrichment lookups below fail transiently.\n if (binding.role === 'review') {\n updates.untrustedCheckout = true;\n }\n // Best-effort enrichment mirroring the start coordinator's original seed:\n // PR-sourced work items are untrusted regardless of role, and `baseRef`\n // restores the trusted instruction source for untrusted checkouts.\n try {\n const item = await options.storage.get({ orgId: binding.orgId, id: binding.workItemId });\n const foundSession = (await options.sessions?.getBySessionId(binding.sessionId)) ?? null;\n const sourceSession = foundSession?.orgId === binding.orgId ? foundSession : null;\n if (sourceSession) {\n updates.projectRepositoryId = sourceSession.projectRepositoryId;\n }\n const untrusted = updates.untrustedCheckout === true || item?.externalSource?.type === 'pull-request';\n if (untrusted) {\n updates.untrustedCheckout = true;\n const metadataBaseBranch = item?.metadata?.baseBranch;\n const baseRef =\n sourceSession?.baseBranch ||\n (typeof metadataBaseBranch === 'string' && metadataBaseBranch ? metadataBaseBranch : undefined);\n if (baseRef) updates.baseRef = baseRef;\n }\n } catch {\n // Enrichment is best-effort; the address itself only needs the binding.\n }\n try {\n await options.context.setState(updates);\n } catch {\n // State healing must never block tool registration on the recovered address.\n }\n}\n"],"mappings":";;AA0BA,SAAgB,6BACd,gBACwC;CACxC,IAAI,CAAC,kBAAkB,OAAO,eAAe,QAAQ,YAAY,OAAO;CACxE,MAAM,UAAU,eAAe,IAAI,YAAY;CAC/C,MAAM,mBAAmB,SAAS,SAAS,CAAC,CAAC;CAC7C,IAAI,CAAC,SAAS,YAAY,CAAC,QAAQ,cAAc,CAAC,kBAAkB,OAAO;CAC3E,OAAO;EACL;EACA,UAAU,QAAQ;EAClB,YAAY,QAAQ;EACpB,WAAW,QAAQ;CACrB;AACF;AAEA,SAAgB,yBAAyB,gBAA6E;CACpH,MAAM,cAAc,6BAA6B,cAAc;CAC/D,IAAI,CAAC,eAAe,CAAC,kBAAkB,OAAO,eAAe,QAAQ,YAAY,OAAO;CAExF,MAAM,QAAQ,oBADD,eAAe,IAAI,MACK,CAAC;CACtC,IAAI,CAAC,OAAO,OAAO;CACnB,OAAO;EAAE;EAAO,GAAG;CAAY;AACjC;;;;;;;;;;AAiBA,eAAsB,6BAA6B,SAIC;CAClD,MAAM,SAAS,yBAAyB,QAAQ,cAAc;CAC9D,IAAI,QAAQ,OAAO,EAAE,SAAS,OAAO;CAErC,MAAM,iBAAiB,QAAQ;CAC/B,IAAI,CAAC,kBAAkB,OAAO,eAAe,QAAQ,YAAY,OAAO;CACxE,MAAM,UAAU,eAAe,IAAI,YAAY;CAC/C,IAAI,CAAC,SAAS,YAAY,CAAC,QAAQ,YAAY,OAAO;CAEtD,MAAM,QAAQ,oBADD,eAAe,IAAI,MACK,CAAC;CACtC,IAAI,CAAC,OAAO,OAAO;CAEnB,MAAM,UAAU,MAAM,QAAQ,QAAQ,6BAA6B;EACjE;EACA,UAAU,QAAQ;EAClB,YAAY,QAAQ;EACpB,WAAW,QAAQ;CACrB,CAAC;CACD,IAAI,CAAC,SAAS,OAAO;CAErB,MAAM,0BAA0B;EAAE;EAAS;EAAS,SAAS,QAAQ;EAAS,UAAU,QAAQ;CAAS,CAAC;CAE1G,OAAO;EACL,SAAS;GACP;GACA,kBAAkB,QAAQ;GAC1B,UAAU,QAAQ;GAClB,YAAY,QAAQ;GACpB,WAAW,QAAQ;EACrB;EACA;CACF;AACF;AAEA,eAAe,0BAA0B,SAKvB;CAChB,MAAM,EAAE,YAAY;CACpB,MAAM,UAA+B,EAAE,kBAAkB,QAAQ,iBAAiB;CAIlF,IAAI,QAAQ,SAAS,UACnB,QAAQ,oBAAoB;CAK9B,IAAI;EACF,MAAM,OAAO,MAAM,QAAQ,QAAQ,IAAI;GAAE,OAAO,QAAQ;GAAO,IAAI,QAAQ;EAAW,CAAC;EACvF,MAAM,eAAgB,MAAM,QAAQ,UAAU,eAAe,QAAQ,SAAS,KAAM;EACpF,MAAM,gBAAgB,cAAc,UAAU,QAAQ,QAAQ,eAAe;EAC7E,IAAI,eACF,QAAQ,sBAAsB,cAAc;EAG9C,IADkB,QAAQ,sBAAsB,QAAQ,MAAM,gBAAgB,SAAS,gBACxE;GACb,QAAQ,oBAAoB;GAC5B,MAAM,qBAAqB,MAAM,UAAU;GAC3C,MAAM,UACJ,eAAe,eACd,OAAO,uBAAuB,YAAY,qBAAqB,qBAAqB,KAAA;GACvF,IAAI,SAAS,QAAQ,UAAU;EACjC;CACF,QAAQ,CAER;CACA,IAAI;EACF,MAAM,QAAQ,QAAQ,SAAS,OAAO;CACxC,QAAQ,CAER;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/rules/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EASV,YAAY,EACZ,qBAAqB,EAMtB,MAAM,YAAY,CAAC;AAGpB,eAAO,MAAM,4BAA4B,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/rules/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EASV,YAAY,EACZ,qBAAqB,EAMtB,MAAM,YAAY,CAAC;AAGpB,eAAO,MAAM,4BAA4B,uBAAuB,CAAC;AAuajE,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,qBAAqB,EAC3B,SAAS,GAAE,qBAA0B,GACpC,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,CAQ/B;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,qBAAqB,CAAA;CAAE,GAAG,YAAY,CAW/G;AAED,wBAAgB,mBAAmB,IAAI,YAAY,CAElD"}
|
package/dist/rules/defaults.js
CHANGED
|
@@ -4,6 +4,9 @@ const DEFAULT_FACTORY_RULE_VERSION = "factory-default-v1";
|
|
|
4
4
|
function trustedGithubActor(context) {
|
|
5
5
|
return context.actor.type === "github" && context.actor.trusted;
|
|
6
6
|
}
|
|
7
|
+
function githubActorLogin(context) {
|
|
8
|
+
return context.actor.type === "github" ? context.actor.login : void 0;
|
|
9
|
+
}
|
|
7
10
|
function invokeIssueInvestigation(context) {
|
|
8
11
|
return {
|
|
9
12
|
type: "invokeSkill",
|
|
@@ -91,10 +94,26 @@ function issueOpened(context) {
|
|
|
91
94
|
stage: trustedGithubActor(context) && createdAfterFactory(context.issue.createdAt, context.factory.createdAt) ? "triage" : "intake",
|
|
92
95
|
metadata: {
|
|
93
96
|
githubRepositoryId: context.repository.id,
|
|
94
|
-
githubIssueNumber: context.issue.number
|
|
97
|
+
githubIssueNumber: context.issue.number,
|
|
98
|
+
...githubActorLogin(context) ? { author: githubActorLogin(context) } : {},
|
|
99
|
+
assignees: context.issue.assignees ?? [],
|
|
100
|
+
labels: context.issue.labels ?? []
|
|
95
101
|
}
|
|
96
102
|
};
|
|
97
103
|
}
|
|
104
|
+
function issueClosed(context) {
|
|
105
|
+
if (!context.item || context.item.source !== "github-issue" || !context.issue) return;
|
|
106
|
+
if (context.board !== "work") return;
|
|
107
|
+
if (context.item.stages.some((stage) => stage === "done" || stage === "canceled")) return;
|
|
108
|
+
const canceled = context.issue.stateReason === "not_planned" || context.issue.stateReason === "duplicate";
|
|
109
|
+
return {
|
|
110
|
+
type: "transition",
|
|
111
|
+
idempotencyKey: `${context.ingress.id}:issue-closed`,
|
|
112
|
+
board: "work",
|
|
113
|
+
stage: canceled ? "canceled" : "done",
|
|
114
|
+
message: { text: `GitHub issue #${context.issue.number} was closed${context.issue.stateReason ? ` (${context.issue.stateReason})` : ""}; this Work card was moved to ${canceled ? "Canceled" : "Done"}.` }
|
|
115
|
+
};
|
|
116
|
+
}
|
|
98
117
|
function pullRequestOpened(context) {
|
|
99
118
|
if (!context.pullRequest) return;
|
|
100
119
|
return {
|
|
@@ -110,8 +129,15 @@ function pullRequestOpened(context) {
|
|
|
110
129
|
githubRepositoryId: context.repository.id,
|
|
111
130
|
githubPullRequestNumber: context.pullRequest.number,
|
|
112
131
|
factoryAuthored: context.actor.type === "github" && context.actor.factoryAuthored,
|
|
132
|
+
state: context.pullRequest.state,
|
|
133
|
+
draft: context.pullRequest.draft,
|
|
134
|
+
merged: context.pullRequest.merged,
|
|
135
|
+
assignees: context.pullRequest.assignees ?? [],
|
|
136
|
+
requestedReviewers: context.pullRequest.requestedReviewers ?? [],
|
|
137
|
+
labels: context.pullRequest.labels ?? [],
|
|
113
138
|
headBranch: context.pullRequest.headBranch,
|
|
114
|
-
baseBranch: context.pullRequest.baseBranch
|
|
139
|
+
baseBranch: context.pullRequest.baseBranch,
|
|
140
|
+
...githubActorLogin(context) ? { author: githubActorLogin(context) } : {}
|
|
115
141
|
}
|
|
116
142
|
};
|
|
117
143
|
}
|
|
@@ -142,6 +168,19 @@ function pullRequestClosed(context) {
|
|
|
142
168
|
message: { text: `Pull request #${context.pullRequest.number} was closed without merging; this Review card was moved to Canceled.` }
|
|
143
169
|
};
|
|
144
170
|
}
|
|
171
|
+
function reReviewRequestedPullRequest(context) {
|
|
172
|
+
if (!context.item || context.board !== "review" || !context.reviewRequest?.factoryReviewer) return;
|
|
173
|
+
if (!context.pullRequest || context.pullRequest.state !== "open" || context.pullRequest.merged) return;
|
|
174
|
+
if (!trustedGithubActor(context)) return;
|
|
175
|
+
if (context.actor.type === "github" && context.actor.factoryAuthored) return;
|
|
176
|
+
if (context.item.stages.length === 1 && context.item.stages[0] === "review") return;
|
|
177
|
+
return {
|
|
178
|
+
type: "transition",
|
|
179
|
+
idempotencyKey: `${context.ingress.id}:re-review-requested`,
|
|
180
|
+
board: "review",
|
|
181
|
+
stage: "review"
|
|
182
|
+
};
|
|
183
|
+
}
|
|
145
184
|
function linearIssueObserved(context) {
|
|
146
185
|
if (context.item) return;
|
|
147
186
|
return {
|
|
@@ -155,15 +194,37 @@ function linearIssueObserved(context) {
|
|
|
155
194
|
stage: "triage",
|
|
156
195
|
metadata: {
|
|
157
196
|
linearIssueId: context.issue.id,
|
|
158
|
-
|
|
197
|
+
identifier: context.issue.identifier,
|
|
159
198
|
linearState: context.issue.state,
|
|
160
199
|
linearStateType: context.issue.stateType,
|
|
161
200
|
linearPriority: context.issue.priorityLabel,
|
|
162
201
|
linearAssignee: context.issue.assignee,
|
|
163
|
-
|
|
202
|
+
linearCreator: context.issue.creator,
|
|
203
|
+
linearTeam: context.issue.team,
|
|
204
|
+
labels: [...context.issue.labels],
|
|
205
|
+
...context.issue.assignee ? { assignee: context.issue.assignee } : {},
|
|
206
|
+
...context.issue.creator ? {
|
|
207
|
+
creator: context.issue.creator,
|
|
208
|
+
author: context.issue.creator
|
|
209
|
+
} : {}
|
|
164
210
|
}
|
|
165
211
|
};
|
|
166
212
|
}
|
|
213
|
+
function linearIssueClosed(context) {
|
|
214
|
+
if (!context.item || context.item.source !== "linear-issue") return;
|
|
215
|
+
if (context.board !== "work") return;
|
|
216
|
+
if (context.item.stages.some((stage) => stage === "done" || stage === "canceled")) return;
|
|
217
|
+
const stateType = context.issue.stateType;
|
|
218
|
+
if (stateType !== "completed" && stateType !== "canceled") return;
|
|
219
|
+
const canceled = stateType === "canceled";
|
|
220
|
+
return {
|
|
221
|
+
type: "transition",
|
|
222
|
+
idempotencyKey: `${context.ingress.id}:issue-closed`,
|
|
223
|
+
board: "work",
|
|
224
|
+
stage: canceled ? "canceled" : "done",
|
|
225
|
+
message: { text: `Linear issue ${context.issue.identifier} was ${canceled ? "canceled" : "completed"}; this Work card was moved to ${canceled ? "Canceled" : "Done"}.` }
|
|
226
|
+
};
|
|
227
|
+
}
|
|
167
228
|
const BUILT_IN_DEFAULTS = {
|
|
168
229
|
work: {
|
|
169
230
|
triage: {
|
|
@@ -181,14 +242,19 @@ const BUILT_IN_DEFAULTS = {
|
|
|
181
242
|
github: {
|
|
182
243
|
issueOpened: { onEvent: issueOpened },
|
|
183
244
|
issueEdited: { onEvent: retriageGithubIssue },
|
|
245
|
+
issueClosed: { onEvent: issueClosed },
|
|
184
246
|
issueCommentCreated: { onEvent: retriageGithubIssue },
|
|
185
247
|
issueCommentEdited: { onEvent: retriageGithubIssue },
|
|
186
248
|
issueCommentDeleted: { onEvent: retriageGithubIssue },
|
|
187
249
|
pullRequestOpened: { onEvent: pullRequestOpened },
|
|
250
|
+
pullRequestReviewRequested: { onEvent: reReviewRequestedPullRequest },
|
|
188
251
|
pullRequestMerged: { onEvent: pullRequestMerged },
|
|
189
252
|
pullRequestClosed: { onEvent: pullRequestClosed }
|
|
190
253
|
},
|
|
191
|
-
linear: {
|
|
254
|
+
linear: {
|
|
255
|
+
issueObserved: { onEvent: linearIssueObserved },
|
|
256
|
+
issueClosed: { onEvent: linearIssueClosed }
|
|
257
|
+
}
|
|
192
258
|
};
|
|
193
259
|
function mergeBoardRules(base, overrides) {
|
|
194
260
|
const result = {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.js","names":[],"sources":["../../src/rules/defaults.ts"],"sourcesContent":["import type {\n FactoryBoardRuleLeaf,\n FactoryBoardRules,\n FactoryGithubRuleLeaf,\n FactoryGithubEventName,\n FactoryGithubRuleContext,\n FactoryLinearEventName,\n FactoryLinearRuleContext,\n FactoryLinearRuleLeaf,\n FactoryRules,\n FactoryRulesOverrides,\n FactoryRuleSource,\n FactoryRuleStage,\n FactoryStageRuleContext,\n FactoryToolResultRuleContext,\n FactoryToolRuleLeaf,\n} from './types.js';\nimport { assertFactoryRules, FactoryRuleValidationError } from './validation.js';\n\nexport const DEFAULT_FACTORY_RULE_VERSION = 'factory-default-v1';\n\nfunction trustedGithubActor(context: Pick<FactoryStageRuleContext, 'actor'>): boolean {\n return context.actor.type === 'github' && context.actor.trusted;\n}\n\nfunction invokeIssueInvestigation(context: FactoryStageRuleContext) {\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:factory-triage`,\n role: 'triage',\n skillName: 'factory-triage',\n arguments: context.item.url ? `GitHub issue (${context.item.url})` : context.item.title,\n } as const;\n}\n\nfunction investigateTriagedIssue(context: FactoryStageRuleContext) {\n if (context.cause === 'linked_item_materialized' && context.fromStage === 'intake' && context.toStage === 'triage') {\n return;\n }\n return invokeIssueInvestigation(context);\n}\n\nfunction retriageGithubIssue(context: FactoryGithubRuleContext) {\n if (!context.item || context.item.source !== 'github-issue' || !context.item.url) return;\n if (context.actor.type === 'github' && context.actor.factoryAuthored) return;\n\n const reason =\n context.event === 'issueEdited'\n ? context.issueChange?.title && context.issueChange.body\n ? 'issue title and body edited'\n : context.issueChange?.title\n ? 'issue title edited'\n : 'issue body edited'\n : context.event === 'issueCommentDeleted'\n ? 'comment deleted'\n : context.event === 'issueCommentEdited'\n ? 'comment edited'\n : 'comment created';\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:factory-triage`,\n role: 'triage',\n skillName: 'factory-triage',\n arguments: `Re-triage GitHub issue (${context.item.url}) after ${reason}.`,\n } as const;\n}\n\nfunction investigateTriagedLinearIssue(context: FactoryStageRuleContext) {\n const identifier = context.item.sourceKey?.startsWith('linear:')\n ? context.item.sourceKey.slice('linear:'.length)\n : context.item.title;\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:factory-triage-linear`,\n role: 'triage',\n skillName: 'factory-triage',\n arguments: `Linear issue ${identifier}${context.item.url ? ` (${context.item.url})` : ''}`,\n } as const;\n}\n\nfunction planWorkItem(context: FactoryStageRuleContext) {\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:factory-plan`,\n role: 'plan',\n skillName: 'factory-plan',\n arguments: context.item.url ? `Work item (${context.item.url})` : context.item.title,\n } as const;\n}\n\nfunction reviewPullRequest(context: FactoryStageRuleContext) {\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:factory-review`,\n role: 'review',\n skillName: 'factory-review',\n arguments: context.item.url ? `GitHub pull request (${context.item.url})` : context.item.title,\n } as const;\n}\n\nfunction resultContent(value: unknown): string | undefined {\n if (typeof value === 'string') return value;\n if (!value || typeof value !== 'object' || Array.isArray(value)) return;\n const content = (value as { content?: unknown }).content;\n return typeof content === 'string' ? content : undefined;\n}\n\n// Interactive-session path only: factory-plan never calls submit_plan — it\n// advances planning → execute via factory_transition_work_item directly.\nfunction advanceApprovedPlan(context: FactoryToolResultRuleContext) {\n if (\n context.result.status !== 'success' ||\n context.board !== 'work' ||\n context.item.stages.length !== 1 ||\n context.item.stages[0] !== 'planning' ||\n context.actor.type !== 'agent' ||\n context.actor.role !== 'plan' ||\n !resultContent(context.result.value)?.startsWith('Plan approved.')\n ) {\n return;\n }\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:approved-plan`,\n board: 'work',\n stage: 'execute',\n } as const;\n}\n\nfunction createdAfterFactory(createdAt: string | undefined, factoryCreatedAt: string): boolean {\n if (!createdAt) return false;\n const sourceCreatedAt = Date.parse(createdAt);\n const projectCreatedAt = Date.parse(factoryCreatedAt);\n return Number.isFinite(sourceCreatedAt) && Number.isFinite(projectCreatedAt) && sourceCreatedAt > projectCreatedAt;\n}\n\nfunction issueOpened(context: FactoryGithubRuleContext) {\n if (!context.issue) return;\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey: `${context.ingress.id}:issue-intake`,\n board: 'work',\n source: 'github-issue',\n sourceKey: `github-issue:${context.issue.number}`,\n title: context.issue.title,\n url: context.issue.url,\n stage:\n trustedGithubActor(context) && createdAfterFactory(context.issue.createdAt, context.factory.createdAt)\n ? 'triage'\n : 'intake',\n metadata: {\n githubRepositoryId: context.repository.id,\n githubIssueNumber: context.issue.number,\n },\n } as const;\n}\n\nfunction pullRequestOpened(context: FactoryGithubRuleContext) {\n if (!context.pullRequest) return;\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey: `${context.ingress.id}:pull-request-intake`,\n board: 'review',\n source: 'github-pr',\n sourceKey: `github-pr:${context.pullRequest.number}`,\n title: context.pullRequest.title,\n url: context.pullRequest.url,\n stage:\n trustedGithubActor(context) && createdAfterFactory(context.pullRequest.createdAt, context.factory.createdAt)\n ? 'review'\n : 'intake',\n metadata: {\n githubRepositoryId: context.repository.id,\n githubPullRequestNumber: context.pullRequest.number,\n factoryAuthored: context.actor.type === 'github' && context.actor.factoryAuthored,\n headBranch: context.pullRequest.headBranch,\n baseBranch: context.pullRequest.baseBranch,\n },\n } as const;\n}\n\nfunction pullRequestMerged(context: FactoryGithubRuleContext) {\n if (!context.item || !context.pullRequest?.merged) return;\n if (context.board === 'review') {\n // The event is bound to the PR's own Review card: a merged PR is finished\n // review work, so always move the card to Done. The message only reaches\n // an active session (if any) — cards without one just move, instead of\n // failing retries against a binding that never existed.\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:pull-request-merged`,\n board: 'review',\n stage: 'done',\n message: {\n text:\n `Pull request #${context.pullRequest.number} merged; this Review card was moved to Done. ` +\n 'No further review is needed unless follow-up work was requested.',\n },\n } as const;\n }\n // Provenance bound the event to the originating Work item instead: remind\n // its agent to assess completion — never auto-complete the Work item.\n return {\n type: 'sendMessage',\n idempotencyKey: `${context.ingress.id}:assess-work-completion`,\n role: 'work',\n message:\n `Pull request #${context.pullRequest.number} merged. Assess whether the linked Work item is complete. ` +\n 'Do not mark it Done solely because this PR merged; use factory_transition_work_item only after verifying the work.',\n } as const;\n}\n\nfunction pullRequestClosed(context: FactoryGithubRuleContext) {\n if (!context.item || !context.pullRequest || context.pullRequest.merged) return;\n if (context.board !== 'review') return;\n // A PR closed without merging is abandoned review work: clear the card off\n // the board instead of leaving it in Reviewing forever.\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:pull-request-closed`,\n board: 'review',\n stage: 'canceled',\n message: {\n text:\n `Pull request #${context.pullRequest.number} was closed without merging; ` +\n 'this Review card was moved to Canceled.',\n },\n } as const;\n}\n\nfunction linearIssueObserved(context: FactoryLinearRuleContext) {\n if (context.item) return;\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey: `${context.ingress.id}:issue-triage`,\n board: 'work',\n source: 'linear-issue',\n sourceKey: `linear:${context.issue.identifier}`,\n title: `${context.issue.identifier}: ${context.issue.title}`,\n url: context.issue.url,\n stage: 'triage',\n metadata: {\n linearIssueId: context.issue.id,\n linearIssueIdentifier: context.issue.identifier,\n linearState: context.issue.state,\n linearStateType: context.issue.stateType,\n linearPriority: context.issue.priorityLabel,\n linearAssignee: context.issue.assignee,\n linearTeam: context.issue.team,\n },\n } as const;\n}\n\nconst BUILT_IN_DEFAULTS: FactoryRulesOverrides = {\n work: {\n triage: {\n issue: { onEnter: investigateTriagedIssue },\n linearIssue: { onEnter: investigateTriagedLinearIssue },\n },\n planning: {\n issue: { onEnter: planWorkItem },\n linearIssue: { onEnter: planWorkItem },\n manual: { onEnter: planWorkItem },\n },\n },\n review: { review: { pullRequest: { onEnter: reviewPullRequest } } },\n tools: { submit_plan: { onResult: advanceApprovedPlan } },\n github: {\n issueOpened: { onEvent: issueOpened },\n issueEdited: { onEvent: retriageGithubIssue },\n issueCommentCreated: { onEvent: retriageGithubIssue },\n issueCommentEdited: { onEvent: retriageGithubIssue },\n issueCommentDeleted: { onEvent: retriageGithubIssue },\n pullRequestOpened: { onEvent: pullRequestOpened },\n pullRequestMerged: { onEvent: pullRequestMerged },\n pullRequestClosed: { onEvent: pullRequestClosed },\n },\n linear: { issueObserved: { onEvent: linearIssueObserved } },\n};\n\nfunction mergeBoardRules(\n base: FactoryBoardRules | undefined,\n overrides: FactoryBoardRules | undefined,\n): FactoryBoardRules {\n const result: FactoryBoardRules = {};\n const stages = new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]) as Set<FactoryRuleStage>;\n for (const stage of stages) {\n const baseSources = base?.[stage];\n const overrideSources = overrides?.[stage];\n const sources = new Set([\n ...Object.keys(baseSources ?? {}),\n ...Object.keys(overrideSources ?? {}),\n ]) as Set<FactoryRuleSource>;\n const mergedSources: Partial<Record<FactoryRuleSource, FactoryBoardRuleLeaf>> = {};\n for (const source of sources) {\n const baseLeaf = baseSources?.[source];\n const overrideLeaf = overrideSources?.[source];\n mergedSources[source] = {\n ...(baseLeaf?.onEnter ? { onEnter: baseLeaf.onEnter } : {}),\n ...(baseLeaf?.onExit ? { onExit: baseLeaf.onExit } : {}),\n ...(overrideLeaf && 'onEnter' in overrideLeaf ? { onEnter: overrideLeaf.onEnter } : {}),\n ...(overrideLeaf && 'onExit' in overrideLeaf ? { onExit: overrideLeaf.onExit } : {}),\n };\n }\n result[stage] = mergedSources;\n }\n return result;\n}\n\nfunction mergeToolRules(\n base: Record<string, FactoryToolRuleLeaf> | undefined,\n overrides: Record<string, FactoryToolRuleLeaf> | undefined,\n): Record<string, FactoryToolRuleLeaf> {\n const result: Record<string, FactoryToolRuleLeaf> = {};\n for (const name of new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})])) {\n const baseLeaf = base?.[name];\n const overrideLeaf = overrides?.[name];\n result[name] = {\n ...(baseLeaf?.onResult ? { onResult: baseLeaf.onResult } : {}),\n ...(overrideLeaf && 'onResult' in overrideLeaf ? { onResult: overrideLeaf.onResult } : {}),\n };\n }\n return result;\n}\n\nfunction mergeGithubRules(\n base: FactoryRulesOverrides['github'],\n overrides: FactoryRulesOverrides['github'],\n): NonNullable<FactoryRulesOverrides['github']> {\n const result: Partial<Record<FactoryGithubEventName, FactoryGithubRuleLeaf>> = {};\n const events = new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]) as Set<FactoryGithubEventName>;\n for (const event of events) {\n const baseLeaf = base?.[event];\n const overrideLeaf = overrides?.[event];\n result[event] = {\n ...(baseLeaf?.onEvent ? { onEvent: baseLeaf.onEvent } : {}),\n ...(overrideLeaf && 'onEvent' in overrideLeaf ? { onEvent: overrideLeaf.onEvent } : {}),\n };\n }\n return result;\n}\n\nfunction mergeLinearRules(\n base: FactoryRulesOverrides['linear'],\n overrides: FactoryRulesOverrides['linear'],\n): NonNullable<FactoryRulesOverrides['linear']> {\n const result: Partial<Record<FactoryLinearEventName, FactoryLinearRuleLeaf>> = {};\n const events = new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]) as Set<FactoryLinearEventName>;\n for (const event of events) {\n const baseLeaf = base?.[event];\n const overrideLeaf = overrides?.[event];\n result[event] = {\n ...(baseLeaf?.onEvent ? { onEvent: baseLeaf.onEvent } : {}),\n ...(overrideLeaf && 'onEvent' in overrideLeaf ? { onEvent: overrideLeaf.onEvent } : {}),\n };\n }\n return result;\n}\n\nexport function mergeFactoryRuleOverrides(\n base: FactoryRulesOverrides,\n overrides: FactoryRulesOverrides = {},\n): Omit<FactoryRules, 'version'> {\n return {\n work: mergeBoardRules(base.work, overrides.work),\n review: mergeBoardRules(base.review, overrides.review),\n tools: mergeToolRules(base.tools, overrides.tools),\n github: mergeGithubRules(base.github, overrides.github),\n linear: mergeLinearRules(base.linear, overrides.linear),\n };\n}\n\nexport function defaultFactoryRules(input: { version: string; overrides?: FactoryRulesOverrides }): FactoryRules {\n if (typeof input?.version !== 'string' || input.version.trim().length === 0) {\n throw new FactoryRuleValidationError('Factory rule version is required.');\n }\n\n const rules: FactoryRules = {\n version: input.version.trim(),\n ...mergeFactoryRuleOverrides(BUILT_IN_DEFAULTS, input.overrides),\n };\n assertFactoryRules(rules);\n return rules;\n}\n\nexport function builtInFactoryRules(): FactoryRules {\n return defaultFactoryRules({ version: DEFAULT_FACTORY_RULE_VERSION });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"defaults.js","names":[],"sources":["../../src/rules/defaults.ts"],"sourcesContent":["import type {\n FactoryBoardRuleLeaf,\n FactoryBoardRules,\n FactoryGithubRuleLeaf,\n FactoryGithubEventName,\n FactoryGithubRuleContext,\n FactoryLinearEventName,\n FactoryLinearRuleContext,\n FactoryLinearRuleLeaf,\n FactoryRules,\n FactoryRulesOverrides,\n FactoryRuleSource,\n FactoryRuleStage,\n FactoryStageRuleContext,\n FactoryToolResultRuleContext,\n FactoryToolRuleLeaf,\n} from './types.js';\nimport { assertFactoryRules, FactoryRuleValidationError } from './validation.js';\n\nexport const DEFAULT_FACTORY_RULE_VERSION = 'factory-default-v1';\n\nfunction trustedGithubActor(context: Pick<FactoryStageRuleContext, 'actor'>): boolean {\n return context.actor.type === 'github' && context.actor.trusted;\n}\n\nfunction githubActorLogin(context: Pick<FactoryStageRuleContext, 'actor'>): string | undefined {\n return context.actor.type === 'github' ? context.actor.login : undefined;\n}\n\nfunction invokeIssueInvestigation(context: FactoryStageRuleContext) {\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:factory-triage`,\n role: 'triage',\n skillName: 'factory-triage',\n arguments: context.item.url ? `GitHub issue (${context.item.url})` : context.item.title,\n } as const;\n}\n\nfunction investigateTriagedIssue(context: FactoryStageRuleContext) {\n if (context.cause === 'linked_item_materialized' && context.fromStage === 'intake' && context.toStage === 'triage') {\n return;\n }\n return invokeIssueInvestigation(context);\n}\n\nfunction retriageGithubIssue(context: FactoryGithubRuleContext) {\n if (!context.item || context.item.source !== 'github-issue' || !context.item.url) return;\n if (context.actor.type === 'github' && context.actor.factoryAuthored) return;\n\n const reason =\n context.event === 'issueEdited'\n ? context.issueChange?.title && context.issueChange.body\n ? 'issue title and body edited'\n : context.issueChange?.title\n ? 'issue title edited'\n : 'issue body edited'\n : context.event === 'issueCommentDeleted'\n ? 'comment deleted'\n : context.event === 'issueCommentEdited'\n ? 'comment edited'\n : 'comment created';\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:factory-triage`,\n role: 'triage',\n skillName: 'factory-triage',\n arguments: `Re-triage GitHub issue (${context.item.url}) after ${reason}.`,\n } as const;\n}\n\nfunction investigateTriagedLinearIssue(context: FactoryStageRuleContext) {\n const identifier = context.item.sourceKey?.startsWith('linear:')\n ? context.item.sourceKey.slice('linear:'.length)\n : context.item.title;\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:factory-triage-linear`,\n role: 'triage',\n skillName: 'factory-triage',\n arguments: `Linear issue ${identifier}${context.item.url ? ` (${context.item.url})` : ''}`,\n } as const;\n}\n\nfunction planWorkItem(context: FactoryStageRuleContext) {\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:factory-plan`,\n role: 'plan',\n skillName: 'factory-plan',\n arguments: context.item.url ? `Work item (${context.item.url})` : context.item.title,\n } as const;\n}\n\nfunction reviewPullRequest(context: FactoryStageRuleContext) {\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:factory-review`,\n role: 'review',\n skillName: 'factory-review',\n arguments: context.item.url ? `GitHub pull request (${context.item.url})` : context.item.title,\n } as const;\n}\n\nfunction resultContent(value: unknown): string | undefined {\n if (typeof value === 'string') return value;\n if (!value || typeof value !== 'object' || Array.isArray(value)) return;\n const content = (value as { content?: unknown }).content;\n return typeof content === 'string' ? content : undefined;\n}\n\n// Interactive-session path only: factory-plan never calls submit_plan — it\n// advances planning → execute via factory_transition_work_item directly.\nfunction advanceApprovedPlan(context: FactoryToolResultRuleContext) {\n if (\n context.result.status !== 'success' ||\n context.board !== 'work' ||\n context.item.stages.length !== 1 ||\n context.item.stages[0] !== 'planning' ||\n context.actor.type !== 'agent' ||\n context.actor.role !== 'plan' ||\n !resultContent(context.result.value)?.startsWith('Plan approved.')\n ) {\n return;\n }\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:approved-plan`,\n board: 'work',\n stage: 'execute',\n } as const;\n}\n\nfunction createdAfterFactory(createdAt: string | undefined, factoryCreatedAt: string): boolean {\n if (!createdAt) return false;\n const sourceCreatedAt = Date.parse(createdAt);\n const projectCreatedAt = Date.parse(factoryCreatedAt);\n return Number.isFinite(sourceCreatedAt) && Number.isFinite(projectCreatedAt) && sourceCreatedAt > projectCreatedAt;\n}\n\nfunction issueOpened(context: FactoryGithubRuleContext) {\n if (!context.issue) return;\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey: `${context.ingress.id}:issue-intake`,\n board: 'work',\n source: 'github-issue',\n sourceKey: `github-issue:${context.issue.number}`,\n title: context.issue.title,\n url: context.issue.url,\n stage:\n trustedGithubActor(context) && createdAfterFactory(context.issue.createdAt, context.factory.createdAt)\n ? 'triage'\n : 'intake',\n metadata: {\n githubRepositoryId: context.repository.id,\n githubIssueNumber: context.issue.number,\n ...(githubActorLogin(context) ? { author: githubActorLogin(context) } : {}),\n assignees: context.issue.assignees ?? [],\n labels: context.issue.labels ?? [],\n },\n } as const;\n}\n\nfunction issueClosed(context: FactoryGithubRuleContext) {\n if (!context.item || context.item.source !== 'github-issue' || !context.issue) return;\n if (context.board !== 'work') return;\n // Already off the board: nothing to reconcile.\n if (context.item.stages.some(stage => stage === 'done' || stage === 'canceled')) return;\n // Issue closure is a repository fact, not third-party input — no actor trust\n // gate. `not_planned` (and `duplicate`) means abandoned, everything else is\n // completed work.\n const canceled = context.issue.stateReason === 'not_planned' || context.issue.stateReason === 'duplicate';\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:issue-closed`,\n board: 'work',\n stage: canceled ? 'canceled' : 'done',\n message: {\n text:\n `GitHub issue #${context.issue.number} was closed` +\n `${context.issue.stateReason ? ` (${context.issue.stateReason})` : ''}; ` +\n `this Work card was moved to ${canceled ? 'Canceled' : 'Done'}.`,\n },\n } as const;\n}\n\nfunction pullRequestOpened(context: FactoryGithubRuleContext) {\n if (!context.pullRequest) return;\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey: `${context.ingress.id}:pull-request-intake`,\n board: 'review',\n source: 'github-pr',\n sourceKey: `github-pr:${context.pullRequest.number}`,\n title: context.pullRequest.title,\n url: context.pullRequest.url,\n stage:\n trustedGithubActor(context) && createdAfterFactory(context.pullRequest.createdAt, context.factory.createdAt)\n ? 'review'\n : 'intake',\n metadata: {\n githubRepositoryId: context.repository.id,\n githubPullRequestNumber: context.pullRequest.number,\n factoryAuthored: context.actor.type === 'github' && context.actor.factoryAuthored,\n state: context.pullRequest.state,\n draft: context.pullRequest.draft,\n merged: context.pullRequest.merged,\n assignees: context.pullRequest.assignees ?? [],\n requestedReviewers: context.pullRequest.requestedReviewers ?? [],\n labels: context.pullRequest.labels ?? [],\n headBranch: context.pullRequest.headBranch,\n baseBranch: context.pullRequest.baseBranch,\n ...(githubActorLogin(context) ? { author: githubActorLogin(context) } : {}),\n },\n } as const;\n}\n\nfunction pullRequestMerged(context: FactoryGithubRuleContext) {\n if (!context.item || !context.pullRequest?.merged) return;\n if (context.board === 'review') {\n // The event is bound to the PR's own Review card: a merged PR is finished\n // review work, so always move the card to Done. The message only reaches\n // an active session (if any) — cards without one just move, instead of\n // failing retries against a binding that never existed.\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:pull-request-merged`,\n board: 'review',\n stage: 'done',\n message: {\n text:\n `Pull request #${context.pullRequest.number} merged; this Review card was moved to Done. ` +\n 'No further review is needed unless follow-up work was requested.',\n },\n } as const;\n }\n // Provenance bound the event to the originating Work item instead: remind\n // its agent to assess completion — never auto-complete the Work item.\n return {\n type: 'sendMessage',\n idempotencyKey: `${context.ingress.id}:assess-work-completion`,\n role: 'work',\n message:\n `Pull request #${context.pullRequest.number} merged. Assess whether the linked Work item is complete. ` +\n 'Do not mark it Done solely because this PR merged; use factory_transition_work_item only after verifying the work.',\n } as const;\n}\n\nfunction pullRequestClosed(context: FactoryGithubRuleContext) {\n if (!context.item || !context.pullRequest || context.pullRequest.merged) return;\n if (context.board !== 'review') return;\n // A PR closed without merging is abandoned review work: clear the card off\n // the board instead of leaving it in Reviewing forever.\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:pull-request-closed`,\n board: 'review',\n stage: 'canceled',\n message: {\n text:\n `Pull request #${context.pullRequest.number} was closed without merging; ` +\n 'this Review card was moved to Canceled.',\n },\n } as const;\n}\n\nfunction reReviewRequestedPullRequest(context: FactoryGithubRuleContext) {\n // Only a review re-requested *from Factory's own bot* restarts the review —\n // requesting a human reviewer is not Factory's signal.\n if (!context.item || context.board !== 'review' || !context.reviewRequest?.factoryReviewer) return;\n if (!context.pullRequest || context.pullRequest.state !== 'open' || context.pullRequest.merged) return;\n // Trusted (write/admin) requesters only: re-entering review checks out and\n // executes PR code, the same bar pullRequestOpened applies to auto-review.\n if (!trustedGithubActor(context)) return;\n if (context.actor.type === 'github' && context.actor.factoryAuthored) return;\n // Already in Reviewing: a review pass is pending or running; re-entering\n // would be a same-stage no-op anyway (stage rules only fire on change).\n if (context.item.stages.length === 1 && context.item.stages[0] === 'review') return;\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:re-review-requested`,\n board: 'review',\n stage: 'review',\n } as const;\n}\n\nfunction linearIssueObserved(context: FactoryLinearRuleContext) {\n if (context.item) return;\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey: `${context.ingress.id}:issue-triage`,\n board: 'work',\n source: 'linear-issue',\n sourceKey: `linear:${context.issue.identifier}`,\n title: `${context.issue.identifier}: ${context.issue.title}`,\n url: context.issue.url,\n stage: 'triage',\n metadata: {\n linearIssueId: context.issue.id,\n identifier: context.issue.identifier,\n linearState: context.issue.state,\n linearStateType: context.issue.stateType,\n linearPriority: context.issue.priorityLabel,\n linearAssignee: context.issue.assignee,\n linearCreator: context.issue.creator,\n linearTeam: context.issue.team,\n labels: [...context.issue.labels] as string[],\n ...(context.issue.assignee ? { assignee: context.issue.assignee } : {}),\n ...(context.issue.creator ? { creator: context.issue.creator, author: context.issue.creator } : {}),\n },\n } as const;\n}\n\nfunction linearIssueClosed(context: FactoryLinearRuleContext) {\n if (!context.item || context.item.source !== 'linear-issue') return;\n if (context.board !== 'work') return;\n // Already off the board: nothing to reconcile.\n if (context.item.stages.some(stage => stage === 'done' || stage === 'canceled')) return;\n // Only terminal state types trigger close.\n const stateType = context.issue.stateType;\n if (stateType !== 'completed' && stateType !== 'canceled') return;\n const canceled = stateType === 'canceled';\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:issue-closed`,\n board: 'work',\n stage: canceled ? 'canceled' : 'done',\n message: {\n text: `Linear issue ${context.issue.identifier} was ${canceled ? 'canceled' : 'completed'}; this Work card was moved to ${canceled ? 'Canceled' : 'Done'}.`,\n },\n } as const;\n}\n\nconst BUILT_IN_DEFAULTS: FactoryRulesOverrides = {\n work: {\n triage: {\n issue: { onEnter: investigateTriagedIssue },\n linearIssue: { onEnter: investigateTriagedLinearIssue },\n },\n planning: {\n issue: { onEnter: planWorkItem },\n linearIssue: { onEnter: planWorkItem },\n manual: { onEnter: planWorkItem },\n },\n },\n review: { review: { pullRequest: { onEnter: reviewPullRequest } } },\n tools: { submit_plan: { onResult: advanceApprovedPlan } },\n github: {\n issueOpened: { onEvent: issueOpened },\n issueEdited: { onEvent: retriageGithubIssue },\n issueClosed: { onEvent: issueClosed },\n issueCommentCreated: { onEvent: retriageGithubIssue },\n issueCommentEdited: { onEvent: retriageGithubIssue },\n issueCommentDeleted: { onEvent: retriageGithubIssue },\n pullRequestOpened: { onEvent: pullRequestOpened },\n pullRequestReviewRequested: { onEvent: reReviewRequestedPullRequest },\n pullRequestMerged: { onEvent: pullRequestMerged },\n pullRequestClosed: { onEvent: pullRequestClosed },\n },\n linear: { issueObserved: { onEvent: linearIssueObserved }, issueClosed: { onEvent: linearIssueClosed } },\n};\n\nfunction mergeBoardRules(\n base: FactoryBoardRules | undefined,\n overrides: FactoryBoardRules | undefined,\n): FactoryBoardRules {\n const result: FactoryBoardRules = {};\n const stages = new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]) as Set<FactoryRuleStage>;\n for (const stage of stages) {\n const baseSources = base?.[stage];\n const overrideSources = overrides?.[stage];\n const sources = new Set([\n ...Object.keys(baseSources ?? {}),\n ...Object.keys(overrideSources ?? {}),\n ]) as Set<FactoryRuleSource>;\n const mergedSources: Partial<Record<FactoryRuleSource, FactoryBoardRuleLeaf>> = {};\n for (const source of sources) {\n const baseLeaf = baseSources?.[source];\n const overrideLeaf = overrideSources?.[source];\n mergedSources[source] = {\n ...(baseLeaf?.onEnter ? { onEnter: baseLeaf.onEnter } : {}),\n ...(baseLeaf?.onExit ? { onExit: baseLeaf.onExit } : {}),\n ...(overrideLeaf && 'onEnter' in overrideLeaf ? { onEnter: overrideLeaf.onEnter } : {}),\n ...(overrideLeaf && 'onExit' in overrideLeaf ? { onExit: overrideLeaf.onExit } : {}),\n };\n }\n result[stage] = mergedSources;\n }\n return result;\n}\n\nfunction mergeToolRules(\n base: Record<string, FactoryToolRuleLeaf> | undefined,\n overrides: Record<string, FactoryToolRuleLeaf> | undefined,\n): Record<string, FactoryToolRuleLeaf> {\n const result: Record<string, FactoryToolRuleLeaf> = {};\n for (const name of new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})])) {\n const baseLeaf = base?.[name];\n const overrideLeaf = overrides?.[name];\n result[name] = {\n ...(baseLeaf?.onResult ? { onResult: baseLeaf.onResult } : {}),\n ...(overrideLeaf && 'onResult' in overrideLeaf ? { onResult: overrideLeaf.onResult } : {}),\n };\n }\n return result;\n}\n\nfunction mergeGithubRules(\n base: FactoryRulesOverrides['github'],\n overrides: FactoryRulesOverrides['github'],\n): NonNullable<FactoryRulesOverrides['github']> {\n const result: Partial<Record<FactoryGithubEventName, FactoryGithubRuleLeaf>> = {};\n const events = new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]) as Set<FactoryGithubEventName>;\n for (const event of events) {\n const baseLeaf = base?.[event];\n const overrideLeaf = overrides?.[event];\n result[event] = {\n ...(baseLeaf?.onEvent ? { onEvent: baseLeaf.onEvent } : {}),\n ...(overrideLeaf && 'onEvent' in overrideLeaf ? { onEvent: overrideLeaf.onEvent } : {}),\n };\n }\n return result;\n}\n\nfunction mergeLinearRules(\n base: FactoryRulesOverrides['linear'],\n overrides: FactoryRulesOverrides['linear'],\n): NonNullable<FactoryRulesOverrides['linear']> {\n const result: Partial<Record<FactoryLinearEventName, FactoryLinearRuleLeaf>> = {};\n const events = new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]) as Set<FactoryLinearEventName>;\n for (const event of events) {\n const baseLeaf = base?.[event];\n const overrideLeaf = overrides?.[event];\n result[event] = {\n ...(baseLeaf?.onEvent ? { onEvent: baseLeaf.onEvent } : {}),\n ...(overrideLeaf && 'onEvent' in overrideLeaf ? { onEvent: overrideLeaf.onEvent } : {}),\n };\n }\n return result;\n}\n\nexport function mergeFactoryRuleOverrides(\n base: FactoryRulesOverrides,\n overrides: FactoryRulesOverrides = {},\n): Omit<FactoryRules, 'version'> {\n return {\n work: mergeBoardRules(base.work, overrides.work),\n review: mergeBoardRules(base.review, overrides.review),\n tools: mergeToolRules(base.tools, overrides.tools),\n github: mergeGithubRules(base.github, overrides.github),\n linear: mergeLinearRules(base.linear, overrides.linear),\n };\n}\n\nexport function defaultFactoryRules(input: { version: string; overrides?: FactoryRulesOverrides }): FactoryRules {\n if (typeof input?.version !== 'string' || input.version.trim().length === 0) {\n throw new FactoryRuleValidationError('Factory rule version is required.');\n }\n\n const rules: FactoryRules = {\n version: input.version.trim(),\n ...mergeFactoryRuleOverrides(BUILT_IN_DEFAULTS, input.overrides),\n };\n assertFactoryRules(rules);\n return rules;\n}\n\nexport function builtInFactoryRules(): FactoryRules {\n return defaultFactoryRules({ version: DEFAULT_FACTORY_RULE_VERSION });\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start-coordinator.d.ts","sourceRoot":"","sources":["../../src/rules/start-coordinator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"start-coordinator.d.ts","sourceRoot":"","sources":["../../src/rules/start-coordinator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAI9D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4CAA4C,CAAC;AACxF,OAAO,KAAK,EAAwB,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AAClH,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AACnG,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE5E,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1G,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE;QACR,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,mBAAmB,CAAC;KAC5B,CAAC;IACF,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,qBAAa,2BAA4B,SAAQ,KAAK;IACpD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,uBAAuB,EAAE;QAAE,MAAM,EAAE,UAAU,CAAA;KAAE,CAAC,CAAC;gBAE9D,MAAM,EAAE,OAAO,CAAC,uBAAuB,EAAE;QAAE,MAAM,EAAE,UAAU,CAAA;KAAE,CAAC;CAK7E;AAED,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IAClE,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,KAAK,iBAAiB,GAAG,eAAe,CAAC,eAAe,CAAC,CAAC;AAqD1D,qBAAa,uBAAuB;;gBAQhC,UAAU,EAAE,iBAAiB,EAC7B,OAAO,EAAE,gBAAgB,EACzB,iBAAiB,CAAC,EAAE,IAAI,CAAC,wBAAwB,EAAE,YAAY,CAAC,EAChE,aAAa,CAAC,EAAE,0BAA0B,EAC1C,cAAc,CAAC,EAAE,qBAAqB;IASlC,OAAO,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,0BAA0B,CAAC;CAwGjF"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hydrateFactorySession } from "../session/factory-session.js";
|
|
1
2
|
import { formatSkillActivation } from "@mastra/core/workspace";
|
|
2
3
|
import { RequestContext } from "@mastra/core/request-context";
|
|
3
4
|
//#region src/rules/start-coordinator.ts
|
|
@@ -51,16 +52,6 @@ async function configureThread(session, request) {
|
|
|
51
52
|
})));
|
|
52
53
|
return threadId;
|
|
53
54
|
}
|
|
54
|
-
async function applyMemorySettings(session, record) {
|
|
55
|
-
if (record?.observerModelId) await session.om.observer.switchModel({ modelId: record.observerModelId });
|
|
56
|
-
if (record?.reflectorModelId) await session.om.reflector.switchModel({ modelId: record.reflectorModelId });
|
|
57
|
-
const state = {
|
|
58
|
-
...record?.observationThreshold != null ? { observationThreshold: record.observationThreshold } : {},
|
|
59
|
-
...record?.reflectionThreshold != null ? { reflectionThreshold: record.reflectionThreshold } : {},
|
|
60
|
-
...record?.observeAttachments != null ? { observeAttachments: record.observeAttachments } : {}
|
|
61
|
-
};
|
|
62
|
-
if (Object.keys(state).length > 0) await session.state.set(state);
|
|
63
|
-
}
|
|
64
55
|
var FactoryStartCoordinator = class {
|
|
65
56
|
#controller;
|
|
66
57
|
#storage;
|
|
@@ -105,22 +96,12 @@ var FactoryStartCoordinator = class {
|
|
|
105
96
|
...baseRef ? { baseRef } : {}
|
|
106
97
|
} : {}
|
|
107
98
|
});
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
console.warn("[Factory Start] Failed to apply observational-memory settings", { error: error instanceof Error ? error.message : String(error) });
|
|
115
|
-
}
|
|
116
|
-
if (request.defaultModelId) try {
|
|
117
|
-
await session.model.switch({ modelId: request.defaultModelId });
|
|
118
|
-
} catch (error) {
|
|
119
|
-
console.warn("[Factory Start] Failed to apply factory default model", {
|
|
120
|
-
modelId: request.defaultModelId,
|
|
121
|
-
error: error instanceof Error ? error.message : String(error)
|
|
122
|
-
});
|
|
123
|
-
}
|
|
99
|
+
await hydrateFactorySession(session, {
|
|
100
|
+
orgId: request.orgId,
|
|
101
|
+
userId: request.userId,
|
|
102
|
+
defaultModelId: request.defaultModelId,
|
|
103
|
+
memorySettings: this.#memorySettings
|
|
104
|
+
});
|
|
124
105
|
const threadId = await configureThread(session, request);
|
|
125
106
|
const kickoffMessage = await resolveKickoffMessage(session, request.invocation);
|
|
126
107
|
const prepared = await storage.prepareRunStart({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start-coordinator.js","names":["#controller","#storage","#transitionService","#sourceControl","#memorySettings"],"sources":["../../src/rules/start-coordinator.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport { RequestContext } from '@mastra/core/request-context';\nimport { formatSkillActivation } from '@mastra/core/workspace';\n\nimport type { MemorySettingsRecord, MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { SourceControlSession, SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport type { CreateWorkItemInput, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { FactoryTransitionService } from './transition-service.js';\nimport type { FactoryRuleStage, FactoryTransitionResult } from './types.js';\n\nexport interface FactoryStartRequest {\n orgId: string;\n userId: string;\n factoryProjectId: string;\n sessionId: string;\n threadTitle: string;\n threadTags?: Record<string, string>;\n kickoffKey: string;\n invocation?: { type: 'prompt'; prompt: string } | { type: 'skill'; skillName: string; arguments: string };\n destinationStage: FactoryRuleStage;\n defaultModelId?: string;\n workItem: {\n id?: string;\n role: string;\n input: CreateWorkItemInput;\n };\n requestContext?: RequestContext;\n}\n\nexport class FactoryStartTransitionError extends Error {\n readonly result: Extract<FactoryTransitionResult, { status: 'rejected' }>;\n\n constructor(result: Extract<FactoryTransitionResult, { status: 'rejected' }>) {\n super(result.reason);\n this.name = 'FactoryStartTransitionError';\n this.result = result;\n }\n}\n\nexport interface FactoryStartPreparedResult {\n workItemId: string;\n bindingId: string;\n threadId: string;\n resourceId: string;\n sessionId: string;\n branch: string;\n revision: number;\n kickoffStatus: 'pending' | 'leased' | 'retry' | 'sent' | 'failed';\n replayed: boolean;\n}\n\ntype FactoryController = AgentController<MastraCodeState>;\ntype FactorySession = Awaited<ReturnType<FactoryController['createSession']>>;\n\nfunction escapeSkillBoundary(value: string): string {\n return value.replaceAll('</skill>', '</skill>');\n}\n\nasync function resolveKickoffMessage(\n session: FactorySession,\n invocation: FactoryStartRequest['invocation'],\n): Promise<string | null> {\n if (!invocation) return null;\n if (invocation.type === 'prompt') return invocation.prompt;\n\n const skills = session.getWorkspace().skills;\n await skills?.maybeRefresh();\n const skill = await skills?.get(invocation.skillName);\n if (!skill || skill['user-invocable'] === false) {\n throw new Error(`Skill not found: ${invocation.skillName}.`);\n }\n const args = invocation.arguments.trim();\n const content = `${formatSkillActivation(skill)}${args ? `\\n\\nARGUMENTS: ${args}` : ''}`.trim();\n return `<skill name=\"${skill.name}\">\\n${escapeSkillBoundary(content)}\\n</skill>`;\n}\n\nasync function resolveSourceSession(\n storage: SourceControlStorageHandle,\n request: FactoryStartRequest,\n): Promise<SourceControlSession> {\n const session = await storage.sessions.getBySessionId(request.sessionId);\n if (!session || session.orgId !== request.orgId || session.userId !== request.userId) {\n throw new Error('Factory session not found');\n }\n const projectRepository = await storage.projectRepositories.get({\n orgId: request.orgId,\n id: session.projectRepositoryId,\n });\n if (!projectRepository) throw new Error('Factory session repository not found');\n const connection = await storage.connections.get({ orgId: request.orgId, id: projectRepository.connectionId });\n if (!connection || connection.factoryProjectId !== request.factoryProjectId) {\n throw new Error('Factory session does not belong to this project');\n }\n return session;\n}\n\nasync function configureThread(session: FactorySession, request: FactoryStartRequest): Promise<string> {\n const threadId = session.thread.requireId();\n await session.thread.rename({ title: request.threadTitle });\n const settings = { ...(request.threadTags ?? {}), factorySessionId: request.sessionId };\n await Promise.all(Object.entries(settings).map(([key, value]) => session.thread.setSetting({ key, value })));\n return threadId;\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 class FactoryStartCoordinator {\n readonly #controller: FactoryController;\n readonly #storage: WorkItemsStorage;\n readonly #transitionService?: Pick<FactoryTransitionService, 'transition'>;\n readonly #sourceControl?: SourceControlStorageHandle;\n readonly #memorySettings?: MemorySettingsStorage;\n\n constructor(\n controller: FactoryController,\n storage: WorkItemsStorage,\n transitionService?: Pick<FactoryTransitionService, 'transition'>,\n sourceControl?: SourceControlStorageHandle,\n memorySettings?: MemorySettingsStorage,\n ) {\n this.#controller = controller;\n this.#storage = storage;\n this.#transitionService = transitionService;\n this.#sourceControl = sourceControl;\n this.#memorySettings = memorySettings;\n }\n\n async prepare(request: FactoryStartRequest): Promise<FactoryStartPreparedResult> {\n const storage = this.#storage;\n if (!this.#sourceControl) throw new Error('Factory source control storage is unavailable');\n const sourceSession = await resolveSourceSession(this.#sourceControl, request);\n const requestContext = request.requestContext ?? new RequestContext();\n if (!request.requestContext) {\n requestContext.set('user', { workosId: request.userId, organizationId: request.orgId });\n }\n // Sessions kicked off against third-party content (a PR under review, or\n // any pull-request-sourced work item) get `untrustedCheckout` so the SDK\n // never ingests the checkout's AGENTS.md/CLAUDE.md into the system prompt\n // or reminders — those files are attacker-writable in a PR branch.\n const untrustedCheckout =\n request.workItem.input.externalSource?.type === 'pull-request' ||\n (request.invocation?.type === 'skill' && request.invocation.skillName === 'factory-review');\n // The trusted ref the SDK may serve project instruction files from on an\n // untrusted checkout (the PR's base branch). Prefer the session record's\n // base branch; fall back to the intake metadata captured from the PR.\n const metadataBaseBranch = request.workItem.input.metadata?.baseBranch;\n const baseRef =\n (sourceSession.baseBranch || undefined) ??\n (typeof metadataBaseBranch === 'string' && metadataBaseBranch ? metadataBaseBranch : undefined);\n const sessionTags = {\n factoryProjectId: request.factoryProjectId,\n projectRepositoryId: sourceSession.projectRepositoryId,\n };\n const session = await this.#controller.createSession({\n id: sourceSession.sessionId,\n ownerId: request.userId,\n resourceId: sourceSession.sessionId,\n threadId: sourceSession.sessionId,\n requestContext,\n tags: sessionTags,\n });\n // Bound-agent authority gates (the transition tool, the factory-phase\n // processor, workspace token selection) resolve the session address from\n // controller state. Seed it server-side — `tags` covers fresh creation,\n // the explicit setState covers get-or-create returning a session another\n // caller created without them — so autonomous runs never depend on a\n // browser connecting to populate the state. `untrustedCheckout` is a\n // boolean so it rides only on state (tags are string-valued).\n await session.state.set({\n ...sessionTags,\n ...(untrustedCheckout ? { untrustedCheckout: true, ...(baseRef ? { baseRef } : {}) } : {}),\n });\n if (this.#memorySettings) {\n try {\n const record = await this.#memorySettings.get({ orgId: request.orgId, userId: request.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 (request.defaultModelId) {\n try {\n await session.model.switch({ modelId: request.defaultModelId });\n } catch (error) {\n console.warn('[Factory Start] Failed to apply factory default model', {\n modelId: request.defaultModelId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n const threadId = await configureThread(session, request);\n const kickoffMessage = await resolveKickoffMessage(session, request.invocation);\n const prepared = await storage.prepareRunStart({\n orgId: request.orgId,\n userId: request.userId,\n factoryProjectId: request.factoryProjectId,\n workItem: { id: request.workItem.id, input: request.workItem.input },\n role: request.workItem.role,\n session: { sessionId: sourceSession.sessionId, branch: sourceSession.branch, threadId },\n resourceId: sourceSession.sessionId,\n kickoffKey: request.kickoffKey,\n kickoffMessage,\n });\n await session.thread.setSetting({ key: 'factoryWorkItemId', value: prepared.item.id });\n\n let revision = prepared.item.revision;\n if (prepared.item.stages.length !== 1 || prepared.item.stages[0] !== request.destinationStage) {\n if (!this.#transitionService) throw new Error('Factory transition service is unavailable.');\n const transition = await this.#transitionService.transition({\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: prepared.item.id,\n board: prepared.item.externalSource?.type === 'pull-request' ? 'review' : 'work',\n stage: request.destinationStage,\n expectedRevision: prepared.item.revision,\n actor: { type: 'human', id: request.userId },\n ingress: { type: 'human', identity: `start:${request.kickoffKey}:transition` },\n cause: 'run_start',\n });\n if (transition.status === 'rejected') {\n await storage.markPendingStart(prepared.binding.id, 'failed', transition.reason);\n throw new FactoryStartTransitionError(transition);\n }\n revision = transition.revision;\n }\n\n if (kickoffMessage === null) {\n await storage.markPendingStart(prepared.binding.id, 'sent');\n prepared.pendingStart.status = 'sent';\n }\n\n return {\n workItemId: prepared.item.id,\n bindingId: prepared.binding.id,\n threadId,\n resourceId: sourceSession.sessionId,\n sessionId: sourceSession.sessionId,\n branch: sourceSession.branch,\n revision,\n kickoffStatus: prepared.pendingStart.status,\n replayed: prepared.replayed,\n };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"start-coordinator.js","names":["#controller","#storage","#transitionService","#sourceControl","#memorySettings"],"sources":["../../src/rules/start-coordinator.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport { RequestContext } from '@mastra/core/request-context';\nimport { formatSkillActivation } from '@mastra/core/workspace';\n\nimport { hydrateFactorySession } from '../session/factory-session.js';\nimport type { MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { SourceControlSession, SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport type { CreateWorkItemInput, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { FactoryTransitionService } from './transition-service.js';\nimport type { FactoryRuleStage, FactoryTransitionResult } from './types.js';\n\nexport interface FactoryStartRequest {\n orgId: string;\n userId: string;\n factoryProjectId: string;\n sessionId: string;\n threadTitle: string;\n threadTags?: Record<string, string>;\n kickoffKey: string;\n invocation?: { type: 'prompt'; prompt: string } | { type: 'skill'; skillName: string; arguments: string };\n destinationStage: FactoryRuleStage;\n defaultModelId?: string;\n workItem: {\n id?: string;\n role: string;\n input: CreateWorkItemInput;\n };\n requestContext?: RequestContext;\n}\n\nexport class FactoryStartTransitionError extends Error {\n readonly result: Extract<FactoryTransitionResult, { status: 'rejected' }>;\n\n constructor(result: Extract<FactoryTransitionResult, { status: 'rejected' }>) {\n super(result.reason);\n this.name = 'FactoryStartTransitionError';\n this.result = result;\n }\n}\n\nexport interface FactoryStartPreparedResult {\n workItemId: string;\n bindingId: string;\n threadId: string;\n resourceId: string;\n sessionId: string;\n branch: string;\n revision: number;\n kickoffStatus: 'pending' | 'leased' | 'retry' | 'sent' | 'failed';\n replayed: boolean;\n}\n\ntype FactoryController = AgentController<MastraCodeState>;\ntype FactorySession = Awaited<ReturnType<FactoryController['createSession']>>;\n\nfunction escapeSkillBoundary(value: string): string {\n return value.replaceAll('</skill>', '</skill>');\n}\n\nasync function resolveKickoffMessage(\n session: FactorySession,\n invocation: FactoryStartRequest['invocation'],\n): Promise<string | null> {\n if (!invocation) return null;\n if (invocation.type === 'prompt') return invocation.prompt;\n\n const skills = session.getWorkspace().skills;\n await skills?.maybeRefresh();\n const skill = await skills?.get(invocation.skillName);\n if (!skill || skill['user-invocable'] === false) {\n throw new Error(`Skill not found: ${invocation.skillName}.`);\n }\n const args = invocation.arguments.trim();\n const content = `${formatSkillActivation(skill)}${args ? `\\n\\nARGUMENTS: ${args}` : ''}`.trim();\n return `<skill name=\"${skill.name}\">\\n${escapeSkillBoundary(content)}\\n</skill>`;\n}\n\nasync function resolveSourceSession(\n storage: SourceControlStorageHandle,\n request: FactoryStartRequest,\n): Promise<SourceControlSession> {\n const session = await storage.sessions.getBySessionId(request.sessionId);\n if (!session || session.orgId !== request.orgId || session.userId !== request.userId) {\n throw new Error('Factory session not found');\n }\n const projectRepository = await storage.projectRepositories.get({\n orgId: request.orgId,\n id: session.projectRepositoryId,\n });\n if (!projectRepository) throw new Error('Factory session repository not found');\n const connection = await storage.connections.get({ orgId: request.orgId, id: projectRepository.connectionId });\n if (!connection || connection.factoryProjectId !== request.factoryProjectId) {\n throw new Error('Factory session does not belong to this project');\n }\n return session;\n}\n\nasync function configureThread(session: FactorySession, request: FactoryStartRequest): Promise<string> {\n const threadId = session.thread.requireId();\n await session.thread.rename({ title: request.threadTitle });\n const settings = { ...(request.threadTags ?? {}), factorySessionId: request.sessionId };\n await Promise.all(Object.entries(settings).map(([key, value]) => session.thread.setSetting({ key, value })));\n return threadId;\n}\n\nexport class FactoryStartCoordinator {\n readonly #controller: FactoryController;\n readonly #storage: WorkItemsStorage;\n readonly #transitionService?: Pick<FactoryTransitionService, 'transition'>;\n readonly #sourceControl?: SourceControlStorageHandle;\n readonly #memorySettings?: MemorySettingsStorage;\n\n constructor(\n controller: FactoryController,\n storage: WorkItemsStorage,\n transitionService?: Pick<FactoryTransitionService, 'transition'>,\n sourceControl?: SourceControlStorageHandle,\n memorySettings?: MemorySettingsStorage,\n ) {\n this.#controller = controller;\n this.#storage = storage;\n this.#transitionService = transitionService;\n this.#sourceControl = sourceControl;\n this.#memorySettings = memorySettings;\n }\n\n async prepare(request: FactoryStartRequest): Promise<FactoryStartPreparedResult> {\n const storage = this.#storage;\n if (!this.#sourceControl) throw new Error('Factory source control storage is unavailable');\n const sourceSession = await resolveSourceSession(this.#sourceControl, request);\n const requestContext = request.requestContext ?? new RequestContext();\n if (!request.requestContext) {\n requestContext.set('user', { workosId: request.userId, organizationId: request.orgId });\n }\n // Sessions kicked off against third-party content (a PR under review, or\n // any pull-request-sourced work item) get `untrustedCheckout` so the SDK\n // never ingests the checkout's AGENTS.md/CLAUDE.md into the system prompt\n // or reminders — those files are attacker-writable in a PR branch.\n const untrustedCheckout =\n request.workItem.input.externalSource?.type === 'pull-request' ||\n (request.invocation?.type === 'skill' && request.invocation.skillName === 'factory-review');\n // The trusted ref the SDK may serve project instruction files from on an\n // untrusted checkout (the PR's base branch). Prefer the session record's\n // base branch; fall back to the intake metadata captured from the PR.\n const metadataBaseBranch = request.workItem.input.metadata?.baseBranch;\n const baseRef =\n (sourceSession.baseBranch || undefined) ??\n (typeof metadataBaseBranch === 'string' && metadataBaseBranch ? metadataBaseBranch : undefined);\n const sessionTags = {\n factoryProjectId: request.factoryProjectId,\n projectRepositoryId: sourceSession.projectRepositoryId,\n };\n const session = await this.#controller.createSession({\n id: sourceSession.sessionId,\n ownerId: request.userId,\n resourceId: sourceSession.sessionId,\n threadId: sourceSession.sessionId,\n requestContext,\n tags: sessionTags,\n });\n // Bound-agent authority gates (the transition tool, the factory-phase\n // processor, workspace token selection) resolve the session address from\n // controller state. Seed it server-side — `tags` covers fresh creation,\n // the explicit setState covers get-or-create returning a session another\n // caller created without them — so autonomous runs never depend on a\n // browser connecting to populate the state. `untrustedCheckout` is a\n // boolean so it rides only on state (tags are string-valued).\n await session.state.set({\n ...sessionTags,\n ...(untrustedCheckout ? { untrustedCheckout: true, ...(baseRef ? { baseRef } : {}) } : {}),\n });\n await hydrateFactorySession(session, {\n orgId: request.orgId,\n userId: request.userId,\n defaultModelId: request.defaultModelId,\n memorySettings: this.#memorySettings,\n });\n const threadId = await configureThread(session, request);\n const kickoffMessage = await resolveKickoffMessage(session, request.invocation);\n const prepared = await storage.prepareRunStart({\n orgId: request.orgId,\n userId: request.userId,\n factoryProjectId: request.factoryProjectId,\n workItem: { id: request.workItem.id, input: request.workItem.input },\n role: request.workItem.role,\n session: { sessionId: sourceSession.sessionId, branch: sourceSession.branch, threadId },\n resourceId: sourceSession.sessionId,\n kickoffKey: request.kickoffKey,\n kickoffMessage,\n });\n await session.thread.setSetting({ key: 'factoryWorkItemId', value: prepared.item.id });\n\n let revision = prepared.item.revision;\n if (prepared.item.stages.length !== 1 || prepared.item.stages[0] !== request.destinationStage) {\n if (!this.#transitionService) throw new Error('Factory transition service is unavailable.');\n const transition = await this.#transitionService.transition({\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: prepared.item.id,\n board: prepared.item.externalSource?.type === 'pull-request' ? 'review' : 'work',\n stage: request.destinationStage,\n expectedRevision: prepared.item.revision,\n actor: { type: 'human', id: request.userId },\n ingress: { type: 'human', identity: `start:${request.kickoffKey}:transition` },\n cause: 'run_start',\n });\n if (transition.status === 'rejected') {\n await storage.markPendingStart(prepared.binding.id, 'failed', transition.reason);\n throw new FactoryStartTransitionError(transition);\n }\n revision = transition.revision;\n }\n\n if (kickoffMessage === null) {\n await storage.markPendingStart(prepared.binding.id, 'sent');\n prepared.pendingStart.status = 'sent';\n }\n\n return {\n workItemId: prepared.item.id,\n bindingId: prepared.binding.id,\n threadId,\n resourceId: sourceSession.sessionId,\n sessionId: sourceSession.sessionId,\n branch: sourceSession.branch,\n revision,\n kickoffStatus: prepared.pendingStart.status,\n replayed: prepared.replayed,\n };\n }\n}\n"],"mappings":";;;;AA+BA,IAAa,8BAAb,cAAiD,MAAM;CACrD;CAEA,YAAY,QAAkE;EAC5E,MAAM,OAAO,MAAM;EACnB,KAAK,OAAO;EACZ,KAAK,SAAS;CAChB;AACF;AAiBA,SAAS,oBAAoB,OAAuB;CAClD,OAAO,MAAM,WAAW,YAAY,gBAAgB;AACtD;AAEA,eAAe,sBACb,SACA,YACwB;CACxB,IAAI,CAAC,YAAY,OAAO;CACxB,IAAI,WAAW,SAAS,UAAU,OAAO,WAAW;CAEpD,MAAM,SAAS,QAAQ,aAAa,CAAC,CAAC;CACtC,MAAM,QAAQ,aAAa;CAC3B,MAAM,QAAQ,MAAM,QAAQ,IAAI,WAAW,SAAS;CACpD,IAAI,CAAC,SAAS,MAAM,sBAAsB,OACxC,MAAM,IAAI,MAAM,oBAAoB,WAAW,UAAU,EAAE;CAE7D,MAAM,OAAO,WAAW,UAAU,KAAK;CACvC,MAAM,UAAU,GAAG,sBAAsB,KAAK,IAAI,OAAO,kBAAkB,SAAS,KAAK,KAAK;CAC9F,OAAO,gBAAgB,MAAM,KAAK,MAAM,oBAAoB,OAAO,EAAE;AACvE;AAEA,eAAe,qBACb,SACA,SAC+B;CAC/B,MAAM,UAAU,MAAM,QAAQ,SAAS,eAAe,QAAQ,SAAS;CACvE,IAAI,CAAC,WAAW,QAAQ,UAAU,QAAQ,SAAS,QAAQ,WAAW,QAAQ,QAC5E,MAAM,IAAI,MAAM,2BAA2B;CAE7C,MAAM,oBAAoB,MAAM,QAAQ,oBAAoB,IAAI;EAC9D,OAAO,QAAQ;EACf,IAAI,QAAQ;CACd,CAAC;CACD,IAAI,CAAC,mBAAmB,MAAM,IAAI,MAAM,sCAAsC;CAC9E,MAAM,aAAa,MAAM,QAAQ,YAAY,IAAI;EAAE,OAAO,QAAQ;EAAO,IAAI,kBAAkB;CAAa,CAAC;CAC7G,IAAI,CAAC,cAAc,WAAW,qBAAqB,QAAQ,kBACzD,MAAM,IAAI,MAAM,iDAAiD;CAEnE,OAAO;AACT;AAEA,eAAe,gBAAgB,SAAyB,SAA+C;CACrG,MAAM,WAAW,QAAQ,OAAO,UAAU;CAC1C,MAAM,QAAQ,OAAO,OAAO,EAAE,OAAO,QAAQ,YAAY,CAAC;CAC1D,MAAM,WAAW;EAAE,GAAI,QAAQ,cAAc,CAAC;EAAI,kBAAkB,QAAQ;CAAU;CACtF,MAAM,QAAQ,IAAI,OAAO,QAAQ,QAAQ,CAAC,CAAC,KAAK,CAAC,KAAK,WAAW,QAAQ,OAAO,WAAW;EAAE;EAAK;CAAM,CAAC,CAAC,CAAC;CAC3G,OAAO;AACT;AAEA,IAAa,0BAAb,MAAqC;CACnC;CACA;CACA;CACA;CACA;CAEA,YACE,YACA,SACA,mBACA,eACA,gBACA;EACA,KAAKA,cAAc;EACnB,KAAKC,WAAW;EAChB,KAAKC,qBAAqB;EAC1B,KAAKC,iBAAiB;EACtB,KAAKC,kBAAkB;CACzB;CAEA,MAAM,QAAQ,SAAmE;EAC/E,MAAM,UAAU,KAAKH;EACrB,IAAI,CAAC,KAAKE,gBAAgB,MAAM,IAAI,MAAM,+CAA+C;EACzF,MAAM,gBAAgB,MAAM,qBAAqB,KAAKA,gBAAgB,OAAO;EAC7E,MAAM,iBAAiB,QAAQ,kBAAkB,IAAI,eAAe;EACpE,IAAI,CAAC,QAAQ,gBACX,eAAe,IAAI,QAAQ;GAAE,UAAU,QAAQ;GAAQ,gBAAgB,QAAQ;EAAM,CAAC;EAMxF,MAAM,oBACJ,QAAQ,SAAS,MAAM,gBAAgB,SAAS,kBAC/C,QAAQ,YAAY,SAAS,WAAW,QAAQ,WAAW,cAAc;EAI5E,MAAM,qBAAqB,QAAQ,SAAS,MAAM,UAAU;EAC5D,MAAM,WACH,cAAc,cAAc,KAAA,OAC5B,OAAO,uBAAuB,YAAY,qBAAqB,qBAAqB,KAAA;EACvF,MAAM,cAAc;GAClB,kBAAkB,QAAQ;GAC1B,qBAAqB,cAAc;EACrC;EACA,MAAM,UAAU,MAAM,KAAKH,YAAY,cAAc;GACnD,IAAI,cAAc;GAClB,SAAS,QAAQ;GACjB,YAAY,cAAc;GAC1B,UAAU,cAAc;GACxB;GACA,MAAM;EACR,CAAC;EAQD,MAAM,QAAQ,MAAM,IAAI;GACtB,GAAG;GACH,GAAI,oBAAoB;IAAE,mBAAmB;IAAM,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;GAAG,IAAI,CAAC;EAC1F,CAAC;EACD,MAAM,sBAAsB,SAAS;GACnC,OAAO,QAAQ;GACf,QAAQ,QAAQ;GAChB,gBAAgB,QAAQ;GACxB,gBAAgB,KAAKI;EACvB,CAAC;EACD,MAAM,WAAW,MAAM,gBAAgB,SAAS,OAAO;EACvD,MAAM,iBAAiB,MAAM,sBAAsB,SAAS,QAAQ,UAAU;EAC9E,MAAM,WAAW,MAAM,QAAQ,gBAAgB;GAC7C,OAAO,QAAQ;GACf,QAAQ,QAAQ;GAChB,kBAAkB,QAAQ;GAC1B,UAAU;IAAE,IAAI,QAAQ,SAAS;IAAI,OAAO,QAAQ,SAAS;GAAM;GACnE,MAAM,QAAQ,SAAS;GACvB,SAAS;IAAE,WAAW,cAAc;IAAW,QAAQ,cAAc;IAAQ;GAAS;GACtF,YAAY,cAAc;GAC1B,YAAY,QAAQ;GACpB;EACF,CAAC;EACD,MAAM,QAAQ,OAAO,WAAW;GAAE,KAAK;GAAqB,OAAO,SAAS,KAAK;EAAG,CAAC;EAErF,IAAI,WAAW,SAAS,KAAK;EAC7B,IAAI,SAAS,KAAK,OAAO,WAAW,KAAK,SAAS,KAAK,OAAO,OAAO,QAAQ,kBAAkB;GAC7F,IAAI,CAAC,KAAKF,oBAAoB,MAAM,IAAI,MAAM,4CAA4C;GAC1F,MAAM,aAAa,MAAM,KAAKA,mBAAmB,WAAW;IAC1D,OAAO,QAAQ;IACf,kBAAkB,QAAQ;IAC1B,YAAY,SAAS,KAAK;IAC1B,OAAO,SAAS,KAAK,gBAAgB,SAAS,iBAAiB,WAAW;IAC1E,OAAO,QAAQ;IACf,kBAAkB,SAAS,KAAK;IAChC,OAAO;KAAE,MAAM;KAAS,IAAI,QAAQ;IAAO;IAC3C,SAAS;KAAE,MAAM;KAAS,UAAU,SAAS,QAAQ,WAAW;IAAa;IAC7E,OAAO;GACT,CAAC;GACD,IAAI,WAAW,WAAW,YAAY;IACpC,MAAM,QAAQ,iBAAiB,SAAS,QAAQ,IAAI,UAAU,WAAW,MAAM;IAC/E,MAAM,IAAI,4BAA4B,UAAU;GAClD;GACA,WAAW,WAAW;EACxB;EAEA,IAAI,mBAAmB,MAAM;GAC3B,MAAM,QAAQ,iBAAiB,SAAS,QAAQ,IAAI,MAAM;GAC1D,SAAS,aAAa,SAAS;EACjC;EAEA,OAAO;GACL,YAAY,SAAS,KAAK;GAC1B,WAAW,SAAS,QAAQ;GAC5B;GACA,YAAY,cAAc;GAC1B,WAAW,cAAc;GACzB,QAAQ,cAAc;GACtB;GACA,eAAe,SAAS,aAAa;GACrC,UAAU,SAAS;EACrB;CACF;AACF"}
|
package/dist/rules/tools.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { RequestContext } from '@mastra/core/request-context';
|
|
2
2
|
import type { IntegrationTools } from '../integrations/base.js';
|
|
3
3
|
import type { WorkItemsStorage } from '../storage/domains/work-items/base.js';
|
|
4
|
+
import type { FactorySessionSourceLookup } from './binding-context.js';
|
|
4
5
|
import type { FactoryTransitionService } from './transition-service.js';
|
|
5
6
|
export declare function createFactoryTransitionTools(options: {
|
|
6
7
|
requestContext: RequestContext;
|
|
7
8
|
storage: WorkItemsStorage;
|
|
8
9
|
transitionService: Pick<FactoryTransitionService, 'transition'>;
|
|
10
|
+
sessions?: FactorySessionSourceLookup;
|
|
9
11
|
}): Promise<IntegrationTools>;
|
|
10
12
|
//# sourceMappingURL=tools.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/rules/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAInE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/rules/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAInE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAEvE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AA0BxE,wBAAsB,4BAA4B,CAAC,OAAO,EAAE;IAC1D,cAAc,EAAE,cAAc,CAAC;IAC/B,OAAO,EAAE,gBAAgB,CAAC;IAC1B,iBAAiB,EAAE,IAAI,CAAC,wBAAwB,EAAE,YAAY,CAAC,CAAC;IAChE,QAAQ,CAAC,EAAE,0BAA0B,CAAC;CACvC,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAiD5B"}
|
package/dist/rules/tools.js
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
import { FACTORY_RULE_STAGES } from "./types.js";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveFactorySessionAddress } from "./binding-context.js";
|
|
3
3
|
import { createTool } from "@mastra/core/tools";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
//#region src/rules/tools.ts
|
|
6
|
+
const MAX_RATIONALE_LENGTH = 1e3;
|
|
6
7
|
const transitionInputSchema = z.object({
|
|
7
8
|
stage: z.enum(FACTORY_RULE_STAGES),
|
|
8
9
|
expectedRevision: z.number().int().positive(),
|
|
9
|
-
rationale: z.string().trim().min(1).
|
|
10
|
+
rationale: z.string().trim().min(1).transform((value) => value.length <= MAX_RATIONALE_LENGTH ? value : `${value.slice(0, MAX_RATIONALE_LENGTH - 1)}…`)
|
|
10
11
|
}).strict();
|
|
11
12
|
function boardForSource(type) {
|
|
12
13
|
return type === "pull-request" ? "review" : "work";
|
|
13
14
|
}
|
|
14
15
|
async function createFactoryTransitionTools(options) {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
const resolution = await resolveFactorySessionAddress({
|
|
17
|
+
requestContext: options.requestContext,
|
|
18
|
+
storage: options.storage,
|
|
19
|
+
sessions: options.sessions
|
|
20
|
+
});
|
|
21
|
+
if (!resolution) return {};
|
|
22
|
+
const availableBinding = resolution.binding ?? await options.storage.findActiveRunBinding(resolution.address);
|
|
18
23
|
if (!availableBinding) return {};
|
|
19
24
|
return { factory_transition_work_item: createTool({
|
|
20
25
|
id: "factory_transition_work_item",
|
|
@@ -22,7 +27,11 @@ async function createFactoryTransitionTools(options) {
|
|
|
22
27
|
inputSchema: transitionInputSchema,
|
|
23
28
|
requireApproval: true,
|
|
24
29
|
execute: async ({ stage, expectedRevision, rationale }, execution) => {
|
|
25
|
-
const currentAddress =
|
|
30
|
+
const currentAddress = (await resolveFactorySessionAddress({
|
|
31
|
+
requestContext: execution.requestContext,
|
|
32
|
+
storage: options.storage,
|
|
33
|
+
sessions: options.sessions
|
|
34
|
+
}))?.address ?? null;
|
|
26
35
|
const toolCallId = execution.agent?.toolCallId;
|
|
27
36
|
if (!currentAddress || !toolCallId) throw new Error("Factory transitions require an authenticated bound agent tool call.");
|
|
28
37
|
const binding = await options.storage.findActiveRunBinding(currentAddress);
|
package/dist/rules/tools.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.js","names":[],"sources":["../../src/rules/tools.ts"],"sourcesContent":["import type { RequestContext } from '@mastra/core/request-context';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport type { IntegrationTools } from '../integrations/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport {
|
|
1
|
+
{"version":3,"file":"tools.js","names":[],"sources":["../../src/rules/tools.ts"],"sourcesContent":["import type { RequestContext } from '@mastra/core/request-context';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport type { IntegrationTools } from '../integrations/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { FactorySessionSourceLookup } from './binding-context.js';\nimport { resolveFactorySessionAddress } from './binding-context.js';\nimport type { FactoryTransitionService } from './transition-service.js';\nimport { FACTORY_RULE_STAGES } from './types.js';\nimport type { FactoryRuleBoard } from './types.js';\n\nconst MAX_RATIONALE_LENGTH = 1_000;\n\nconst transitionInputSchema = z\n .object({\n stage: z.enum(FACTORY_RULE_STAGES),\n expectedRevision: z.number().int().positive(),\n // Providers strip maxLength from the JSON schema and models can't count characters, so a\n // hard cap invites overshoot-retry loops at the end of every run. Accept and clamp instead.\n rationale: z\n .string()\n .trim()\n .min(1)\n .transform(value =>\n value.length <= MAX_RATIONALE_LENGTH ? value : `${value.slice(0, MAX_RATIONALE_LENGTH - 1)}…`,\n ),\n })\n .strict();\n\nfunction boardForSource(type: string | undefined): FactoryRuleBoard {\n return type === 'pull-request' ? 'review' : 'work';\n}\n\nexport async function createFactoryTransitionTools(options: {\n requestContext: RequestContext;\n storage: WorkItemsStorage;\n transitionService: Pick<FactoryTransitionService, 'transition'>;\n sessions?: FactorySessionSourceLookup;\n}): Promise<IntegrationTools> {\n const resolution = await resolveFactorySessionAddress({\n requestContext: options.requestContext,\n storage: options.storage,\n sessions: options.sessions,\n });\n if (!resolution) return {};\n const availableBinding = resolution.binding ?? (await options.storage.findActiveRunBinding(resolution.address));\n if (!availableBinding) return {};\n\n return {\n factory_transition_work_item: createTool({\n id: 'factory_transition_work_item',\n description:\n 'Request a governed stage transition for the Factory work item exactly bound to this thread. Use the current revision from the factory-phase signal and explain why the transition is appropriate.',\n inputSchema: transitionInputSchema,\n requireApproval: true,\n execute: async ({ stage, expectedRevision, rationale }, execution) => {\n const currentResolution = await resolveFactorySessionAddress({\n requestContext: execution.requestContext,\n storage: options.storage,\n sessions: options.sessions,\n });\n const currentAddress = currentResolution?.address ?? null;\n const toolCallId = execution.agent?.toolCallId;\n if (!currentAddress || !toolCallId) {\n throw new Error('Factory transitions require an authenticated bound agent tool call.');\n }\n const binding = await options.storage.findActiveRunBinding(currentAddress);\n if (!binding || binding.id !== availableBinding.id) {\n throw new Error('Factory agent binding is unavailable, revoked, or no longer matches this session.');\n }\n const item = await options.storage.get({ orgId: binding.orgId, id: binding.workItemId });\n if (!item) throw new Error('Bound Factory work item not found.');\n\n return options.transitionService.transition({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n workItemId: binding.workItemId,\n board: boardForSource(item.externalSource?.type),\n stage,\n expectedRevision,\n actor: { type: 'agent', bindingId: binding.id, role: binding.role },\n ingress: { type: 'agent', identity: `${binding.id}:${toolCallId}` },\n cause: rationale,\n });\n },\n }),\n };\n}\n"],"mappings":";;;;;AAYA,MAAM,uBAAuB;AAE7B,MAAM,wBAAwB,EAC3B,OAAO;CACN,OAAO,EAAE,KAAK,mBAAmB;CACjC,kBAAkB,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS;CAG5C,WAAW,EACR,OAAO,CAAC,CACR,KAAK,CAAC,CACN,IAAI,CAAC,CAAC,CACN,WAAU,UACT,MAAM,UAAU,uBAAuB,QAAQ,GAAG,MAAM,MAAM,GAAG,uBAAuB,CAAC,EAAE,EAC7F;AACJ,CAAC,CAAC,CACD,OAAO;AAEV,SAAS,eAAe,MAA4C;CAClE,OAAO,SAAS,iBAAiB,WAAW;AAC9C;AAEA,eAAsB,6BAA6B,SAKrB;CAC5B,MAAM,aAAa,MAAM,6BAA6B;EACpD,gBAAgB,QAAQ;EACxB,SAAS,QAAQ;EACjB,UAAU,QAAQ;CACpB,CAAC;CACD,IAAI,CAAC,YAAY,OAAO,CAAC;CACzB,MAAM,mBAAmB,WAAW,WAAY,MAAM,QAAQ,QAAQ,qBAAqB,WAAW,OAAO;CAC7G,IAAI,CAAC,kBAAkB,OAAO,CAAC;CAE/B,OAAO,EACL,8BAA8B,WAAW;EACvC,IAAI;EACJ,aACE;EACF,aAAa;EACb,iBAAiB;EACjB,SAAS,OAAO,EAAE,OAAO,kBAAkB,aAAa,cAAc;GAMpE,MAAM,kBAAiB,MALS,6BAA6B;IAC3D,gBAAgB,UAAU;IAC1B,SAAS,QAAQ;IACjB,UAAU,QAAQ;GACpB,CAAC,EAAA,EACyC,WAAW;GACrD,MAAM,aAAa,UAAU,OAAO;GACpC,IAAI,CAAC,kBAAkB,CAAC,YACtB,MAAM,IAAI,MAAM,qEAAqE;GAEvF,MAAM,UAAU,MAAM,QAAQ,QAAQ,qBAAqB,cAAc;GACzE,IAAI,CAAC,WAAW,QAAQ,OAAO,iBAAiB,IAC9C,MAAM,IAAI,MAAM,mFAAmF;GAErG,MAAM,OAAO,MAAM,QAAQ,QAAQ,IAAI;IAAE,OAAO,QAAQ;IAAO,IAAI,QAAQ;GAAW,CAAC;GACvF,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,oCAAoC;GAE/D,OAAO,QAAQ,kBAAkB,WAAW;IAC1C,OAAO,QAAQ;IACf,kBAAkB,QAAQ;IAC1B,YAAY,QAAQ;IACpB,OAAO,eAAe,KAAK,gBAAgB,IAAI;IAC/C;IACA;IACA,OAAO;KAAE,MAAM;KAAS,WAAW,QAAQ;KAAI,MAAM,QAAQ;IAAK;IAClE,SAAS;KAAE,MAAM;KAAS,UAAU,GAAG,QAAQ,GAAG,GAAG;IAAa;IAClE,OAAO;GACT,CAAC;EACH;CACF,CAAC,EACH;AACF"}
|