@mastra/factory 0.9.0 → 0.10.0-alpha.10

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 (129) hide show
  1. package/CHANGELOG.md +179 -0
  2. package/dist/auth.d.ts.map +1 -1
  3. package/dist/auth.js +1 -1
  4. package/dist/auth.js.map +1 -1
  5. package/dist/factory.d.ts +8 -0
  6. package/dist/factory.d.ts.map +1 -1
  7. package/dist/factory.js +24 -8
  8. package/dist/factory.js.map +1 -1
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +2 -1
  12. package/dist/integrations/github/integration.d.ts.map +1 -1
  13. package/dist/integrations/github/integration.js +1 -0
  14. package/dist/integrations/github/integration.js.map +1 -1
  15. package/dist/integrations/github/provenance.d.ts +7 -2
  16. package/dist/integrations/github/provenance.d.ts.map +1 -1
  17. package/dist/integrations/github/provenance.js +16 -2
  18. package/dist/integrations/github/provenance.js.map +1 -1
  19. package/dist/integrations/github/routes.d.ts +3 -0
  20. package/dist/integrations/github/routes.d.ts.map +1 -1
  21. package/dist/integrations/github/routes.js +165 -6
  22. package/dist/integrations/github/routes.js.map +1 -1
  23. package/dist/integrations/github/rules.d.ts.map +1 -1
  24. package/dist/integrations/github/rules.js +193 -127
  25. package/dist/integrations/github/rules.js.map +1 -1
  26. package/dist/integrations/linear/routes.d.ts.map +1 -1
  27. package/dist/integrations/linear/routes.js +50 -0
  28. package/dist/integrations/linear/routes.js.map +1 -1
  29. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  30. package/dist/integrations/platform/github/integration.js +1 -0
  31. package/dist/integrations/platform/github/integration.js.map +1 -1
  32. package/dist/routes/attention.d.ts +17 -0
  33. package/dist/routes/attention.d.ts.map +1 -0
  34. package/dist/routes/attention.js +305 -0
  35. package/dist/routes/attention.js.map +1 -0
  36. package/dist/routes/intake.d.ts.map +1 -1
  37. package/dist/routes/intake.js +11 -2
  38. package/dist/routes/intake.js.map +1 -1
  39. package/dist/routes/surface.d.ts +2 -3
  40. package/dist/routes/surface.d.ts.map +1 -1
  41. package/dist/routes/surface.js +61 -43
  42. package/dist/routes/surface.js.map +1 -1
  43. package/dist/routes/work-items.d.ts.map +1 -1
  44. package/dist/routes/work-items.js +35 -14
  45. package/dist/routes/work-items.js.map +1 -1
  46. package/dist/rules/defaults.js +5 -5
  47. package/dist/rules/defaults.js.map +1 -1
  48. package/dist/rules/dispatch-errors.d.ts +13 -0
  49. package/dist/rules/dispatch-errors.d.ts.map +1 -0
  50. package/dist/rules/dispatch-errors.js +77 -0
  51. package/dist/rules/dispatch-errors.js.map +1 -0
  52. package/dist/rules/dispatcher.d.ts +9 -0
  53. package/dist/rules/dispatcher.d.ts.map +1 -1
  54. package/dist/rules/dispatcher.js +118 -45
  55. package/dist/rules/dispatcher.js.map +1 -1
  56. package/dist/rules/processor.d.ts.map +1 -1
  57. package/dist/rules/processor.js +30 -14
  58. package/dist/rules/processor.js.map +1 -1
  59. package/dist/rules/terminal-cleanup.d.ts +1 -1
  60. package/dist/rules/terminal-cleanup.d.ts.map +1 -1
  61. package/dist/rules/terminal-cleanup.js +2 -2
  62. package/dist/rules/terminal-cleanup.js.map +1 -1
  63. package/dist/rules/transition-service.d.ts +2 -1
  64. package/dist/rules/transition-service.d.ts.map +1 -1
  65. package/dist/rules/transition-service.js +1 -1
  66. package/dist/rules/transition-service.js.map +1 -1
  67. package/dist/rules/types.d.ts +2 -0
  68. package/dist/rules/types.d.ts.map +1 -1
  69. package/dist/rules/types.js +9 -1
  70. package/dist/rules/types.js.map +1 -1
  71. package/dist/secret-encryption.d.ts +25 -0
  72. package/dist/secret-encryption.d.ts.map +1 -0
  73. package/dist/secret-encryption.js +96 -0
  74. package/dist/secret-encryption.js.map +1 -0
  75. package/dist/session/factory-session.d.ts +13 -3
  76. package/dist/session/factory-session.d.ts.map +1 -1
  77. package/dist/session/factory-session.js +15 -6
  78. package/dist/session/factory-session.js.map +1 -1
  79. package/dist/session/first-exec-capture.d.ts +16 -10
  80. package/dist/session/first-exec-capture.d.ts.map +1 -1
  81. package/dist/session/first-exec-capture.js +74 -14
  82. package/dist/session/first-exec-capture.js.map +1 -1
  83. package/dist/session/first-message-capture.d.ts +14 -10
  84. package/dist/session/first-message-capture.d.ts.map +1 -1
  85. package/dist/session/first-message-capture.js +17 -11
  86. package/dist/session/first-message-capture.js.map +1 -1
  87. package/dist/session/session-title.d.ts +7 -0
  88. package/dist/session/session-title.d.ts.map +1 -0
  89. package/dist/session/session-title.js +14 -0
  90. package/dist/session/session-title.js.map +1 -0
  91. package/dist/session/thread-title-mirror.d.ts +35 -0
  92. package/dist/session/thread-title-mirror.d.ts.map +1 -0
  93. package/dist/session/thread-title-mirror.js +51 -0
  94. package/dist/session/thread-title-mirror.js.map +1 -0
  95. package/dist/storage/domains/credentials/base.d.ts +3 -1
  96. package/dist/storage/domains/credentials/base.d.ts.map +1 -1
  97. package/dist/storage/domains/credentials/base.js +33 -15
  98. package/dist/storage/domains/credentials/base.js.map +1 -1
  99. package/dist/storage/domains/custom-providers/base.d.ts +3 -1
  100. package/dist/storage/domains/custom-providers/base.d.ts.map +1 -1
  101. package/dist/storage/domains/custom-providers/base.js +45 -25
  102. package/dist/storage/domains/custom-providers/base.js.map +1 -1
  103. package/dist/storage/domains/intake/base.d.ts +6 -5
  104. package/dist/storage/domains/intake/base.d.ts.map +1 -1
  105. package/dist/storage/domains/intake/base.js +21 -13
  106. package/dist/storage/domains/intake/base.js.map +1 -1
  107. package/dist/storage/domains/integrations/base.d.ts +3 -1
  108. package/dist/storage/domains/integrations/base.d.ts.map +1 -1
  109. package/dist/storage/domains/integrations/base.js +28 -12
  110. package/dist/storage/domains/integrations/base.js.map +1 -1
  111. package/dist/storage/domains/source-control/base.d.ts +8 -0
  112. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  113. package/dist/storage/domains/source-control/base.js +14 -2
  114. package/dist/storage/domains/source-control/base.js.map +1 -1
  115. package/dist/storage/domains/source-control/inmemory.d.ts +4 -0
  116. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  117. package/dist/storage/domains/source-control/inmemory.js +7 -0
  118. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  119. package/dist/storage/domains/work-items/base.d.ts +115 -12
  120. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  121. package/dist/storage/domains/work-items/base.js +497 -43
  122. package/dist/storage/domains/work-items/base.js.map +1 -1
  123. package/dist/work-item-branch.d.ts +23 -0
  124. package/dist/work-item-branch.d.ts.map +1 -0
  125. package/dist/work-item-branch.js +39 -0
  126. package/dist/work-item-branch.js.map +1 -0
  127. package/factory-skills/factory-rereview/SKILL.md +2 -0
  128. package/factory-skills/factory-review/SKILL.md +2 -0
  129. package/package.json +7 -7
