@mastra/factory 0.18.0 → 0.19.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/README.md +13 -1
  2. package/dist/boards/registry.d.ts.map +1 -1
  3. package/dist/boards/registry.js +0 -2
  4. package/dist/boards/registry.js.map +1 -1
  5. package/dist/factory.d.ts.map +1 -1
  6. package/dist/factory.js +12 -2
  7. package/dist/factory.js.map +1 -1
  8. package/dist/integrations/github/default-rules.d.ts +10 -2
  9. package/dist/integrations/github/default-rules.d.ts.map +1 -1
  10. package/dist/integrations/github/default-rules.js +28 -4
  11. package/dist/integrations/github/default-rules.js.map +1 -1
  12. package/dist/integrations/github/integration.js +1 -1
  13. package/dist/integrations/github/reconcile-worker.d.ts.map +1 -1
  14. package/dist/integrations/github/reconcile-worker.js +1 -2
  15. package/dist/integrations/github/reconcile-worker.js.map +1 -1
  16. package/dist/integrations/github/routes.d.ts.map +1 -1
  17. package/dist/integrations/github/routes.js +2 -15
  18. package/dist/integrations/github/routes.js.map +1 -1
  19. package/dist/integrations/github/rules.d.ts +5 -0
  20. package/dist/integrations/github/rules.d.ts.map +1 -1
  21. package/dist/integrations/github/rules.js +17 -8
  22. package/dist/integrations/github/rules.js.map +1 -1
  23. package/dist/integrations/github/stale-reviews.d.ts +22 -0
  24. package/dist/integrations/github/stale-reviews.d.ts.map +1 -0
  25. package/dist/integrations/github/stale-reviews.js +70 -0
  26. package/dist/integrations/github/stale-reviews.js.map +1 -0
  27. package/dist/integrations/gitlab/version-control.d.ts.map +1 -1
  28. package/dist/integrations/gitlab/version-control.js +4 -5
  29. package/dist/integrations/gitlab/version-control.js.map +1 -1
  30. package/dist/integrations/gitlab/webhook.d.ts.map +1 -1
  31. package/dist/integrations/gitlab/webhook.js +2 -2
  32. package/dist/integrations/gitlab/webhook.js.map +1 -1
  33. package/dist/integrations/issue-reconcile-worker.d.ts.map +1 -1
  34. package/dist/integrations/issue-reconcile-worker.js +1 -2
  35. package/dist/integrations/issue-reconcile-worker.js.map +1 -1
  36. package/dist/integrations/jira/integration.d.ts.map +1 -1
  37. package/dist/integrations/jira/integration.js +7 -1
  38. package/dist/integrations/jira/integration.js.map +1 -1
  39. package/dist/integrations/linear/integration.d.ts.map +1 -1
  40. package/dist/integrations/linear/integration.js +10 -30
  41. package/dist/integrations/linear/integration.js.map +1 -1
  42. package/dist/integrations/platform/github/event-worker.d.ts.map +1 -1
  43. package/dist/integrations/platform/github/event-worker.js +1 -2
  44. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  45. package/dist/integrations/platform/github/integration.js +1 -1
  46. package/dist/integrations/platform/gitlab/event-worker.d.ts.map +1 -1
  47. package/dist/integrations/platform/gitlab/event-worker.js +1 -2
  48. package/dist/integrations/platform/gitlab/event-worker.js.map +1 -1
  49. package/dist/integrations/platform/jira/integration.d.ts.map +1 -1
  50. package/dist/integrations/platform/jira/integration.js +7 -1
  51. package/dist/integrations/platform/jira/integration.js.map +1 -1
  52. package/dist/integrations/platform/linear/event-worker.d.ts.map +1 -1
  53. package/dist/integrations/platform/linear/event-worker.js +1 -2
  54. package/dist/integrations/platform/linear/event-worker.js.map +1 -1
  55. package/dist/integrations/platform/linear/integration.d.ts.map +1 -1
  56. package/dist/integrations/platform/linear/integration.js +9 -9
  57. package/dist/integrations/platform/linear/integration.js.map +1 -1
  58. package/dist/integrations/slack/slack.d.ts.map +1 -1
  59. package/dist/integrations/slack/slack.js +1 -2
  60. package/dist/integrations/slack/slack.js.map +1 -1
  61. package/dist/review-verdict.d.ts +6 -0
  62. package/dist/review-verdict.d.ts.map +1 -0
  63. package/dist/review-verdict.js +14 -0
  64. package/dist/review-verdict.js.map +1 -0
  65. package/dist/routes/attention-providers.d.ts +6 -7
  66. package/dist/routes/attention-providers.d.ts.map +1 -1
  67. package/dist/routes/attention-providers.js +12 -5
  68. package/dist/routes/attention-providers.js.map +1 -1
  69. package/dist/routes/attention.d.ts +2 -0
  70. package/dist/routes/attention.d.ts.map +1 -1
  71. package/dist/routes/attention.js +9 -3
  72. package/dist/routes/attention.js.map +1 -1
  73. package/dist/routes/oauth.d.ts.map +1 -1
  74. package/dist/routes/oauth.js +3 -3
  75. package/dist/routes/oauth.js.map +1 -1
  76. package/dist/routes/source-control-sessions.d.ts.map +1 -1
  77. package/dist/routes/source-control-sessions.js +1 -2
  78. package/dist/routes/source-control-sessions.js.map +1 -1
  79. package/dist/routes/work-items.d.ts.map +1 -1
  80. package/dist/routes/work-items.js +10 -5
  81. package/dist/routes/work-items.js.map +1 -1
  82. package/dist/rules/decision-applicability.d.ts +26 -0
  83. package/dist/rules/decision-applicability.d.ts.map +1 -0
  84. package/dist/rules/decision-applicability.js +47 -0
  85. package/dist/rules/decision-applicability.js.map +1 -0
  86. package/dist/rules/dispatcher.d.ts +4 -0
  87. package/dist/rules/dispatcher.d.ts.map +1 -1
  88. package/dist/rules/dispatcher.js +33 -24
  89. package/dist/rules/dispatcher.js.map +1 -1
  90. package/dist/rules/processor.d.ts.map +1 -1
  91. package/dist/rules/processor.js +5 -5
  92. package/dist/rules/processor.js.map +1 -1
  93. package/dist/rules/transition-service.d.ts.map +1 -1
  94. package/dist/rules/transition-service.js +1 -2
  95. package/dist/rules/transition-service.js.map +1 -1
  96. package/dist/rules/types.d.ts +18 -1
  97. package/dist/rules/types.d.ts.map +1 -1
  98. package/dist/rules/types.js.map +1 -1
  99. package/dist/rules/validation.d.ts.map +1 -1
  100. package/dist/rules/validation.js +20 -0
  101. package/dist/rules/validation.js.map +1 -1
  102. package/dist/secret-encryption.js +2 -2
  103. package/dist/secret-encryption.js.map +1 -1
  104. package/dist/session/factory-session.d.ts.map +1 -1
  105. package/dist/session/factory-session.js +1 -2
  106. package/dist/session/factory-session.js.map +1 -1
  107. package/dist/session/source-control-tools.d.ts +9 -0
  108. package/dist/session/source-control-tools.d.ts.map +1 -1
  109. package/dist/session/source-control-tools.js +51 -2
  110. package/dist/session/source-control-tools.js.map +1 -1
  111. package/dist/state-signing.js +3 -3
  112. package/dist/state-signing.js.map +1 -1
  113. package/dist/storage/domains/audit/base.d.ts.map +1 -1
  114. package/dist/storage/domains/audit/base.js +2 -3
  115. package/dist/storage/domains/audit/base.js.map +1 -1
  116. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  117. package/dist/storage/domains/source-control/inmemory.js +5 -6
  118. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  119. package/dist/storage/domains/work-items/base.d.ts +3 -0
  120. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  121. package/dist/storage/domains/work-items/base.js +15 -2
  122. package/dist/storage/domains/work-items/base.js.map +1 -1
  123. package/dist/supervisor/write-tools.d.ts +2 -0
  124. package/dist/supervisor/write-tools.d.ts.map +1 -1
  125. package/dist/supervisor/write-tools.js +3 -0
  126. package/dist/supervisor/write-tools.js.map +1 -1
  127. package/factory-skills/configure-factory-rules/SKILL.md +1 -1
  128. package/factory-skills/factory-gitlab-rereview/SKILL.md +1 -1
  129. package/factory-skills/factory-gitlab-review/SKILL.md +1 -1
  130. package/factory-skills/factory-rereview/SKILL.md +12 -3
  131. package/factory-skills/factory-review/SKILL.md +12 -3
  132. package/package.json +9 -9