@@ -0,0 +1,77 @@
1
+ //#region src/rules/dispatch-errors.ts
2
+ const FAILURE_METADATA = {
3
+ session_unavailable: {
4
+ canRetry: true,
5
+ label: "Factory session unavailable"
6
+ },
7
+ source_control_missing: {
8
+ canRetry: true,
9
+ label: "Source-control connection unavailable"
10
+ },
11
+ source_repository_missing: {
12
+ canRetry: true,
13
+ label: "Source repository unavailable"
14
+ },
15
+ unsupported_provider_item: {
16
+ canRetry: false,
17
+ label: "Unsupported provider work item"
18
+ },
19
+ notification_delivery_failed: {
20
+ canRetry: true,
21
+ label: "Factory message delivery failed"
22
+ },
23
+ repository_git_missing: {
24
+ canRetry: false,
25
+ label: "Git is unavailable in the workspace"
26
+ },
27
+ repository_egress_blocked: {
28
+ canRetry: false,
29
+ label: "Repository network access is blocked"
30
+ },
31
+ repository_clone_failed: {
32
+ canRetry: true,
33
+ label: "Repository clone failed"
34
+ },
35
+ repository_pull_failed: {
36
+ canRetry: true,
37
+ label: "Repository update failed"
38
+ },
39
+ repository_push_failed: {
40
+ canRetry: true,
41
+ label: "Repository push failed"
42
+ },
43
+ repository_commit_failed: {
44
+ canRetry: true,
45
+ label: "Repository commit failed"
46
+ },
47
+ repository_cli_missing: {
48
+ canRetry: false,
49
+ label: "GitHub CLI is unavailable in the workspace"
50
+ },
51
+ repository_pr_failed: {
52
+ canRetry: true,
53
+ label: "Pull request creation failed"
54
+ },
55
+ unknown: {
56
+ canRetry: true,
57
+ label: "Factory automation failed"
58
+ }
59
+ };
60
+ var FactoryDispatchError = class extends Error {
61
+ code;
62
+ constructor(code, message, options) {
63
+ super(message, options);
64
+ this.code = code;
65
+ this.name = "FactoryDispatchError";
66
+ }
67
+ };
68
+ function factoryDispatchFailureCode(error) {
69
+ return error instanceof FactoryDispatchError ? error.code : "unknown";
70
+ }
71
+ function factoryDispatchFailureMetadata(code) {
72
+ return code === null ? FAILURE_METADATA.unknown : FAILURE_METADATA[code];
73
+ }
74
+ //#endregion
75
+ export { FactoryDispatchError, factoryDispatchFailureCode, factoryDispatchFailureMetadata };
76
+
77
+ //# sourceMappingURL=dispatch-errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch-errors.js","names":[],"sources":["../../src/rules/dispatch-errors.ts"],"sourcesContent":["import type { FactoryDispatchFailureCode } from '../storage/domains/work-items/base.js';\n\ninterface FactoryDispatchFailureMetadata {\n canRetry: boolean;\n label: string;\n}\n\nconst FAILURE_METADATA = {\n session_unavailable: { canRetry: true, label: 'Factory session unavailable' },\n source_control_missing: { canRetry: true, label: 'Source-control connection unavailable' },\n source_repository_missing: { canRetry: true, label: 'Source repository unavailable' },\n unsupported_provider_item: { canRetry: false, label: 'Unsupported provider work item' },\n notification_delivery_failed: { canRetry: true, label: 'Factory message delivery failed' },\n repository_git_missing: { canRetry: false, label: 'Git is unavailable in the workspace' },\n repository_egress_blocked: { canRetry: false, label: 'Repository network access is blocked' },\n repository_clone_failed: { canRetry: true, label: 'Repository clone failed' },\n repository_pull_failed: { canRetry: true, label: 'Repository update failed' },\n repository_push_failed: { canRetry: true, label: 'Repository push failed' },\n repository_commit_failed: { canRetry: true, label: 'Repository commit failed' },\n repository_cli_missing: { canRetry: false, label: 'GitHub CLI is unavailable in the workspace' },\n repository_pr_failed: { canRetry: true, label: 'Pull request creation failed' },\n unknown: { canRetry: true, label: 'Factory automation failed' },\n} satisfies Record<FactoryDispatchFailureCode, FactoryDispatchFailureMetadata>;\n\nexport class FactoryDispatchError extends Error {\n constructor(\n readonly code: FactoryDispatchFailureCode,\n message: string,\n options?: ErrorOptions,\n ) {\n super(message, options);\n this.name = 'FactoryDispatchError';\n }\n}\n\nexport function factoryDispatchFailureCode(error: unknown): FactoryDispatchFailureCode {\n return error instanceof FactoryDispatchError ? error.code : 'unknown';\n}\n\nexport function factoryDispatchFailureMetadata(\n code: FactoryDispatchFailureCode | null,\n): FactoryDispatchFailureMetadata {\n return code === null ? FAILURE_METADATA.unknown : FAILURE_METADATA[code];\n}\n"],"mappings":";AAOA,MAAM,mBAAmB;CACvB,qBAAqB;EAAE,UAAU;EAAM,OAAO;CAA8B;CAC5E,wBAAwB;EAAE,UAAU;EAAM,OAAO;CAAwC;CACzF,2BAA2B;EAAE,UAAU;EAAM,OAAO;CAAgC;CACpF,2BAA2B;EAAE,UAAU;EAAO,OAAO;CAAiC;CACtF,8BAA8B;EAAE,UAAU;EAAM,OAAO;CAAkC;CACzF,wBAAwB;EAAE,UAAU;EAAO,OAAO;CAAsC;CACxF,2BAA2B;EAAE,UAAU;EAAO,OAAO;CAAuC;CAC5F,yBAAyB;EAAE,UAAU;EAAM,OAAO;CAA0B;CAC5E,wBAAwB;EAAE,UAAU;EAAM,OAAO;CAA2B;CAC5E,wBAAwB;EAAE,UAAU;EAAM,OAAO;CAAyB;CAC1E,0BAA0B;EAAE,UAAU;EAAM,OAAO;CAA2B;CAC9E,wBAAwB;EAAE,UAAU;EAAO,OAAO;CAA6C;CAC/F,sBAAsB;EAAE,UAAU;EAAM,OAAO;CAA+B;CAC9E,SAAS;EAAE,UAAU;EAAM,OAAO;CAA4B;AAChE;AAEA,IAAa,uBAAb,cAA0C,MAAM;CAEnC;CADX,YACE,MACA,SACA,SACA;EACA,MAAM,SAAS,OAAO;EAJb,KAAA,OAAA;EAKT,KAAK,OAAO;CACd;AACF;AAEA,SAAgB,2BAA2B,OAA4C;CACrF,OAAO,iBAAiB,uBAAuB,MAAM,OAAO;AAC9D;AAEA,SAAgB,+BACd,MACgC;CAChC,OAAO,SAAS,OAAO,iBAAiB,UAAU,iBAAiB;AACrE"}
@@ -2,6 +2,7 @@ import type { MastraCodeState } from '@mastra/code-sdk/schema';
2
2
  import type { AgentController } from '@mastra/core/agent-controller';
3
3
  import type { FactoryDeferredDecisionRecord, WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';
4
4
  import type { FactoryTransitionService } from './transition-service.js';
5
+ import type { FactoryCommitDecision } from './types.js';
5
6
  type FactoryController = Pick<AgentController<MastraCodeState>, 'getSessionByResource'>;
6
7
  export interface FactoryBindingPreparationInput {
7
8
  record: FactoryDeferredDecisionRecord;
@@ -24,6 +25,12 @@ export interface FactoryDecisionDispatcherOptions {
24
25
  orgId: string;
25
26
  userId: string;
26
27
  }) => Promise<void>;
28
+ resolveLinkedWorkItemParentId?: (input: {
29
+ orgId: string;
30
+ decision: Extract<FactoryCommitDecision, {
31
+ type: 'upsertLinkedWorkItem';
32
+ }>;
33
+ }) => Promise<string | null>;
27
34
  maxInFlight?: number;
28
35
  /** How often the stale-binding sweep runs. Defaults to 10 minutes. */
29
36
  staleBindingSweepIntervalMs?: number;
@@ -31,6 +38,8 @@ export interface FactoryDecisionDispatcherOptions {
31
38
  staleBindingTtlMs?: number;
32
39
  /** How often the bound-thread reconcile walk runs. Defaults to 30 seconds. */
33
40
  reconcileIntervalMs?: number;
41
+ /** How long to wait for a run's terminal event before failing for retry. Defaults to 10 minutes. */
42
+ skillCompletionObservationTimeoutMs?: number;
34
43
  }
35
44
  export declare class FactoryDecisionDispatcher {
36
45
  #private;
@@ -1 +1 @@
1
- {"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/rules/dispatcher.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAgC,MAAM,+BAA+B,CAAC;AAKnG,OAAO,KAAK,EACV,6BAA6B,EAG7B,WAAW,EACX,gBAAgB,EACjB,MAAM,uCAAuC,CAAC;AAE/C,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AAkDxE,KAAK,iBAAiB,GAAG,IAAI,CAAC,eAAe,CAAC,eAAe,CAAC,EAAE,sBAAsB,CAAC,CAAC;AAExF,MAAM,WAAW,8BAA8B;IAC7C,MAAM,EAAE,6BAA6B,CAAC;IACtC,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gCAAgC;IAC/C,UAAU,EAAE,iBAAiB,CAAC;IAC9B,iBAAiB,EAAE,IAAI,CAAC,wBAAwB,EAAE,YAAY,CAAC,CAAC;IAChE,OAAO,EAAE,gBAAgB,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wFAAwF;IACxF,gBAAgB,EAAE,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5F,oBAAoB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,8BAA8B,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,sFAAsF;IACtF,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,8EAA8E;IAC9E,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AA0ED,qBAAa,yBAAyB;;gBAoBxB,OAAO,EAAE,gCAAgC;IAmBrD,KAAK,IAAI,IAAI;IAOP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAOrB,OAAO,CAAC,GAAG,OAAa,GAAG,OAAO,CAAC,IAAI,CAAC;CAwnB/C;AAED,eAAO,MAAM,0BAA0B;;;;;;;;;;CAU7B,CAAC"}
1
+ {"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/rules/dispatcher.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAyC,MAAM,+BAA+B,CAAC;AAK5G,OAAO,KAAK,EACV,6BAA6B,EAG7B,WAAW,EACX,gBAAgB,EACjB,MAAM,uCAAuC,CAAC;AAG/C,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,KAAK,EAAE,qBAAqB,EAA4C,MAAM,YAAY,CAAC;AA+DlG,KAAK,iBAAiB,GAAG,IAAI,CAAC,eAAe,CAAC,eAAe,CAAC,EAAE,sBAAsB,CAAC,CAAC;AAGxF,MAAM,WAAW,8BAA8B;IAC7C,MAAM,EAAE,6BAA6B,CAAC;IACtC,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gCAAgC;IAC/C,UAAU,EAAE,iBAAiB,CAAC;IAC9B,iBAAiB,EAAE,IAAI,CAAC,wBAAwB,EAAE,YAAY,CAAC,CAAC;IAChE,OAAO,EAAE,gBAAgB,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wFAAwF;IACxF,gBAAgB,EAAE,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5F,oBAAoB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,8BAA8B,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChF,6BAA6B,CAAC,EAAE,CAAC,KAAK,EAAE;QACtC,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,OAAO,CAAC,qBAAqB,EAAE;YAAE,IAAI,EAAE,sBAAsB,CAAA;SAAE,CAAC,CAAC;KAC5E,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,sFAAsF;IACtF,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,8EAA8E;IAC9E,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oGAAoG;IACpG,mCAAmC,CAAC,EAAE,MAAM,CAAC;CAC9C;AA+FD,qBAAa,yBAAyB;;gBAsBxB,OAAO,EAAE,gCAAgC;IAwBrD,KAAK,IAAI,IAAI;IAOP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAOrB,OAAO,CAAC,GAAG,OAAa,GAAG,OAAO,CAAC,IAAI,CAAC;CAmuB/C;AAED,eAAO,MAAM,0BAA0B;;;;;;;;;;CAU7B,CAAC"}
@@ -1,7 +1,8 @@
1
1
  import { FACTORY_RULE_STAGES } from "./types.js";
2
2
  import { validateFactoryRuleDecision } from "./validation.js";
3
- import { resolvePromptInvocation, resolveSkillInvocation } from "../skills/service.js";
4
3
  import { FACTORY_RULE_MATERIALIZATION_KEY } from "../storage/domains/work-items/base.js";
4
+ import { FactoryDispatchError, factoryDispatchFailureCode } from "./dispatch-errors.js";
5
+ import { resolvePromptInvocation, resolveSkillInvocation } from "../skills/service.js";
5
6
  import { randomUUID } from "crypto";
6
7
  import { RequestContext } from "@mastra/core/request-context";
7
8
  //#region src/rules/dispatcher.ts
@@ -16,9 +17,9 @@ const MAX_IN_FLIGHT = 25;
16
17
  const STALE_BINDING_SWEEP_INTERVAL_MS = 10 * 6e4;
17
18
  const STALE_BINDING_TTL_MS = 1440 * 6e4;
18
19
  const RECONCILE_INTERVAL_MS = 3e4;
19
- function waitForAgentEndOrTimeout(agentEnd) {
20
+ function waitForAgentEndOrTimeout(agentEnd, timeoutMs) {
20
21
  return new Promise((resolve) => {
21
- const timeout = setTimeout(() => resolve(false), SKILL_COMPLETION_OBSERVATION_TIMEOUT_MS);
22
+ const timeout = setTimeout(() => resolve(false), timeoutMs);
22
23
  timeout.unref?.();
23
24
  agentEnd.then(() => {
24
25
  clearTimeout(timeout);
@@ -65,19 +66,26 @@ function leaseIdentity(record, ownerId) {
65
66
  ownerId
66
67
  };
67
68
  }
68
- async function awaitNotification(result, requireDelivery = false) {
69
- await result.persisted;
70
- if (!result.accepted) {
71
- if (requireDelivery) throw new Error("Factory notification was persisted without agent delivery.");
72
- return;
73
- }
74
- const accepted = await result.accepted;
75
- if (!requireDelivery) return;
76
- if (accepted.action === "wake") {
77
- await accepted.output.consumeStream();
78
- return;
69
+ async function awaitNotification(send, requireDelivery = false) {
70
+ try {
71
+ const notification = await send();
72
+ const [, accepted] = await Promise.all([notification.persisted, notification.accepted]);
73
+ if (!accepted) {
74
+ if (requireDelivery) throw new FactoryDispatchError("notification_delivery_failed", "Factory notification was persisted without agent delivery.");
75
+ return;
76
+ }
77
+ if (!requireDelivery) return accepted;
78
+ if (accepted.action === "wake") {
79
+ if (!accepted.output) throw new FactoryDispatchError("notification_delivery_failed", "Factory notification wake had no output.");
80
+ await accepted.output.consumeStream();
81
+ return accepted;
82
+ }
83
+ if (accepted.action !== "deliver") throw new FactoryDispatchError("notification_delivery_failed", `Factory notification did not reach the agent (${String(accepted.action)}).`);
84
+ return accepted;
85
+ } catch (error) {
86
+ if (error instanceof FactoryDispatchError) throw error;
87
+ throw new FactoryDispatchError("notification_delivery_failed", `Factory notification delivery failed: ${sanitizeDispatchError(error)}`, { cause: error });
79
88
  }
80
- if (accepted.action !== "deliver") throw new Error(`Factory notification did not reach the agent (${String(accepted.action)}).`);
81
89
  }
82
90
  var FactoryDecisionDispatcher = class {
83
91
  #controller;
@@ -88,11 +96,13 @@ var FactoryDecisionDispatcher = class {
88
96
  #reconcileToolResults;
89
97
  #prepareBinding;
90
98
  #primeCredentials;
99
+ #resolveLinkedWorkItemParentId;
91
100
  #maxInFlight;
92
101
  #staleBindingSweepIntervalMs;
93
102
  #staleBindingTtlMs;
94
103
  #lastStaleBindingSweepAt;
95
104
  #reconcileIntervalMs;
105
+ #skillCompletionObservationTimeoutMs;
96
106
  #lastReconcileAt;
97
107
  #reconcileInFlight;
98
108
  #timer;
@@ -107,11 +117,13 @@ var FactoryDecisionDispatcher = class {
107
117
  this.#reconcileToolResults = options.reconcileToolResults;
108
118
  this.#prepareBinding = options.prepareBinding;
109
119
  this.#primeCredentials = options.primeCredentials;
120
+ this.#resolveLinkedWorkItemParentId = options.resolveLinkedWorkItemParentId;
110
121
  const maxInFlight = options.maxInFlight ?? MAX_IN_FLIGHT;
111
122
  this.#maxInFlight = Number.isFinite(maxInFlight) && maxInFlight > 0 ? Math.floor(maxInFlight) : MAX_IN_FLIGHT;
112
123
  this.#staleBindingSweepIntervalMs = positiveMs(options.staleBindingSweepIntervalMs, STALE_BINDING_SWEEP_INTERVAL_MS);
113
124
  this.#staleBindingTtlMs = positiveMs(options.staleBindingTtlMs, STALE_BINDING_TTL_MS);
114
125
  this.#reconcileIntervalMs = positiveMs(options.reconcileIntervalMs, RECONCILE_INTERVAL_MS);
126
+ this.#skillCompletionObservationTimeoutMs = positiveMs(options.skillCompletionObservationTimeoutMs, SKILL_COMPLETION_OBSERVATION_TIMEOUT_MS);
115
127
  }
116
128
  start() {
117
129
  if (this.#timer) return;
@@ -213,6 +225,7 @@ var FactoryDecisionDispatcher = class {
213
225
  }
214
226
  }
215
227
  async #dispatchDecision(record, now) {
228
+ let executionCompleted = false;
216
229
  try {
217
230
  const decision = validateFactoryRuleDecision(record.decision, record.causalChain.length);
218
231
  if (decision.type === "reject") throw new Error("Deferred Factory decisions cannot reject.");
@@ -222,6 +235,7 @@ var FactoryDecisionDispatcher = class {
222
235
  }
223
236
  await this.#supersedeProposals(record, decision);
224
237
  await this.#withLease(async (leaseExpiresAt) => this.#storage.renewDeferredDecisionLease(leaseIdentity(record, this.#ownerId), leaseExpiresAt), async () => this.#executeDecision(record, decision));
238
+ executionCompleted = true;
225
239
  if (!await this.#storage.completeDeferredDecision(leaseIdentity(record, this.#ownerId), /* @__PURE__ */ new Date())) throw new Error("Factory decision lease was lost before completion.");
226
240
  } catch (error) {
227
241
  const terminal = record.attempts >= MAX_ATTEMPTS;
@@ -230,7 +244,9 @@ var FactoryDecisionDispatcher = class {
230
244
  now: /* @__PURE__ */ new Date(),
231
245
  availableAt: retryAt(now, record.attempts),
232
246
  lastError: sanitizeDispatchError(error),
233
- terminal
247
+ failureCode: factoryDispatchFailureCode(error),
248
+ terminal,
249
+ advanceDeliveryGeneration: !executionCompleted
234
250
  });
235
251
  }
236
252
  }
@@ -244,12 +260,12 @@ var FactoryDecisionDispatcher = class {
244
260
  async #supersedeProposals(record, decision) {
245
261
  if (decision.type !== "invokeSkill" || !record.workItemId) return;
246
262
  try {
247
- await this.#storage.dismissProposalsForWorkItem({
263
+ await this.#storage.supersedeDecisionsForWorkItem({
248
264
  orgId: record.orgId,
249
265
  factoryProjectId: record.factoryProjectId,
250
266
  workItemId: record.workItemId,
251
267
  role: decision.role,
252
- dismissedAt: /* @__PURE__ */ new Date()
268
+ supersededAt: /* @__PURE__ */ new Date()
253
269
  });
254
270
  } catch (error) {
255
271
  console.error("Factory proposal supersede failed", sanitizeDispatchError(error));
@@ -296,8 +312,9 @@ var FactoryDecisionDispatcher = class {
296
312
  ...decision.reenter ? { reenter: true } : {}
297
313
  });
298
314
  if (result.status === "rejected") throw new Error(`${result.code}: ${result.reason}`);
299
- if (!decision.message) return;
300
- const binding = await this.#findBinding(record, decision.message.role);
315
+ const transitionMessage = decision.message;
316
+ if (!transitionMessage) return;
317
+ const binding = await this.#findBinding(record, transitionMessage.role);
301
318
  if (!binding) return;
302
319
  const startedBy = item.sessions[binding.role]?.startedBy;
303
320
  if (!startedBy) return;
@@ -310,12 +327,14 @@ var FactoryDecisionDispatcher = class {
310
327
  workosId: startedBy,
311
328
  organizationId: record.orgId
312
329
  });
313
- await awaitNotification(await (await this.#requireSession(binding)).sendNotificationSignal({
330
+ const session = await this.#findSession(binding);
331
+ if (!session) return;
332
+ await awaitNotification(() => session.sendNotificationSignal({
314
333
  source: "factory",
315
334
  kind: "rule-message",
316
- summary: decision.message.text,
335
+ summary: transitionMessage.text,
317
336
  priority: "high",
318
- payload: { message: decision.message.text },
337
+ payload: { message: transitionMessage.text },
319
338
  sourceId: record.id,
320
339
  dedupeKey: record.idempotencyKey
321
340
  }, {
@@ -354,14 +373,16 @@ var FactoryDecisionDispatcher = class {
354
373
  });
355
374
  const session = resolved.session;
356
375
  await this.#switchThread(session, binding);
357
- if ((await session.thread.listActiveMessages()).some((message) => message.id === record.id)) return;
376
+ const deliveryId = record.deliveryGeneration === 0 ? record.id : `${record.id}:retry:${record.deliveryGeneration}`;
377
+ if ((await session.thread.listActiveMessages()).some((message) => message.id === deliveryId)) return;
358
378
  if (decision.cancelInFlight) session.abort();
359
- if (decision.precedingMessage) await awaitNotification(await session.sendNotificationSignal({
379
+ const precedingMessage = decision.precedingMessage;
380
+ if (precedingMessage) await awaitNotification(() => session.sendNotificationSignal({
360
381
  source: "factory",
361
382
  kind: "stage-transition",
362
- summary: decision.precedingMessage,
383
+ summary: precedingMessage,
363
384
  priority: "medium",
364
- payload: { message: decision.precedingMessage },
385
+ payload: { message: precedingMessage },
365
386
  sourceId: `${record.id}:stage-transition`,
366
387
  dedupeKey: `${record.idempotencyKey}:stage-transition`
367
388
  }, {
@@ -387,7 +408,7 @@ var FactoryDecisionDispatcher = class {
387
408
  });
388
409
  const sendKickoff = async () => {
389
410
  const settled = await session.sendSignal({
390
- id: record.id,
411
+ id: deliveryId,
391
412
  type: "user",
392
413
  tagName: "user",
393
414
  contents: resolved.message
@@ -401,17 +422,14 @@ var FactoryDecisionDispatcher = class {
401
422
  try {
402
423
  let settled = await sendKickoff();
403
424
  if (settled.action === "deliver") {
404
- if (!(await session.thread.listActiveMessages()).some((message) => message.id === record.id)) {
405
- if (!await waitForAgentEndOrTimeout(agentEnd)) throw new Error("Factory skill invocation is waiting on a run that has not ended.");
425
+ if (!(await session.thread.listActiveMessages()).some((message) => message.id === deliveryId)) {
426
+ if (!await waitForAgentEndOrTimeout(agentEnd, this.#skillCompletionObservationTimeoutMs)) throw new Error("Factory skill invocation is waiting on a run that has not ended.");
406
427
  armAgentEnd();
407
428
  settled = await sendKickoff();
408
429
  if (settled.action !== "wake") throw new Error("Factory skill invocation was queued onto an ending run and never reached the agent.");
409
430
  }
410
431
  }
411
- if (!await waitForAgentEndOrTimeout(agentEnd)) console.warn("Factory skill run terminal event was not observed before timeout", {
412
- decisionId: record.id,
413
- runId: settled.action === "wake" ? settled.runId : void 0
414
- });
432
+ if (!await waitForAgentEndOrTimeout(agentEnd, this.#skillCompletionObservationTimeoutMs)) throw new Error("Factory skill run terminal event was not observed before timeout.");
415
433
  else if (endReason === "error") throw new Error("Factory skill run ended in error.");
416
434
  else if (endReason === "aborted") throw new Error("Factory skill run was aborted before it finished.");
417
435
  } finally {
@@ -435,7 +453,8 @@ var FactoryDecisionDispatcher = class {
435
453
  workosId: startedBy,
436
454
  organizationId: record.orgId
437
455
  });
438
- await awaitNotification(await (await this.#requireSession(binding)).sendNotificationSignal({
456
+ const session = await this.#requireSession(binding);
457
+ await awaitNotification(() => session.sendNotificationSignal({
439
458
  source: "factory",
440
459
  kind: "rule-message",
441
460
  summary: decision.message,
@@ -452,7 +471,8 @@ var FactoryDecisionDispatcher = class {
452
471
  }
453
472
  case "notify": {
454
473
  const binding = await this.#requireBinding(record);
455
- await awaitNotification(await (await this.#requireSession(binding)).sendNotificationSignal({
474
+ const session = await this.#requireSession(binding);
475
+ await awaitNotification(() => session.sendNotificationSignal({
456
476
  source: "factory",
457
477
  kind: "rule-notification",
458
478
  summary: decision.title,
@@ -467,13 +487,17 @@ var FactoryDecisionDispatcher = class {
467
487
  }
468
488
  }
469
489
  async #upsertLinkedItem(record, decision, causalChain) {
470
- const result = await this.#storage.upsert({
490
+ const parentWorkItemId = record.workItemId ?? await this.#resolveLinkedWorkItemParentId?.({
491
+ orgId: record.orgId,
492
+ decision
493
+ }) ?? null;
494
+ let result = await this.#storage.upsert({
471
495
  orgId: record.orgId,
472
496
  userId: "factory-rule-dispatcher",
473
497
  factoryProjectId: record.factoryProjectId,
474
498
  input: {
475
499
  externalSource: externalSourceForDecision(decision),
476
- parentWorkItemId: record.workItemId,
500
+ parentWorkItemId,
477
501
  title: decision.title,
478
502
  stages: ["intake"],
479
503
  sessions: {},
@@ -484,6 +508,18 @@ var FactoryDecisionDispatcher = class {
484
508
  },
485
509
  reuseMode: "preserve"
486
510
  });
511
+ if (!result.item.parentWorkItemId && parentWorkItemId) {
512
+ const item = await this.#storage.setParentWorkItemIfMissing({
513
+ orgId: record.orgId,
514
+ id: result.item.id,
515
+ userId: "factory-rule-dispatcher",
516
+ parentWorkItemId
517
+ });
518
+ if (item) result = {
519
+ ...result,
520
+ item
521
+ };
522
+ }
487
523
  const materializedByDecision = result.item.metadata?.[FACTORY_RULE_MATERIALIZATION_KEY] === record.idempotencyKey;
488
524
  if (!materializedByDecision && (decision.stage === "intake" || !result.item.stages.includes("intake"))) return;
489
525
  const board = decision.board;
@@ -554,7 +590,7 @@ var FactoryDecisionDispatcher = class {
554
590
  }
555
591
  async #requireBinding(record, role) {
556
592
  const binding = await this.#findBinding(record, role);
557
- if (!binding) throw new Error(role ? `No active Factory binding for role ${role}.` : "No active Factory binding.");
593
+ if (!binding) throw new FactoryDispatchError("session_unavailable", role ? `No active Factory binding for role ${role}.` : "No active Factory binding.");
558
594
  return binding;
559
595
  }
560
596
  async #requireOrPrepareBinding(record, role) {
@@ -562,7 +598,7 @@ var FactoryDecisionDispatcher = class {
562
598
  if (binding) {
563
599
  if (await this.#controller.getSessionByResource(binding.resourceId)) return binding;
564
600
  }
565
- if (!this.#prepareBinding) throw new Error(binding ? "Bound Factory session not found." : `No active Factory binding for role ${role}.`);
601
+ if (!this.#prepareBinding) throw new FactoryDispatchError("session_unavailable", binding ? "Bound Factory session not found." : `No active Factory binding for role ${role}.`);
566
602
  const item = await this.#requireItem(record);
567
603
  await this.#prepareBinding({
568
604
  record,
@@ -571,12 +607,17 @@ var FactoryDecisionDispatcher = class {
571
607
  });
572
608
  return this.#requireBinding(record, role);
573
609
  }
574
- async #requireSession(binding) {
610
+ async #findSession(binding) {
575
611
  const session = await this.#controller.getSessionByResource(binding.resourceId);
576
- if (!session) throw new Error("Bound Factory session not found.");
612
+ if (!session) return void 0;
577
613
  await this.#switchThread(session, binding);
578
614
  return session;
579
615
  }
616
+ async #requireSession(binding) {
617
+ const session = await this.#findSession(binding);
618
+ if (!session) throw new FactoryDispatchError("session_unavailable", "Bound Factory session not found.");
619
+ return session;
620
+ }
580
621
  async #switchThread(session, binding) {
581
622
  await session.thread.switch({ threadId: binding.threadId });
582
623
  }
@@ -607,7 +648,7 @@ var FactoryDecisionDispatcher = class {
607
648
  await this.#withLease(async (leaseExpiresAt) => this.#storage.renewPendingStartLease(leaseIdentity(record, this.#ownerId), leaseExpiresAt), async () => {
608
649
  if (record.message === null) return;
609
650
  const binding = (await this.#storage.listRunBindings(record.orgId, record.factoryProjectId)).find((candidate) => candidate.id === record.bindingId && candidate.status === "active");
610
- if (!binding) throw new Error("Prepared Factory binding is unavailable or revoked.");
651
+ if (!binding) throw new FactoryDispatchError("session_unavailable", "Prepared Factory binding is unavailable or revoked.");
611
652
  const startedBy = (await this.#storage.get({
612
653
  orgId: record.orgId,
613
654
  id: binding.workItemId
@@ -622,19 +663,50 @@ var FactoryDecisionDispatcher = class {
622
663
  workosId: startedBy,
623
664
  organizationId: record.orgId
624
665
  });
625
- await awaitNotification(await (await this.#requireSession(binding)).sendNotificationSignal({
666
+ const session = await this.#requireSession(binding);
667
+ let resolveAgentEnd;
668
+ let agentEnd;
669
+ let endReason;
670
+ const armAgentEnd = () => {
671
+ endReason = void 0;
672
+ agentEnd = new Promise((resolve) => {
673
+ resolveAgentEnd = resolve;
674
+ });
675
+ };
676
+ armAgentEnd();
677
+ const unsubscribe = session.subscribe((event) => {
678
+ if (event.type === "agent_end") {
679
+ endReason = event.reason;
680
+ resolveAgentEnd();
681
+ }
682
+ });
683
+ const sendKickoff = (dedupeKey) => awaitNotification(() => session.sendNotificationSignal({
626
684
  source: "factory",
627
685
  kind: "run-kickoff",
628
686
  summary: record.message,
629
687
  priority: "high",
630
688
  payload: { message: record.message },
631
689
  sourceId: record.id,
632
- dedupeKey: `factory-kickoff:${record.kickoffKey}`
690
+ dedupeKey
633
691
  }, {
634
692
  ifActive: { behavior: "deliver" },
635
693
  ifIdle: { behavior: "wake" },
636
694
  requestContext
637
695
  }), true);
696
+ try {
697
+ let settled = await sendKickoff(`factory-kickoff:${record.kickoffKey}`);
698
+ if (settled?.action === "deliver") {
699
+ if (!await waitForAgentEndOrTimeout(agentEnd, this.#skillCompletionObservationTimeoutMs)) throw new Error("Factory kickoff is waiting on a run that has not ended.");
700
+ armAgentEnd();
701
+ settled = await sendKickoff(`factory-kickoff:${record.kickoffKey}:retry:${record.attempts}`);
702
+ if (settled?.action !== "wake") throw new Error("Factory kickoff was queued onto an ending run and never reached the agent.");
703
+ }
704
+ if (!await waitForAgentEndOrTimeout(agentEnd, this.#skillCompletionObservationTimeoutMs)) throw new Error("Factory kickoff run terminal event was not observed before timeout.");
705
+ else if (endReason === "error") throw new Error("Factory kickoff run ended in error.");
706
+ else if (endReason === "aborted") throw new Error("Factory kickoff run was aborted before it finished.");
707
+ } finally {
708
+ unsubscribe();
709
+ }
638
710
  });
639
711
  if (!await this.#storage.completePendingStart(leaseIdentity(record, this.#ownerId), /* @__PURE__ */ new Date())) throw new Error("Factory kickoff lease was lost before completion.");
640
712
  } catch (error) {
@@ -643,6 +715,7 @@ var FactoryDecisionDispatcher = class {
643
715
  now: /* @__PURE__ */ new Date(),
644
716
  availableAt: retryAt(now, record.attempts),
645
717
  lastError: sanitizeDispatchError(error),
718
+ failureCode: factoryDispatchFailureCode(error),
646
719
  terminal: record.attempts >= MAX_ATTEMPTS
647
720
  });
648
721
  }