@@ -8,10 +8,10 @@ import { moveCardToBoard } from "../boards/relocate.js";
8
8
  import { FactoryDispatchError, factoryDispatchFailureCode, factoryDispatchFailureMetadata } from "./dispatch-errors.js";
9
9
  import { isHumanActorId } from "../storage/domains/audit/actors.js";
10
10
  import { resolvePromptInvocation, resolveSkillInvocation, resolveSkillResumeInvocation } from "../skills/service.js";
11
+ import { decisionOvertaken } from "./decision-applicability.js";
11
12
  import { FACTORY_OPEN_RUN_STALE_MS, heartbeatSessionOpenRun, isOpenRunLiveElsewhere, listSessionOpenRuns, recordSessionRunStart, waitForSessionRunAudit } from "../session/run-audit.js";
12
13
  import { withWorkItemFeed } from "../storage/domains/comments/feed-context.js";
13
14
  import { RequestContext } from "@mastra/core/request-context";
14
- import { randomUUID } from "crypto";
15
15
  //#region src/rules/dispatcher.ts
16
16
  const LEASE_MS = 3e4;
17
17
  const POLL_MS = 1e3;
@@ -34,6 +34,8 @@ const BOOKKEEPING_MAX_IN_FLIGHT = 4;
34
34
  const STALE_BINDING_SWEEP_INTERVAL_MS = 10 * 6e4;
35
35
  const STALE_BINDING_TTL_MS = 1440 * 6e4;
36
36
  const RECONCILE_INTERVAL_MS = 3e4;
37
+ /** The card moved on before this run could act; it settles as superseded, not succeeded. */
38
+ var FactoryDecisionSuperseded = class extends Error {};
37
39
  function isTerminalFailure(attempts, failureCode) {
38
40
  return attempts >= MAX_ATTEMPTS || !factoryDispatchFailureMetadata(failureCode).canRetry;
39
41
  }
@@ -245,6 +247,7 @@ var FactoryDecisionDispatcher = class {
245
247
  #refreshManagedMemorySettings;
246
248
  #primeCredentials;
247
249
  #feedReader;
250
+ #dismissStaleReviews;
248
251
  #resolveLinkedWorkItemParentId;
249
252
  #maxInFlight;
250
253
  #staleBindingSweepIntervalMs;
@@ -269,7 +272,7 @@ var FactoryDecisionDispatcher = class {
269
272
  this.#transitionService = options.transitionService;
270
273
  this.#boards = options.boards ?? createBoardRegistry();
271
274
  this.#storage = options.storage;
272
- this.#ownerId = options.ownerId ?? `factory-dispatcher:${randomUUID()}`;
275
+ this.#ownerId = options.ownerId ?? `factory-dispatcher:${globalThis.crypto.randomUUID()}`;
273
276
  this.#isAutoRunEnabled = options.isAutoRunEnabled;
274
277
  this.#autoApprovePlans = options.autoApprovePlans;
275
278
  this.#reconcileToolResults = options.reconcileToolResults;
@@ -277,6 +280,7 @@ var FactoryDecisionDispatcher = class {
277
280
  this.#refreshManagedMemorySettings = options.refreshManagedMemorySettings;
278
281
  this.#primeCredentials = options.primeCredentials;
279
282
  this.#feedReader = options.feedReader;
283
+ this.#dismissStaleReviews = options.dismissStaleReviews;
280
284
  this.#resolveLinkedWorkItemParentId = options.resolveLinkedWorkItemParentId;
281
285
  const maxInFlight = options.maxInFlight ?? MAX_IN_FLIGHT;
282
286
  this.#maxInFlight = Number.isFinite(maxInFlight) && maxInFlight > 0 ? Math.floor(maxInFlight) : MAX_IN_FLIGHT;
@@ -417,6 +421,10 @@ var FactoryDecisionDispatcher = class {
417
421
  executionCompleted = true;
418
422
  if (!await this.#storage.completeDeferredDecision(leaseIdentity(record, this.#ownerId), /* @__PURE__ */ new Date())) throw new Error("Factory decision lease was lost before completion.");
419
423
  } catch (error) {
424
+ if (error instanceof FactoryDecisionSuperseded) {
425
+ await this.#storage.supersedeLeasedDecision(leaseIdentity(record, this.#ownerId), /* @__PURE__ */ new Date());
426
+ return;
427
+ }
420
428
  const failureCode = factoryDispatchFailureCode(error);
421
429
  const terminal = isTerminalFailure(record.attempts, failureCode);
422
430
  const failed = await this.#storage.failDeferredDecision({
@@ -565,7 +573,7 @@ var FactoryDecisionDispatcher = class {
565
573
  await this.#upsertLinkedItem(record, decision, nextChain);
566
574
  return;
567
575
  case "invokeSkill": {
568
- if (await this.#roleSuperseded(record, decision.role)) return;
576
+ if (await this.#roleSuperseded(record, decision.role) || await this.#decisionOvertaken(record, decision.role)) throw new FactoryDecisionSuperseded();
569
577
  const binding = await this.#requireOrPrepareBinding(record, decision.role);
570
578
  await this.#withBindingSkillRun(binding.id, async () => {
571
579
  const item = record.workItemId ? await this.#storage.get({
@@ -600,24 +608,7 @@ var FactoryDecisionDispatcher = class {
600
608
  await this.#switchThread(session, binding);
601
609
  const deliveryId = record.deliveryGeneration === 0 ? record.id : `${record.id}:retry:${record.deliveryGeneration}`;
602
610
  const kickoffLanded = async () => (await session.thread.listActiveMessages()).some((message) => message.id === deliveryId);
603
- const kickoffStale = async () => {
604
- if (await this.#roleSuperseded(record, decision.role)) return true;
605
- if ((await this.#findBinding(record, decision.role))?.id !== binding.id) return true;
606
- if (!record.workItemId) return false;
607
- const current = await this.#storage.get({
608
- orgId: record.orgId,
609
- id: record.workItemId
610
- });
611
- if (current === null) return true;
612
- const decidedAt = record.createdAt.getTime();
613
- const enteredSince = current.stageHistory.filter((entry) => entry.exitedAt === void 0 && new Date(entry.enteredAt).getTime() > decidedAt);
614
- const board = boardForWorkItem(current);
615
- const carriesRole = (stage) => resolvePhaseSemantics(this.#boards, board, stage)?.role === decision.role;
616
- const movedOn = !current.stageHistory.some((entry) => new Date(entry.enteredAt).getTime() <= decidedAt && (entry.exitedAt === void 0 || new Date(entry.exitedAt).getTime() > decidedAt) && carriesRole(entry.stage)) ? enteredSince.filter((entry) => !carriesRole(entry.stage)) : enteredSince;
617
- if (movedOn.length === 0) return false;
618
- const siblingStages = new Set((await this.#storage.listDeferredDecisions(record.orgId, record.factoryProjectId)).filter((sibling) => sibling.evaluationId === record.evaluationId && sibling.decision.type === "transition").map((sibling) => sibling.decision.stage));
619
- return movedOn.some((entry) => !siblingStages.has(entry.stage));
620
- };
611
+ const kickoffStale = async () => await this.#roleSuperseded(record, decision.role) || (await this.#findBinding(record, decision.role))?.id !== binding.id || await this.#decisionOvertaken(record, decision.role);
621
612
  const runStillActive = () => this.#controller.listActiveThreadRuns().some((active) => active.threadId === binding.threadId);
622
613
  while (true) {
623
614
  const openRun = watchRun(session, {
@@ -637,10 +628,10 @@ var FactoryDecisionDispatcher = class {
637
628
  }
638
629
  await waitForSessionRunAudit(session);
639
630
  }
640
- if (await this.#roleSuperseded(record, decision.role)) return;
631
+ if (await this.#roleSuperseded(record, decision.role)) throw new FactoryDecisionSuperseded();
641
632
  if ((await this.#requireBinding(record, decision.role)).id !== binding.id) throw new FactoryDispatchError("session_unavailable", `Factory binding ${binding.id} changed while waiting for its open run to end.`);
642
633
  if (await kickoffLanded()) return;
643
- if (await kickoffStale()) return;
634
+ if (await kickoffStale()) throw new FactoryDecisionSuperseded();
644
635
  const kickoffContents = await withWorkItemFeed(this.#feedReader, {
645
636
  orgId: record.orgId,
646
637
  factoryProjectId: record.factoryProjectId,
@@ -687,7 +678,7 @@ var FactoryDecisionDispatcher = class {
687
678
  if (await kickoffLanded()) break;
688
679
  if (!await run.wait()) throw new FactoryDispatchError("run_terminal_event_missing", "Factory skill invocation is waiting on a run whose terminal event was not observed.");
689
680
  if (await kickoffLanded()) break;
690
- if (await kickoffStale()) return;
681
+ if (await kickoffStale()) throw new FactoryDecisionSuperseded();
691
682
  run.arm();
692
683
  settled = await sendKickoff();
693
684
  }
@@ -737,6 +728,10 @@ var FactoryDecisionDispatcher = class {
737
728
  }), true);
738
729
  return;
739
730
  }
731
+ case "dismissStaleReviews":
732
+ if (!this.#dismissStaleReviews) throw new Error("GitHub review dismissal is not configured.");
733
+ await this.#dismissStaleReviews(decision);
734
+ return;
740
735
  case "notify": {
741
736
  const binding = await this.#requireBinding(record);
742
737
  const session = await this.#requireSession(binding);
@@ -944,6 +939,20 @@ var FactoryDecisionDispatcher = class {
944
939
  * fresh decision for the role (the card came back to it) must still dispatch
945
940
  * even though an older revoked binding for that role is on record.
946
941
  */
942
+ async #decisionOvertaken(record, role) {
943
+ if (!record.workItemId) return false;
944
+ const [item, siblings] = await Promise.all([this.#storage.get({
945
+ orgId: record.orgId,
946
+ id: record.workItemId
947
+ }), this.#storage.listDecisionsForEvaluations(record.orgId, record.factoryProjectId, [record.evaluationId])]);
948
+ return decisionOvertaken({
949
+ boards: this.#boards,
950
+ item,
951
+ record,
952
+ role,
953
+ siblings
954
+ });
955
+ }
947
956
  async #roleSuperseded(record, role) {
948
957
  if (!record.workItemId) return false;
949
958
  const bindings = await this.#storage.listRunBindings(record.orgId, record.factoryProjectId, record.workItemId);